From the comments on the article, it became clear that many people are leaning towards the ecosystem of the Create React App (aka React Scripts). This is quite reasonable, because it is the most popular and easy-to-use product (due to the lack of configuration and support of the leading people of the React-community), in which, moreover, there is almost everything you need - build, development mode, tests, coverage statistics. All that is missing is server rendering.
As one of the methods in the official documentation it is proposed either to drive the initial data into the template or to use static casts . The first approach does not allow search engines to properly index static HTML, and the second does not support forwarding of any initial data other than HTML ( phrase from the documentation :). Therefore, if Redux is used, you will have to use something else for rendering.
I adapted the example from the article for use with the Create React App, now it is called Create React Server and is able to start server rendering with the command:
create-react-server --createRoutes src/routes.js --createStore src/store.js
With this launch, no special configuration is required, everything is done via command line parameters. If necessary, you can also slip your templates and handlers.
A small lyrical digression. As the authors say React Router - their sites are indexed by Google without any problems and without any server rendering. Maybe this is so. But one of the main problems is not only Google, but also fast content delivery to the user, and this may even be more important than indexing, which can be deceived.
First, install the required packages for this example:
npm install create-react-server --save-dev
.babelrc
file or the babel
section to the package.json
file { "presets": [ "react-app" ] }
The preset babel-preset-react-app
is put together with react-scripts
, but for server rendering we need to explicitly refer to it.
As before, the essence of server rendering is quite simple: on the server we need to determine, based on the rules of the router, which component will be shown on the page, find out what data it needs to work, request this data, render HTML, and send this HTML along with the data on the client.
The server takes the final component, calls getInitialProps
from it, inside which you can make a Redux action dispatch and return the initial set of props
(in case Redux is not used). The method is invoked both on the client and on the server, which makes it possible to greatly simplify the initial loading of data.
// src/Page.js import React, {Component} from "react"; import {connect} from "react-redux"; import {withWrapper} from "create-react-server/wrapper"; import {withRouter} from "react-router"; export class App extends Component { static async getInitialProps({location, query, params, store}) { await store.dispatch(barAction()); return {custom: 'custom'}; // props }; render() { const {foo, bar, custom, initialError} = this.props; if (initialError) return (<pre> getInitialProps: {initialError.stack}</pre>); return ( <div>Foo {foo}, Bar {bar}, Custom {custom}</div> ); } } // Redux Provider App = connect(state => ({foo: state.foo, bar: state.bar})(App); // WrapperProvider, initialProps App = withWrapper(App); // React Router App = withRouter(App); export default App;
The initialError
variable will have a value if an error occurred in the getInitialProps
function, and no matter where - on the client or on the server, the behavior is the same.
The page that will be used as a stub for 404 errors should have the static property notFound
:
// src/NotFound.js import React, {Component} from "react"; import {withWrapper} from "create-react-server/wrapper"; class NotFound extends Component { static notFound = true; render() { return ( <div>404 Not Found</div> ); } } export default withWrapper(NotFound);
The createRoutes
function should return the rules of the router, asynchronous routes are also supported, but for simplicity we omit this for now:
// src/routes.js import React from "react"; import {IndexRoute, Route} from "react-router"; import NotFound from './NotFound'; import App from './Page'; export default function(history) { return <Route path="/"> <IndexRoute component={App}/> <Route path='*' component={NotFound}/> </Router>; }
The createStore
function should take the initial state as a parameter and return the new Store
:
// src/store.js import {createStore} from "redux"; function reducer(state, action) { return state; } export default function (initialState, {req, res}) { if (req) initialState = {foo: req.url}; return createStore( reducer, initialState ); }
When the function is called on the server, the second parameter will have Request and Response objects from NodeJS, you can pull out some information and attach it to the initial state.
Putting it all together, and add a special wrapper to get the initialProps
from the server:
// src/index.js import React from "react"; import {render} from "react-dom"; import {Provider} from "react-redux"; import {browserHistory, match, Router} from "react-router"; import {WrapperProvider} from "react-router-redux-middleware/wrapper"; import createRoutes from "./routes"; import createStore from "./store"; const Root = () => ( <Provider store={createStore(window.__INITIAL_STATE__)}> <WrapperProvider initialProps={window.__INITIAL__PROPS__}> <Router history={browserHistory}>{createRoutes()}</Router> </WrapperProvider> </Provider> ); render((<Root/>), document.getElementById('root'));
Add scripts to the scripts
section of the package.json
file:
{ "build": "react-scripts build", "server": "create-react-server --createRoutes src/routes.js --createStore src/store.js }
And run
npm run build npm run server
Now if we open http://localhost:3000
in the browser - we will see a page prepared on the server.
In this mode, the result of the server build is not stored anywhere and is calculated on the fly each time.
If there are few command line capabilities, or you need to store server build results, you can always create a server not through the CLI, but through the API.
Install in addition to the previous packages of babel-cli
, it will be needed to build the server:
npm install babel-cli --save-dev
Add scripts to the scripts
section of the package.json
file:
{ "build": "react-scripts build && npm run build-server", "build-server": "NODE_ENV=production babel --source-maps --out-dir build-lib src", "server": "node ./build-lib/server.js" }
Thus, the client part will continue to create the Create React App (React Scripts), and the server part will be assembled with the help of Babel, who will take all the src
and put it in build-lib
.
// src/server.js import path from "path"; import express from "express"; import {createExpressServer} from "create-react-server"; import createRoutes from "./createRoutes"; import createStore from "./createStore"; createExpressServer({ createRoutes: () => (createRoutes()), createStore: ({req, res}) => (createStore({})), outputPath: path.join(process.cwd(), 'build'), port: process.env.PORT || 3000 }));
Run:
npm run build npm run server
Now if we open http://localhost:3000
again in the browser, we will again see the same page prepared on the server.
The full code of the example can be found here: https://github.com/kirill-konshin/react-router-redux-middleware/tree/master/examples/create-react-app .
Source: https://habr.com/ru/post/323846/
All Articles