How to stop /#/ in browser with react-router?
ReactjsReact RouterReactjs Problem Overview
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.
Reactjs Solutions
Solution 1 - Reactjs
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.
Versions 2 and 3
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
Version 4
For the upcoming version 4 of react-router, the syntax has changed a great deal 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);
Source React Router Version 4 Docs
Solution 2 - Reactjs
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.
Solution 3 - Reactjs
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
Solution 4 - Reactjs
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...
Solution 5 - Reactjs
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.
Solution 6 - Reactjs
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 :-)