✅ Updated for Create React App v3.4.1
This repository was bootstrapped with Create React App, ejected, then configured to perform Server Side Rendering (SSR).
If you’re interested in SSR then you’d be aware that Create React App has no plans to include support for it:
Ultimately server side rendering is very hard to add in a meaningful way without also taking opinionated decisions. We don’t intend to make such decisions at this time. — Dan Abramov
This repository, then, is my minimally-opinionated SSR configuration, sticking closely to Create React App’s style and introducing as few new things (packages, code, configuration) as possible.
- Minimal intervention: Minimise changes to Create React App’s configuration, so that reviewing the diff can clearly illustrate the requirements of SSR;
- Developer experience: Retain the cohesive curated experience provided by Create React App - in particular, you shouldn’t need to do a production build to test your SSR code path.
- Follow the Create React App instructions to create a new project;
- Eject;
- View the diff and apply the same changes to your project, by hand or as a patch;
- Run
yarn
to update dependencies; - Run
yarn start
oryarn build
as normal.
Note: the diff and patch links here compare the HEAD of the master branch to the commit immediately after ejecting, so they will stay up to date if/when new commits are pushed to this repo.
I recommend applying the changes by hand, rather than forking or otherwise copying this repo, so that you benefit from the latest version of Create React App (and to see first-hand what the configuration changes are!).
If you use the patch, be sure to review the changes after applying, to make sure it all makes sense with the current version of Create React App’s scripts and configuration.
To run in production, run yarn build
then deploy at least the following files and folders:
build/
config/
scripts/
package.json
yarn.lock
If the server’s environment has NODE_ENV=production
set, then run yarn install
to install dependencies then use
node scripts/start.js
(or yarn start
) to launch the server process (which you’ll probably want to do via tools like
Nodemon and pm2).
If your server doesn’t already have NODE_ENV=production
set in its environment, then use yarn install --prod
and
NODE_ENV=production node scripts/start.js
(or yarn start:prod
) instead.
Note: the dependencies in
package.json
have been split into devDependencies and normal runtime dependencies, so runningyarn install --prod
(or running withNODE_ENV=production
) will not install any libraries that aren’t needed in a production environment. That mostly means the webpack infrastructure.
README.md
(the file you’re reading now).editorconfig
(although I do recommend using one)- version numbers in
package.json
(useyarn add
to add the new dependencies so you get the current versions) yarn.lock
(yours will be regenerated when you runyarn
)
yarn start
is used for both the local dev server (the default) and in production;- The file
index.html
is removed, and the html received by the browser is constructed instead by the express middleware insrc/server/index.js
(including pre-rendering the React component tree on the server) in both local dev and production; - In the client code,
ReactDOM.render()
is changed toReactDOM.hydrate()
; yarn build
is used (as usual) to prepare a build for production, and must be run beforeyarn start
can be used in production;- During local development, code changes are automatically recompiled and reloaded in your browser - even the code in
src/server/
.
Note: As with Create React App, only CSS changes are hot-reloaded without a browser refresh. If you’re interested in improving your hot-module-replacement experience, try vanilla webpack HMR first, especially if you don’t need to preserve component state (e.g. you use Redux). That’s simply a matter of:
- Run
yarn add -D webpack-hot-middleware
. (This is required because create-react-app’s HMR client doesn’t understand multiple compiler configurations, so forces a full refresh on every code update).- Add the below code to
src/index.js
:
module.hot.accept('./App', () => {
ReactDOM.render(<App />, document.getElementById('root'));
});
- In
server.dev.js
, addconst webpackHotMiddleware = require('webpack-hot-middleware');
at the top, andapp.use(webpackHotMiddleware(compiler.compilers[0]));
as the first line ofserverConfig.after()
.If that’s not enough for your use case, and you are brave, try react-hot-loader.
webpack.config.js
is changed to export an [array] of configurations, containing configuration for the client and server builds respectively;- The configurations are named
client
andserver
, which is a requirement of webpack-hot-server-middleware; - Configuration related to the
index.html
file is deleted; - The stats-webpack-plugin is added to the production config (so that the server can find out things, like the filenames of generated assets, at runtime);
- The server configuration is created by copying the client configuration and then changing some things. The changes
are done by code in
config/webpack.config.server.common.js
and mostly involve disabling tasks that don’t need to be repeated in the server build:- don’t emit static assets (images, css etc.);
- don’t build the service worker file;
- don’t minify the code;
- don’t inline
process.env
settings into the bundle.
- Get the client configuration from the configuration array now that the project uses multiple configurations;
- Turn off the history API fallback, because that is now the responsibility of the server code in
src/server/
; - Change the order in which the Webpack Dev Server initializes things, for compatibility with webpack-hot-server-middleware (explained in more detail in a comment).
The code that is specific to local development has been moved into scripts/server.dev.js
, so that start.js
can be
used to start the server in production as well.
Initializes the Webpack Dev Server. Unfortunately I couldn’t find a good way to link to a visual diff between the
original start.js
and the new server.dev.js
but if you use a tool of your own, such as the compare function in an
IDE, you’ll see that it is made up of fragments from the original start.js
with only a few additions:
- Use the
after
hook to mount the webpack-hot-server-middleware, and the same error handling middleware that will be used in production; - Add a workaround to fix hot replacement of CSS changes, due to the Webpack Dev Server’s hot replacement code not handling multiple configurations correctly.
This file is all new, and basically it initializes an Express server to:
- serve the static assets generated by the client build, and
- pass all other requests through to the SSR middleware in
src/server/index.js
.
It also includes a quick check that you’ve run yarn build
, and if the assets aren’t found then it attempts to run the
build script itself before proceeding.
Note: This automatic build won’t work if
yarn install
was executed in an environment whereNODE_ENV
was set toproduction
, because the webpack infrastructure won’t be present.
- Remove references to
index.html
; - Use the client configuration when measuring asset sizes, now that the project uses multiple configurations.
This code is all new, with the most interesting work happening in src/server/middleware/render.js
. That’s where the
html is constructed by:
- Constructing
<link>
tags for any CSS assets output by the build (code adapted from html-webpack-plugin); - Constructing a
<script>
tag for the main client bundle; - Populating React’s mount
<div>
with the output fromrenderToString()
.
You’ll want to add things like prerendered Redux state, or prerendered CSS from a CSS-in-JS technology like JSS, here.
During local development, you will see a FOUC (Flash Of Unstyled Content) between the browser’s initial paint and when the client Javascript inserts the CSS into the DOM. This does not occur in production.
The FOUC is inherent to the CSS configuration adopted by Create React App (i.e. the use of style-loader to support hot module replacement during development), but it isn’t an issue without SSR because without SSR you can’t see anything at all until the client Javascript has executed!
Personally, in my projects I delete the CSS configuration and use JSS instead. When using JSS and Redux, hot reloading is clean and seamless.
Many thanks to @faceyspacey for universal-demo, which introduced me to webpack-hot-server-middleware and the idea of using multiple Webpack configurations to build the client and server code at the same time 👍.