Any way to prevent /#/
from showing in the browser's address bar when using react-router? That's with ReactJS. i.e. Clicking on links to go to a new route shows localhost:3000/#/
or
localhost:3000/#/about
. Depending on the route.
-
1It's due to using `HashHistory` i.s.o. `BrowserHistory`. See also [this SO question](http://stackoverflow.com/questions/27928372/react-router-urls-dont-work-when-refreshing-or-writting-manually) where I give a lot of background info on this subject. – Stijn de Witt Jul 10 '16 at 19:56
6 Answers
The answer to this question has changed dramatically over the years as React Router has been refactored again and again. Here is a breakdown of how to solve the issue with each version.
Version 6
The idea is to set the router to be a "browser router", which is created using the createBrowserRouter()
function. This router is then added to the root element of the React app.
import React from "react";
import ReactDOM from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
Route,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: ...,
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Source react-router Version 6 Docs: createBrowserRouter
Version 4
For version 4 of react-router, the syntax is very different and it is required is to use BrowserRouter
as the router root tag.
import BrowserRouter from 'react-router/BrowserRouter'
ReactDOM.render ((
<BrowserRouter>
...
<BrowserRouter>
), document.body);
Note that this will work in version 6, but it's not recommended and the BrowserRouter
component doesn't support the new React Router data APIs.
Source React Router Version 4 Docs
Versions 2 and 3
For the versions 1, 2 and 3 of React Router, the correct way to set the route to URL mapping scheme is by passing a history implementation into the history
parameter of <Router>
. From the histories documentation:
In a nutshell, a history knows how to listen to the browser's address bar for changes and parses the URL into a location object that the router can use to match routes and render the correct set of components.
In react-router 2 and 3, your route configuration code will look something like this:
import { browserHistory } from 'react-router'
ReactDOM.render ((
<Router history={browserHistory} >
...
</Router>
), document.body);
Version 1
In version 1.x, you will instead use the following:
import createBrowserHistory from 'history/lib/createBrowserHistory'
ReactDOM.render ((
<Router history={createBrowserHistory()} >
...
</Router>
), document.body);
Source: Version 2.0 Upgrade Guide

- 1,056
- 10
- 13
-
6Note that `history` is a [stand-alone package](https://github.com/rackt/history) you'll need to install. – Jan Klimo Oct 24 '15 at 15:01
-
4They changed the `browserHistory` in v2.x : `import { browserHistory } from 'react-router'
` Check [react-router upgrade guide](https://github.com/rackt/react-router/blob/latest/upgrade-guides/v2.0.0.md#using-browser-html5-pushstate-history) – pistou Jan 06 '16 at 09:19 -
-
1For `hashHistory`, is there a way to get rid of this query param at the end? `http://localhost:8080/#/dashboard?_k=yqwtyu` – Con Antonakos Mar 17 '16 at 02:40
-
2@Matt It does work, but requires support on the server. That's because when you refresh, you hit the server with an URL with path. – Stijn de Witt Jul 10 '16 at 19:57
Router.run(routes, Router.HistoryLocation, function (Handler) {
React.render(<Handler/>, document.body);
});
For the current version 0.11 and forward, you need to add Router.HistoryLocation
to Router.run()
. <Routes>
are now deprecated. See the Upgrade Guide for the 0.12.x HistoryLocation implementation.

- 1,350
- 15
- 16
-
1this completely ruined my app. looks like their current implementation is buggy? – ninjaneer Apr 01 '15 at 23:02
-
2@Ninja perhaps post a new question with exact version numbers for react and react-router, failing code and errors received. – pxwise Apr 06 '15 at 22:06
-
@Chet Looks like react-router docs have shuffled. Updated link to the only reference for HistoryLocation found in the Upgrade Guide. – pxwise Sep 30 '15 at 00:42
If you don't need to support IE8, you can use Browser History and react-router will use window.pushState
instead of setting the hash.
How exactly to do this depends on the version of React Router that you are using:
- v4: https://reacttraining.com/react-router/web/api/BrowserRouter
- v3: https://github.com/ReactTraining/react-router/blob/v3/docs/guides/Histories.md
- v2: https://github.com/ReactTraining/react-router/blob/v2.0.0/docs/guides/Histories.md
- v1: https://github.com/ReactTraining/react-router/blob/1.0.x/docs/guides/basics/Histories.md

- 43
- 8

- 139,698
- 36
- 220
- 238
-
1I added `
` it all works OK, until you refresh the browser when on route i.e. `localhost:3000/about` then I get a 404 error. Is that expected, I'm using `python -m SimpleHTTPServer 3000`? – Giant Elk Aug 02 '14 at 23:32 -
5You need to make sure your serverside can handle the push state url. In this instance it probably means you just need to make sure whatever is serving your app always sends every url it gets to the same root. So that `/about` actually loads your root page `/`. Otherwise your server is trying to look for a route that matches `/about` and finds nothing (404). I don't personally use python but you usually find either a manual route for `/*` or `/.*` -> `/` works - or it might be something called `html5Mode` urls in your server settings. – Mike Driver Aug 04 '14 at 12:53
-
Right — as Mike says, you just need to make sure your server can handle those pages. I would be wary of allowing literally any URL; it can be desirable to issue real 404s. The ideal is to validate the URL server-side and send a rendered page down, then launch a React app, seamlessly. People are calling apps like this *isomorphic web apps* and they seem to be the future. – Alan H. Nov 26 '14 at 06:26
-
3IE9 doesn't support pushState either -- so that is really "If you don't need to support IE9" right? I wish I was wrong. – Cymen Jul 01 '15 at 06:15
-
1
-
@GiantElk Right if you use History, you need to handle URLs server-side as well as client-side. See also this question: http://stackoverflow.com/questions/27928372/react-router-urls-dont-work-when-refreshing-or-writting-manually – Stijn de Witt Oct 04 '16 at 07:23
-
I think https://github.com/ReactTraining/react-router/blob/master/docs/guides/Histories.md is what was linked to in the answer. – Dagligleder Jan 26 '17 at 12:42
You can actually use .htaccess to accomplish this. The browser normally needs the query string delimiter ?
or #
to determine where the query string begins and the directory paths end.
The end result we want is www.mysite.com/dir
So we need to catch the issue before the web server searches for the directory it thinks we asked for /dir
.
So we place a .htaccess
file in the root of the project.
# Setting up apache options
AddDefaultCharset utf-8
Options +FollowSymlinks -MultiViews -Indexes
RewriteEngine on
# Setting up apache options (Godaddy specific)
#DirectoryIndex index.php
#RewriteBase /
# Defining the rewrite rules
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^.*$ ./index.html
Then you obtain the query parameters with window.location.pathname
You can then avoid using react routes if you want and just manipulate the url and browser history if you want as well. Hope this helps someone...

- 569
- 4
- 8
Install the history package
npm install history --save
Next import createHistory and useBasename from the history
import { createHistory, useBasename } from 'history';
...
const history = useBasename(createHistory)({
basename: '/root'
});
if your app url is www.example.com/myApp, then /root should be /myApp.
pass the history variable to the Router
render((
<Router history={history}>
...
</Router>
), document.getElementById('example'));
Now for all your Link tags append a "/" in front of all the paths.
<Link to="/somewhere">somewhere</Link>
The inspiration of the solution came from React-Router Example Which unfortunately, was not properly documented in their API.

- 2,355
- 2
- 26
- 42
-
does this require a node server? I'm trying to achieve the same URL style but only through the client side. Is it possible? – Sebastialonso Apr 07 '16 at 17:11
-
1nope, you do not need a node server. In fact I am running on django backend. But you probably need node for tools. – Mox Apr 08 '16 at 05:13
-
1Ok, I tried this approach. When I hit F5, all I get is "Not found". Is it possible for this history to deal with that? – Sebastialonso Apr 29 '16 at 22:29
-
if u are getting not found, that is returned by the server. this means the url pattern is not part of react router. – Mox Apr 30 '16 at 01:37
-
1Yes, after reading a bit more, everything became clear. I ended it up going with hashHistory without the keys. – Sebastialonso Apr 30 '16 at 01:53
Another way to handle what to display after the hash (so if you don't use pushState !) is to create your CustomLocation and load it at ReactRouter creation.
For exemple, if you want to have hashbang url (so with #!) to comply with google specs for crawling, your can create a HashbangLocation.js file which mainly copy the original HashLocation such as :
'use strict';
var LocationActions = require('../../node_modules/react-router/lib/actions/LocationActions');
var History = require('../../node_modules/react-router/lib/History');
var _listeners = [];
var _isListening = false;
var _actionType;
function notifyChange(type) {
if (type === LocationActions.PUSH) History.length += 1;
var change = {
path: HashbangLocation.getCurrentPath(),
type: type
};
_listeners.forEach(function (listener) {
listener.call(HashbangLocation, change);
});
}
function slashToHashbang(path) {
return "!" + path.replace(/^\//, '');
}
function ensureSlash() {
var path = HashbangLocation.getCurrentPath();
if (path.charAt(0) === '/') {
return true;
}HashbangLocation.replace('/' + path);
return false;
}
function onHashChange() {
if (ensureSlash()) {
// If we don't have an _actionType then all we know is the hash
// changed. It was probably caused by the user clicking the Back
// button, but may have also been the Forward button or manual
// manipulation. So just guess 'pop'.
var curActionType = _actionType;
_actionType = null;
notifyChange(curActionType || LocationActions.POP);
}
}
/**
* A Location that uses `window.location.hash`.
*/
var HashbangLocation = {
addChangeListener: function addChangeListener(listener) {
_listeners.push(listener);
// Do this BEFORE listening for hashchange.
ensureSlash();
if (!_isListening) {
if (window.addEventListener) {
window.addEventListener('hashchange', onHashChange, false);
} else {
window.attachEvent('onhashchange', onHashChange);
}
_isListening = true;
}
},
removeChangeListener: function removeChangeListener(listener) {
_listeners = _listeners.filter(function (l) {
return l !== listener;
});
if (_listeners.length === 0) {
if (window.removeEventListener) {
window.removeEventListener('hashchange', onHashChange, false);
} else {
window.removeEvent('onhashchange', onHashChange);
}
_isListening = false;
}
},
push: function push(path) {
_actionType = LocationActions.PUSH;
window.location.hash = slashToHashbang(path);
},
replace: function replace(path) {
_actionType = LocationActions.REPLACE;
window.location.replace(window.location.pathname + window.location.search + '#' + slashToHashbang(path));
},
pop: function pop() {
_actionType = LocationActions.POP;
History.back();
},
getCurrentPath: function getCurrentPath() {
return decodeURI(
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
"/" + (window.location.href.split('#!')[1] || ''));
},
toString: function toString() {
return '<HashbangLocation>';
}
};
module.exports = HashbangLocation;
Note the slashToHashbang function.
Then you juste have to do
ReactRouter.create({location: HashbangLocation})
And that's it :-)

- 264
- 3
- 10