Create React Server
Config-free server side rendering for React applications based on React Router. Compatible with Redux apps and Webpack Dev Middleware for simple and painless development and production usage. Comes as CLI script, standalone server, and middleware.
This package is formerly known as react-router-redux-middleware
(which is now deprecated).
- Installation
- Examples
- Async Routes
- Preconditions
- CLI mode — simple integration with Create React App (aka React Scripts)
- Config
- Template Function
- Custom server
- Use with Webpack Dev Middleware useful for
- Use with React Helmet
- Asynchronous require
- Handling props updates
Installation
npm install create-react-server babel-preset-react-app babel-preset-es2015 --save-dev
You don't have to install babel-preset-react-app
if you use Create React App, it will be pre-installed already.
Examples
- With Create React App: examples/create-react-app
- With Webpack Blocks (custom webpack config, server, dev middleware): examples/webpack-blocks
In order to use examples you should clone the repository and do npm install
inside the repo and then separate
npm install
in example dir.
git clone https://github.com/kirill-konshin/create-react-server.gitcd create-react-servernpm installcd examples/[any-example]npm installnpm run redeploy # launch in production mode
Async Routes
!!!ATTENTION!!! Due to changes in React Router 4 async routes are no longer supported by this package!
Official RR4 documentation says the following:
- You need synchronous module resolution on the server so you can get those bundles in the initial render.
- You need to load all the bundles in the client that were involved in the server render before rendering so that the client render is the same as the server render. (The trickiest part, I think its possible but this is where I gave up.)
- You need asynchronous resolution for the rest of the client app’s life.
Code Splitting & Server Rendering
So not at this moment at least, stay tuned, we will try to add this in future releases! Especially if React Fiber (16) will take care of some async component lifecycle.
Preconditions
.babelrc
file or babel
section of package.json
Add
Page (e.g. leaf router node)
Server rendering procedure takes getInitialProps
static property and performs everything defined in it both on server
and later on client (if needed). Anything that was returned from getInitialProps
becomes the initial set of props
when component will be rendered on server.
On client these props will be available with some delay, so you may use custom property initialLoading
which will be
true
while getInitialProps
function is resolving.
If an error occurs inside getInitialProps
it will be available via initialError
property. This property is populated
from the server as well (as a string, no trace, you may capture this also in template
function).
Component also receives a wrapped version of getInitialProps
in its props
, so that it can be called when needed,
for example when componentWillReceiveProps
on React Router route change to load new data, but be careful and don't
cause infinite loops or race conditions.
If you use withWrapper
you must wrap each leaf page, otherwise if you open unwrapped page in browser and then
navigate to wrapped no getInitialProps
will be called because wrapped will assume that it's first run.
// src/Page.js ;; // this is optional; static async { await store; // this is optional return custom: 'custom'; }; { const foo bar custom initialError = thisprops; if initialError return <pre>Initial Error: initialErrorstack</pre>; return <div>Foo foo Bar bar Custom custom</div> ; } App = App; // this is optionalApp; // here we connect to WrapperProvider
Component which will be used as 404 stub should have notFound
static property:
// src/NotFound.js ; static notFound = true; { return <div>404 Not Found</div> ; }
Main App
You have to make a createApp
function that should return an app with React Router routes.
// src/app.js ;;;;; { if !state && !!req // this means function is called on server state = 'foo': requrl + ':' + Date ; return <Provider store=> <WrapperProvider initialProps=props> <Switch> <Route exact path="/" component=IndexPage/> <Route path="/page" component=Page/> <Route component=NotFound/> </Switch> </WrapperProvider> </Provider> ; };
If you don't use Redux then Provider
is not needed.
Parameters that function receives are (all parameters may be null
depending on where the function is launched, you may
have custom logic specifically for server or client based on what paratements are available):
state
— initial Redux state received from server (if any)props
— initial props received from server (if any)req
— NodeJS Request (if any)res
— NodeJS Response (if any)
Redux (optional)
If your app is using Redux, then you will have to make a createStore
function, that should take initial state as an
argument and return a new Store
:
// src/store.js ; { return state; } { if req // it means it's launched from server in CLI mode initialState = foo: resurl; // so we can pre-populate something return ;}
Main App Entry Point
You have to create a root app component, which normally consists only of BrowserRouter
or HashRouter
and a call to
createApp
.
// src/index.js ;;;; const Root = <BrowserRouter> </BrowserRouter>; ;
CLI Mode
First of all prepare your application according to steps in preconditions.
It is convenient to put console command into scripts
section of package.json
:
All specified JS files must export functions as default export
or as module.exports
. It assumes that
--app path-to-app.js
as path to file which exports a createApp
and so on.
Available options:
--app
or-r
path to JS file withcreateApp
function--template
or-t
path to JS file withtemplate
function--outputPath
or-o
as path to yourbuild
(e.g. your static files)--templatePath
or-i
path to yourindex.html
--debug
or-d
if you want to get more information of requests handling & stack traces of errors--port
or-p
to bind to something other than3000
, port will also be automatically taken fromprocess.env.PORT
You may also run with NODE_ENV=development
to get more info:
NODE_ENV=development create-react-server [your options]
Then run from console:
npm run buildnpm run server
Now if you open http://localhost:3000
you will see a page rendered on the server.
Config
Middleware accepts following options:
options.outputPath
required path with static files, usually equals to Webpack'soutput.path
options.app({props, state, req, res})
required function must return an app that uses React Routeroptions.template
optional, main template function, performs injection of rendered HTML to the template, default = replaces<div id="root"></div>
with<div id="root">%HTML%</div>
completely failed to renderoptions.templatePath
optional path toindex.html
, default =%outputPath%/index.html
options.debug
optional emits to console some extra information about request handling, default =false
options.initialStateKey
optional key inwindow
object that will be used to pass initial props, default =__INITIAL_PROPS__
options.initialPropsKey
optional key inwindow
object that will be used to pass initial state, default =__INITIAL_STATE__
Server accepts following options in addition to the ones accepted by middleware:
options.skipExtensions
optional array of strings that represent most commonly imported non-JS extensions that has to be skipped during server build, default =['css', 'jpg', 'gif', ...]
options.port
optional port to listen, default =3000
options.listen
optional Express's listen function
Template Function
Template function performs injection of rendered HTML to the template. This function will also be called if React App failed to render (e.g. in case of server error).
Function accepts config
as parameter with the following always available properties:
config.error
error object if function is called as error handler, equalsnull
otherwiseconfig.req
instance of NodeJS Requestconfig.res
instance of NodeJS Responseconfig.template
contents ofindex.html
or any other file defined intemplatePath
option
If function is called in a normal way, e.g. NOT as error handler, the following properties will also be provided, some of them still may be available in error handler mode, depending on which stage the error has happened:
config.component
matched component (your leaf page)config.html
result of React renderingconfig.initialProps
result ofgetInitialProps
(resolved result of Promise if it was returned)config.store
instance of Redux Store
Most common cases when this function is called as error handler are:
- You forgot to configure React Router's fallback route, e.g.
<Route path="*" component={...}/>
- React Router has returned an error
If you don't output any error information then the client will be rendered as if nothing happened.
By default this function replaces <div id="root"></div>
(exactly as written). If there is an error — it's HTML is
injected right before div#root
.
If anything will be thrown from this function, then default error handler will take over. You should avoid this by
placing try {} catch {}
around your code, in this case default handler wiil not be called.
Custom server
First of all prepare your application according to steps in preconditions.
In these example we will use express
server and babel-cli
:
npm install express babel-cli --save-dev
Modify scripts
section of package.json
:
It makes client side build using Create React App (React Scripts) and server side build using Babel, which takes
everything from src
and puts the outcome to build-lib
. You may add this directory to .gitignore
.
// src/server.js ;;; ;
Check out the ready-to-use example in examples/create-react-app folder.
In this mode your createStore
function will on server will receive second config argument: {req, res}
with request
and response respectively. In other modes you can control what is passed where.
Middleware
There are two middleware modes: for Webpack Dev Server and for Express server.
If you have access to webpack.config.js
then you may run the Webpack Dev Server along with server side rendering,
this example covers both.
In order to do that we need to install webpack-dev-server
(in addition to packages from
preconditions step), you may skip this if you have already installed it. In these example we will use
express
server and babel-cli
to make server side builds:
npm install express babel-cli babel-preset-react-app webpack-dev-server html-webpack-plugin --save-dev
*Note: you don't have to install babel-preset-react-app
if you use Create React App or you already have preset.
scripts
section of package.json
Modify In this example we run server by Babel Node, in this case server will be transformed in runtime (which is not recommended for production). You also can build the server like in custom server section.
Webpack Config
Main entry file index.html
should be a part of webpack build, e.g. emitted to you output path. It could be a
real file or generated by HtmlWebpackPlugin
, but it has to be known by Webpack.
Make sure your webpack.config.js
has all the following:
// webpack.config.js var HtmlWebpackPlugin = ;moduleexports = //... "output": path: process + '/build' // mandatory publicPath: '/' "plugins": filename: 'index.html' favicon: './src/favicon.ico' // this is optional template: './src/index.html' devServer: port: processenvPORT || 3000 contentBase: './src' //...
Server
// src/server.js ;;;;; // same file as in client side;; ; // this may be omitted but then you need to manually teach Node to ignore non-js files const port = processenvPORT || 3000; const options = app: app template templatePath: path outputPath: configoutputpath; if processenvNODE_ENV !== 'production' const compiler = ; configdevServer { app; }; compiler configdevServer; else const app = ; app; app; app; { if err throw err; console;}
Check out the ready-to-use example in examples/webpack-blocks folder.
Use with React Helmet
Take a look at React Helmet's readme note about server side rendering.
In a few words you have to add renderStatic()
call to your implementation of template
option:
; const template = { const head = Helmet; return template ; };
Asynchronous Require
If you use require.ensure
in your app, you will have to install babel-plugin-transform-ensure-ignore
.
npm install babel-plugin-transform-ensure-ignore --save-dev
And add it to .babelrc
file or babel
section of package.json
:
If you use dynamic import()
function, then you will need more plugins babel-plugin-dynamic-import-webpack
, it should
be used together with babel-plugin-transform-ensure-ignore
. Make sure it is used only on server, and Webpack (client
build) will not pick it up. On client plugin babel-plugin-syntax-dynamic-import
should be used.
Handling props updates
Your component may receive props from React Router without unmounting/mounting, for example query
or param
has
changed.
In this case you can create a componentWillReceiveProps
lifecycle hook and call this.props.getInitialProps()
from
it to force static getInitialProps
method to be called again:
Component static async { var res = await ; return await res; } { if thispropsparamsslug !== newPropsparamsslug thisprops; } { // your stuff here } Page;