This repo is an attempt to make the simplest server-side rendered (universal) async React Redux app.
Boilerplates can be a great for two things:
- Get started with your application code quickly since you don't have to scaffold your app.
- Learn how apps can be scaffolded, and learn how technologies can fit together.
This repository is more aimed at the second point.
It was born out of frustrations with complex boilerplates where you can't understand what is going on behind the scenes. Developers tend to want to know how things work under the hood. This repo offers a boiled-down example to be tweaked and hacked around with.
It tries to be as un-opinionated and simple as possible.
It borrows heavily from the documentation of Redux and React-Router.
yarn install
yarn run dev
Open localhost:3000
yarn run build
yarn run start
Open localhost:3000
This repo is developed and tested on Mac OS with node v10.10.0 and npm v6.7.0
This repo is tested on Windows. You might have to install nodemon globally though.
npm i -g nodemon
Everything starts with the Express App.
You can find this in src/server/index.js
Here we can see that all requests are routed to the handleRender
function:
app.use(handleRender);
The handleRender function does a number of things:
- Create a new redux store on every request from the client
- Match the request path (
req.path
) to the react router routes specified insrc/universal/routes
- Asynchronously fetch the data required to render this route (using the route's
loadData
function) - Use react-dom/server
renderToString
function to create the required html - Insert the html and redux preloadedState into a full html page using the
renderFullPage
function - Send the response to the client
res.send(
For the client side the index file is src/client/index.js
In this file, we use the redux preloadedState
provided by the server to initialise a client side redux store.
We then use the React hydrate
function to initialise React on the client side.
In the React components, any asynchronous data is fetched in componentDidMount
. If data already exists, the component will not make the fetch.
componentDidMount() {
// only fetch the data if there is no data
if (!this.props.data) this.props.getData();
}
In this way, components won't make requests for data if the data has already been requested server side.
The difference in the react tree between server side and client side is as follows:
Server src/server/handleRender.js
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<Router />
</StaticRouter>
</Provider>
Client src/client/index.js
<Provider store={store}>
<BrowserRouter>
<Router />
</BrowserRouter>
</Provider>
Everything else in the entire React tree is the same between server and client.
Any issues, reports, feedback or bugs or pull requests are more than welcome.
However it is worth mentioning that the purpose of this repo is to create the simplest, most up-to-date, most robust universal async react redux boilerplate.
Therefore any pull request should aim to simplify, fix or update the current solution, not add new packages or complexity.
MIT License
Copyright (c) 2019 William Woodhead
Good luck with it! Please star or follow on twitter: @williamwoodhead