React | Simple implementing SSR(Server-Side Rendering) in React with Redux
Hello! You guys! I am Clark today we are keep going to learn how to implementing SSR in React applications with Redux!
Last article, We are already finish implement SSR in a base React application! Here is the React application Repo!But the Repo is for last article, This article I have prepare a another Repo, it is React applications with Redux, but it is very similar to last Repo, only have a different:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchRequestQuery } from '../actions';
const Content = () => {
const dispatch = useDispatch();
useEffect(() => {
fetchRequestQuery(dispatch);
}, []);
const { request } = useSelector(state => state);
return (
<span>{JSON.stringify(request)}</span>
);
};
export default Content;
In the Content
, I through API get data and use Redux store it.
Review
Okay, first we review what is we should prepare file for SSR:
1. We need a hepler.js help us display first HTML in client:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { renderRoutes } from 'react-router-config';
import Routes from '../Routes';
export default (req) => {
const content = renderToString(
<StaticRouter location={req.path}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>,
);
return `
<html>
<body>
<div id="root">${content}</div>
<script src="./bundle.js"></script>
</body>
</html>
`;
};
2. Install express and create a file srever.js, to handle first response:
import express from 'express';
const app = express();
const port = process.env.PORT || 3001;
app.use(express.static('dist'));
app.get('*', (req, res) => {
const content = renderer(req);
res.send(content);
});
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
So, if you guys already understand code above, we can continue handle component Content
! if you have any question, you can reference last article or comments below:)
We can through server.js send response correct, if we can handle Content
’s fetch correct, so first we need would export method of fetch, if it is need for render component:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchRequestQuery } from '../actions';
const Content = () => {
const dispatch = useDispatch();
useEffect(() => {
fetchRequestQuery(dispatch);
}, []);
const { request } = useSelector(state => state);
return (
<span>{JSON.stringify(request)}</span>
);
};
export default {
component: Content,
loadData: dispatch => (
fetchRequestQuery(dispatch)
),
};
Now export from Content
is not a component, it is a object and include the component and API method, so we don’t forget modify src/Routes.js
:
import Content from './pages/Content';
import NotFound from './pages/NotFound';
import App from './App';
export default [{
...App,
routes: [
{
...Content,
path: '/',
exact: true,
}, {
component: NotFound,
},
],
}];
We are almost finish, next we start to handle Redux, first of all, the store
in client we only can use one, but if we use one store handle request from all client, then data maybe will influence each other request, so we must modify ./src/store/index.js
:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers';
export default () => createStore(reducers, applyMiddleware(thunk));
Look nice, So each new request, we can create a new store, data will no influence each other request.
Next we go to ./src/server.js
, we need use new createStore
get store
, and handle fetch if component need it.
import express from 'express';
import { matchRoutes } from 'react-router-config';
import Routes from './Routes';
import renderer from './helpers/renderer';
import createStore from './store';
const app = express();
const port = process.env.PORT || 3001;
app.use(express.static('dist'));
app.get('*', (req, res) => {
// (1)
const store = createStore();
const { dispatch } = store;
// (2)
const routes = matchRoutes(Routes, req.path);
// (3)
const promises = routes.map(
({ route }) => (route.loadData ? route.loadData(dispatch) : null),
);
// (4)
Promise.all(promises).then(() => {
const content = renderer(req, store);
res.send(content);
});
});
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
I will explain in four part below code:
- Use
createStore
getstore
and getdispatch
fromstore
. - Use
matchRoutes
(It is method in react-router-config) get correspond information of render components fromRoutes
, andRoutes
is an Array, data like:[{ route: { /* component information */ }, }, { route: { /* component information */ }, }]
. - Use
map
check if component need API fetch data(Judge by loadData), if component need fetch data, then add topromises
. - Considering that the API needs to be in order, so we use
promise.all
ensure API to be in order! when all promise responses is finish, we can callrenderer
to get client’s HTML.
We are almost finish, we can open ./src/helpers/renderer.js
to receive store
add Provider
to get data from store
:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { renderRoutes } from 'react-router-config';
import { Provider } from 'react-redux';
import Routes from '../Routes';
export default (req, store) => {
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>
</Provider>,
);
return `
<html>
<body>
<div id="root">${content}</div>
<script src="./bundle.js"></script>
</body>
</html>
`;
};
Perfect! Finally we can type npm run build
, and npm run server
to running server:
You can find this Repo in my Github!
Thank for reading my post, if you have any question and think, please let me know, in comments below :)