When you are developing your Single Page Application there is a moment when you need design the url structure too.

Usually developers use 2 types of url…

N ormal Url

This is the url that we are used to seeing, with normal path, like this:

https://myspa.com/app/login

https://myspa.com/app/dashboard

This kind of url has the advantage of SEO, but you have to create much back-end endpoint as many as application routes in order to avoid the 404 error code message while reloading the url or pass the url to other people.

Hash Based Url

This url has a unique application endpoint /app/ controlled by one single back-end action and multiple hashtags managed by Javascript that define the SPA routes.

https://myspa.com/app/#/login

https://myspa.com/app/#/dashboard

In this case we just need to create one single controller for the app. Doing that the front-end team will only use the back-end API, separating the front-end and back-end logic and keeping clear the whole structure! To the other hand the SEO is more difficult to manage (but this is relative when we are talking about SPA).

Implement Hash Based Url in React

In order to implement the hash based url in our SPA with React I choose to use Connected React Router library.

This library can:

Synchronize router state with redux store through uni-directional flow

Support both React Router v4 and v5

Support functional component hot reloading while preserving state

Dispatching of history methods ( push , replace , go , goBack , goForward ) works for both redux-thunk and redux-saga

, , , , ) works for both redux-thunk and redux-saga And much more!

So first of all we need to install it

yarn add connected-react-router

After that we need to create a reducer with the specific key router

// reducers.js

import { combineReducers } from 'redux'

import { connectRouter } from 'connected-react-router'



export default (history) => combineReducers({

router: connectRouter(history),

... // rest of your reducers

})

Now we add the related middleware so we can dispatch history actions

// configureStore.js

...

import { createHashHistory } from 'history'

import { applyMiddleware, compose, createStore } from 'redux'

import { routerMiddleware } from 'connected-react-router'

import createRootReducer from './reducers'

...

export const history = createHashHistory({

hashType: 'slash',

getUserConfirmation: (message, callback) => callback(window.confirm(message))

});



export default function configureStore(preloadedState) {

const store = createStore(

createRootReducer(history), // root reducer with router state

preloadedState,

compose(

applyMiddleware(

routerMiddleware(history), // for dispatching history actions

// ... other middlewares ...

),

),

)



return store

}

history package provides 3 different methods for creating a history object, depending on your environment.

createBrowserHistory is for use in modern web browsers that support the HTML5 history API (see cross-browser compatibility)

is for use in modern web browsers that support the HTML5 history API (see cross-browser compatibility) createMemoryHistory is used as a reference implementation and may also be used in non-DOM environments, like React Native or tests

is used as a reference implementation and may also be used in non-DOM environments, like React Native or tests createHashHistory is for use in legacy web browsers

We are using createHashHistory instead of createBrowserHistory in order to use hash based urls.

Finally retrieve the ConnectedRouter and pass it the created history.

// index.js

...

import { Provider } from 'react-redux'

import { Route, Switch } from 'react-router' // react-router v4/v5

import { ConnectedRouter } from 'connected-react-router'

import configureStore, { history } from './configureStore'

...

const store = configureStore(/* provide initial state if any */)



ReactDOM.render(

<Provider store={store}>

<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }

<> { /* your usual react-router v4/v5 routing */ }

<Switch>

<Route exact path="/" render={() => (<div>Match</div>)} />

<Route render={() => (<div>Miss</div>)} />

</Switch>

</>

</ConnectedRouter>

</Provider>,

document.getElementById('react-root')

)

Enjoy! 😉