React-router: Add <Routes baseHref>

Created on 5 Oct 2014  Â·  52Comments  Â·  Source: ReactTraining/react-router

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

Most helpful comment

@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.

All 52 comments

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.

@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?

@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>

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.

<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.

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

I'm running router with

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

so it's html5 mode

Is this now possible, or still considered unnecessary?

Am using [email protected], 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`);

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?

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 :smile_cat:

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'
})

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.

Sorry what version are we talking here @anjianshi ?

@jamiehill [email protected]

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")
);

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

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.

@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.

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

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

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.

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

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.

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.

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.

As I just said, try Stack Overflow or Reactiflux.

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).

Using [email protected], [email protected], I managed this:

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

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

Took me a while to find out that createBrowserHistory is actually exported as createHistory.
if you need the hashHistory, use import { createHashHistory } from 'history'; instead.

I'm getting this error when trying to use your solution:

Invariant Violation: Browser history needs a DOM

Here is the code of my universal router. I'm using react-router 2.3.0:

import React from 'react';
import { createHistory } from 'history';
import { Router, Route, IndexRoute, Redirect, useRouterHistory } from 'react-router';

import MainLayout from './../views/layout/mainLayout.jsx';
import Home from './../views/home.jsx';
import About from './../views/about.jsx';

import Error404 from './../views/404.jsx';

const browserHistory = useRouterHistory(createHistory)({
    basename: '/example-url'
});

module.exports = (
  <Router history={browserHistory}>
    <Route path='/' component={MainLayout}>
      <IndexRoute component={Home} />
      <Route path='about/' component={About} />
      <Route path='*' component={Error404} />
    </Route>
  </Router>

Could you please help? thanks a lot

I had alot of trouble getting yair-pantaflix's solution working with [email protected]:

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 [email protected]

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 [email protected] and [email protected] 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?

Same here. [email protected] and [email protected]

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

This is a very urgent problem!

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.

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

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

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 [email protected]
    

    ...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.

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...

@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:

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


````

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

To get basename working for [email protected]:

npm install --save [email protected]
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 [email protected])
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;
  }
}

@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.

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

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

Looks like this guy had it:

<BrowserRouter basename="/lessons" />

@pshrmn Thank you

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

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>
  )
}

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.

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?

@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.

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.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

stnwk picture stnwk  Â·  3Comments

Radivarig picture Radivarig  Â·  3Comments

Waquo picture Waquo  Â·  3Comments

ackvf picture ackvf  Â·  3Comments

misterwilliam picture misterwilliam  Â·  3Comments