Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add <Routes baseHref> #353

Closed
CezaryDanielNowak opened this issue Oct 5, 2014 · 52 comments
Closed

Add <Routes baseHref> #353

CezaryDanielNowak opened this issue Oct 5, 2014 · 52 comments

Comments

@CezaryDanielNowak
Copy link

Hey!
When website is in subdirectory (like localhost/projects/MyProject), react router triggers NotFoundRoute instead of DefaultRoute for /.

It would be cool if there is base href option for react-router.

Thanks, Cezary

@CezaryDanielNowak
Copy link
Author

OK. I found initialPath property for Routes constructor. Sorry for ticket duplication.

I'd recommend to rename this property to baseHref, cause it's known for front-end developers since HTML 3.2.

@mjackson
Copy link
Member

mjackson commented Oct 6, 2014

@korpirkor initialPath is not what you're looking for. We don't currently have a way to specify a base URL for all your routes. Instead, you can specify full paths in your <Routes> config.

I'm going to leave this open tho because I think <Routes baseHref> could be interesting. Anyone else have this use case?

@ryanflorence
Copy link
Member

@mjackson yeah, but we were all talking about different things here #111

In our app we find it decent to just add the base url to our root route path, everything else is relative from there and not a big deal.

<Routes>
  <Route path="/some/sub/dir">
    <!-- since its relative here, I only have to specify the sub directory once
      which is not really different than a baseUrl option, is it? --> 
   <Route path="foo" />
  </Route>
</Routes>

@mjackson
Copy link
Member

mjackson commented Oct 6, 2014

Yeah, it's not too much different. I still think <Routes baseHref> could be a nice feature tho. Then you could still use absolute paths in your <Route>s and add/remove the baseHref without changing anything else. Makes it a little more portable.

@mjackson mjackson changed the title base href support Add <Routes baseHref> Oct 9, 2014
@mjackson
Copy link
Member

<Routes> are gone in 0.11, so this doesn't really make sense any more. If you'd like to have a "base URL", please use the <Route> component as @rpflorence suggested.

@let4be
Copy link

let4be commented Jul 31, 2015

Can't make it work @mjackson

I'm developing using webpack so my root url on development machine is like this http://localhost:3000/webpack-dev-server/

<Route path="/">
    <Route path="webpack-dev-server/" handler={App}>
        <Route name="job" handler={Job}/>
        <Route name="about" handler={About}/>
        <DefaultRoute handler={Job}/>
    </Route>
</Route>

I'm i'm doing just

<Route name="app" path="/" handler={App}>
    <Route name="job" handler={Job}/>
    <Route name="about" handler={About}/>
    <DefaultRoute handler={Job}/>
</Route>

it works with subdirectory but I don't see path changing in the address bar when navigate thru the states.
I have html5 mode enabled in webpack-dev-server and it returns index.html for any url

@let4be
Copy link

let4be commented Jul 31, 2015

I'm running router with

Router.run(routes, Router.HistoryLocation, Handler => {
    React.render(<Handler/>, document.getElementById('app'));
});

so it's html5 mode

@jhchill666
Copy link

Is this now possible, or still considered unnecessary?

Am using react-router@1.0.0-beta3, by the by.

Unfortunately for me, my root path is different on different servers, and again different on our clients prod domains.

I could set the root path dynamically, having assessed the domain structure, but seems fairly brittle. Also, maybe I'm missing something, but Redirects seem to need the full route path for to and from. If this is the case, it makes setting the root path dynamically pretty awkward, as I'd have to prepend all redirects to's and from's with the server context.

let base = "/server/base";

<Route name="app" path={base} handler={App}>
    <Redirect from={base} to{`${base}/SomePage`}/>
    <Route name="page" handler={SomePage}/>
</Route>

SomeOtherClass.js

let base = getBase();
this.context.router.transitionTo(`${base}/login`);

@vrde
Copy link

vrde commented Oct 13, 2015

I need it as well for the same reasons @jamiehill explained (the root path is different on different servers). It'd be great if we don't have to pass the baseUrl around, any updates about that?

@vrde
Copy link

vrde commented Oct 13, 2015

Maybe the cleanest way is to write a helper to create URLs to prepend the baseUrl to every URL if the URL is absolute (i.e. it starts with a /).

Another solution is to monkey patch some react-router internals, but I still have to figure out where to do this, if any of you can point me to a specific function it'd be nice 😸

@vrde
Copy link

vrde commented Oct 13, 2015

OK I found a very interesting example.

You can add the basename while creating the history instance, it looks like this:

import { createHistory, useBasename } from 'history'

const history = useBasename(createHistory)({
  basename: '/my-custom-root'
})

@anjianshi
Copy link

Thanks @vrde

I have an app runs under http://domain.com/app_name/
Without this, every times I create <Router> or <Link>, I need put the app_name into URL,
Like this:

let base = "/app_name/"

<Router history={browserHistory}>
    <Route path={base} component={App}></Route>
</Router>

<Link path={base + "some_path"}>some_path</Link>

With basename, I can just:

const browserHistory = useBasename(createHistory)({
    basename: "/app_name"
});

<Router history={browserHistory}>
    <Route path="/" component={App}></Route>
</Router>

<Link path="/some_path">some_path</Link>

It's more convenience and clear.

@jhchill666
Copy link

Sorry what version are we talking here @anjianshi ?

@anjianshi
Copy link

@jamiehill react-router@1.0.0-rc3

const React = require("react");
const ReactDOM = require("react-dom");

import { Router, IndexRoute } from "react-router";
import { createHistory, useBasename } from "history";

const App = require("./App.jsx");
const Home = require("./Home.jsx");

const browserHistory = useBasename(createHistory)({
    basename: "/app_name"
});

ReactDOM.render(
    <Router history={browserHistory}>
        <Route path="/" component={App}>
            <IndexRoute component={Home} />
        </Route>
    </Router>,
    document.getElementById("wrap")
);

@taion
Copy link
Contributor

taion commented Oct 15, 2015

Yes, that's what useBasename is for. Please stop commenting on closed issues unless there are further problems.

@jhchill666
Copy link

I'm sorry but sometimes an issue is closed before all questions have been satisfied. And that said, both @mjackson and @ryanflorence have added comments since it was closed.

@vrde
Copy link

vrde commented Oct 15, 2015

@taion yes it was a necropost from my side, but I found this issue googling the keywords react-router baseurl. I'm quite sure many developers will land here using similar keywords, that's why I added some info about the solution.

@communicatedesign
Copy link

@vrde I owe you a beer, dude. I was really stuck on this problem before I found your post. Thank you!

@richgong
Copy link

richgong commented Dec 1, 2015

useBasename is awesome for client-side. Any ideas how to have links include baseName for server-side rendering?

@tobice
Copy link

tobice commented Jan 20, 2016

I managed to get this working in 2.0.0-rc5:

const browserHistory = useRouterHistory(useBasename(createHistory))({
  basename: "/baseurl"
});

Unfortunately, it only worked with the provided Link component. I'm using redux-simple-router over react-router which means that I usually change the URL using an API call (dispatching appropriate action). In this case the configured basename is completely ignored.

I solved it by wrapping the API by my own function that simply prepends the base url and I also added the base url to the root Route which means that I don't need the custom history at all (the base url value is stored in a config). I guess that this way it should work on server as well. The last remaining step is to create my own Link component but I haven't gotten there yet.

To be honest, I kind of miss the times before 1.0.0 when route naming and reversed URL resolving was still part of react-router. I wouldn't have to deal with this at all.

@taion
Copy link
Contributor

taion commented Jan 20, 2016

It appears that you are doing multiple things wrong. Consider reaching out on Stack Overflow or Reactiflux for support.

@tobice
Copy link

tobice commented Jan 20, 2016

Ah :D Well it's very possible. I had the same problem this thread addresses and since it's been active until pretty recently I considered the information here pretty up-to-date.

@taion
Copy link
Contributor

taion commented Jan 20, 2016

A lot of things change, especially internally.

We don't want to encourage the use of the issue tracker for this sort of thing, because the issues stay the same while the code marches onward.

It's not generally safe to follow stale old issue tracker examples, and it's confusing to us as well when people point to very old examples and expect them to work.

@tobice
Copy link

tobice commented Jan 20, 2016

I understand that. Sorry. Do you think you could point me to a good source of information regarding this problem? This is the only source I was able to find.

@taion
Copy link
Contributor

taion commented Jan 20, 2016

As I just said, try Stack Overflow or Reactiflux.

@clintharris
Copy link

For fellow React noobs who might not consider this "obvious" alternative right away: this isn't an issue if you use the old-fashioned, hash-based URLs (via createHashHistory in 1.0.x or the newer hashHistory).

@amielnbc
Copy link

amielnbc commented Oct 7, 2016

I had alot of trouble getting yair-pantaflix's solution working with react-router@2.8.1:

import { createHistory } from 'history';
import { Router, useRouterHistory } from 'react-router';

const browserHistory = useRouterHistory(createHistory)({
      basename: '/whatever'
});

Finally worked out that this solution will not work with the latest version of history, I needed to downgrade to history@2.1.2

@462960
Copy link

462960 commented Oct 26, 2016

To what extend

import { createHistory } from 'history';
import { Router, useRouterHistory } from 'react-router';

const browserHistory = useRouterHistory(createHistory)({
      basename: '/whatever'
});

is version sensitive?
I've tried to play with 'react-router' and 'history' versions with no success. I have react-router@3.0.0 and history@4.3.0 for now but console says:"Uncaught TypeError: createHistory is not a function". So, React components could not be rendered.
As far as I see, this particular history method comes from https://github.com/ReactTraining/react-router/blob/d4aa590dd13848a857ca27508149552458c7cdfb/examples/dynamic-segments/app.js But rendering this example makes console to say:"Uncaught TypeError: (0 , _history.useBasename) is not a function". Is there any other ways to have both history and custom URL?

@JohannesKlauss
Copy link

JohannesKlauss commented Nov 25, 2016

Same here. react-router@2.8.1 and history@4.2.0

If I try this it gives me the same error: "Uncaught TypeError: createHistory is not a function"

This is a very urgent problem!

@462960
Copy link

462960 commented Nov 25, 2016

Hi Johannes, this particular problem I solved:

import {browserHistory} from 'react-router';
<Router history={browserHistory}>
</Router>

but another issue had arisen. Having pretty URL is nice but each reload requires to begin with localhost:8080 which means manual labor instead of hot reload.
The same is going on within production version, loaded at the real server. In case user just reloads the page, he sees white space.

@JohannesKlauss
Copy link

How does this solve the problem? When I'm just importing browserHistory, how can I define a basename?

@462960
Copy link

462960 commented Dec 2, 2016

Sorry, without defining your own brand new basename but you got rid of random abracadabra.
Share the knowledge, please, as soon as you find out:
-- How to set a basename
-- How to reload a page without blank sheet

@beausmith
Copy link

beausmith commented Dec 7, 2016

Using "react-router": "^3.0.0" and "history": "^3.0.0", I was able to get my react app (initialized with create-react-app) to run on a Github Pages at: https://username.github.io/project-name.

This implementation is based upon the file withExampleBasename.js in React Router Examples:

  1. React-Router only supports History v3, so run…

     npm install --save history@3.0.0
    

    ...to get "history": "^3.0.0", in your package.json file dependences.

  2. Add the useBasename import to your component:

     import { useBasename } from 'history'
    

    And ensure that you're importing browserHistory from react-router:

     import { Router, Route, IndexRoute, browserHistory } from 'react-router'
    
  3. Update the Router history attribute to:

     <Router history={useBasename(() => browserHistory)({ basename: process.env.PUBLIC_URL })}>
    

    I'm publishing to Github Pages and process.env.PUBLIC_URL evaluates to my project name, eg. /project-name.

Please "@mention" me if you have any feedback on this implementation.

@Richacinas
Copy link

Hello @beausmith

Do you use the same router for your server side and client side rendering?

This is the scenario in which we have problems. When NodeJS is trying to use your "universal" router to do the server-side rendering, it is unable to use browserHistory, throwing an Invariant Violation...

@mmcgahan
Copy link
Contributor

@Richacinas It might vary a little bit depending on what version of react-router you're using, but if you have the match + <RouterContext> setup described here, you can wrap the renderProps.history and renderProps.router.history in a similar way to what @beausmith described:

// inside your `match` callback
const basename = YOUR_BASENAME;
renderProps.history = useBasename(() => renderProps.history)({ basename });
renderProps.router = { ...renderProps.router, ...renderProps.history };

<RouterContext { ...renderProps } />

@beausmith
Copy link

beausmith commented Dec 19, 2016

@Richacinas I haven't ventured into server-side rendering yet.

@unyo
Copy link

unyo commented Feb 16, 2017

To get basename working for react-router@3.0.2:

npm install --save react-router@3.0.2
npm install --save history@^3.0.0 (check react-router package.json to get the correct history version to use, current major version of history is 4.x and won't work with react-router@3.0.2)
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router'
import { createHistory } from 'history'

const history = useRouterHistory(createHistory)({
  basename: '/subdirectory-where-the-app-is-hosted-goes-here'
})

render((
  <Router history={history}>
    <Route path='/' component={Layout}>
      <IndexRoute component={HomeView} />
      <Route path='other-views' component={OtherViews} />
    </Route>
  </Router>
), document.getElementById('main'))

You'll also need to set up .htaccess if you're running Apache or nginx.conf if you're running nginx:
https://github.com/ReactTraining/react-router/blob/master/docs/guides/Histories.md

RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
server {
  ...
  location / {
    try_files $uri /index.html;
  }
}

@kimasendorf
Copy link

@unyo Since history is a dependency of react-router you don't need to install the package manually. It is already there and you can simply import it.

@jobsamuel
Copy link

@unyo, any advice to make that solution works in Firebase? It works great locally but not deployed in Firebase hosting 😢

@bentrility
Copy link

How does this work in react-router 4? Been digging through source code and haven't found useRouterHistory yet.

@pshrmn
Copy link
Contributor

pshrmn commented Apr 1, 2017

@bentrility https://reacttraining.com/react-router/web/api/BrowserRouter/basename-string

@bentrility
Copy link

bentrility commented Apr 1, 2017

Looks like this guy had it:

<BrowserRouter basename="/lessons" />

@bentrility
Copy link

@pshrmn Thank you

@bodrovphone
Copy link

Looks like nobody stops me from using something like this:
component={Content(this.props)}

Content should be a wrapper function that return another function with anything that should be rendered

@piotr-cz
Copy link

piotr-cz commented Jun 21, 2017

BTW: basename doesn't work if contains protocol and host, so if anyone would like to use <base href /> tag and document.baseURI, or PUBLIC_URL env variable you have to strip these::

construct(props) {
  super(props)

  this.basepath = (process.env.PUBLIC_URL === '.') ?
    undefined :
    process.env.PUBLIC_URL.substr(`${window.location.protocol}//${window.location.host}`.length)
}

render() {
  return (
    <Router basename={ this.basepath }>
      //...
    </Router>
  )
}

@kkorada
Copy link

kkorada commented Jun 22, 2017

React router should be automatically detecting the context root instead of hardcoding it in code. Any one can deploy that build with any context they like we cannot restrict them. In current scenario we need to update basename everytime we change context root and moreover setting context root is not part of developement it is part of deployement.

@yarnball
Copy link

yarnball commented Jun 23, 2017

If I"m using BrowserRouter I set it up like so- <BrowserRouter basename="/myFolder">

However,

this.basepath = (process.env.PUBLIC_URL === '.') ?
  undefined :
  process.env.PUBLIC_URL.substr(`${window.location.protocol}//${window.location.host}`.length)

Anything unsafe about- window.location.pathname.split('/')[1]?

Does not return anything. Any ideas on that?

@piotr-cz
Copy link

@yarnball that code doesn't work as it returns only first segment ([1]) of the pathname.
Moreover window.location.pathname contains last segment (such as index.html).

IMHO optimal way is to extract pathname from PUBLIC_URL variable. Unless it's a . - then it usually means that there's no need to configure router basename option.

@ritwickdey
Copy link

ritwickdey commented Feb 17, 2018

I think, the api is changing a lot in every version.
I'm using...

"history": "^4.7.2" "react-router": "^4.2.0", "react-router-dom": "^4.2.2"

And it works for me.

import { createBrowserHistory } from 'history';
import { Router } from 'react-router-dom';

export const history = createBrowserHistory({ basename });

export const AppRouter = () => (
  <Router history={history}>
	...
	...
  </Router>
)

The API gets more simplified.

@piotr-cz
Copy link

Now I'm using code similar to @ritwickdey's example.
Another benefit is that since history is created explicitly, it's possible to use it's methods directly.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 5, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests