Getting started
1. Install
- npm
- yarn
npm install --save-dev react-ssr-webpack-plugin
yarn add -D react-ssr-webpack-plugin
2. Configure ReactSSRWebpackPlugin
In order to output SSR chunks together with CSR chunks, you need to use ReactSSRWebpackPlugin.
webpack.config.js
const {ReactSSRWebpackPlugin, PLUGIN_NAME} = require("react-ssr-webpack-plugin");
module.exports = {
  ...
  "module": {
    "rules": [
      ...
      {
        "test":/\.css$/,
        "use": (info) => {
          // You will need this config when you use css modules
          const isNode = info.compiler === PLUGIN_NAME;
          return (isNode ? [] : [{"loader": MiniCssExtractPlugin.loader}])
            .concat([
              {
                "loader":"css-loader",
                "ident": "css-loader",
                "options": {
                  "modules": {
                    "exportOnlyLocals": isNode, // See https://webpack.js.org/loaders/css-loader/#exportonlylocals for the details
                  },
                },
              },
          ]);
        },
      },
      ...
    ]
  },
  "plugins": [
    ...
    new ReactSSRWebpackPlugin({
      "entry": {
        "a.node": path.resolve(__dirname, "a.node")
      },
    }),
    ...
  ]
  ...
}
3a. Prepare node entry
Here is an example of a entry for SSR. You can use JSX for <head> tag.
a.node.js
import {renderToStaticMarkup, renderToString} from "react-dom/server";
import {App} from "./App";
export default async (props = {}) => { // this needs to be a default and async export
  const root = renderToString(<App {...props} />)
  const body = "<!DOCTYPE html>" + renderToStaticMarkup(<html>
    <head>
      <meta charSet="utf-8" />
      <meta name="viewport" content="width=device-width,initial-scale=1" />
      <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkqAcAAIUAgUW0RjgAAAAASUVORK5CYII="></link>
      <script dangerouslySetInnerHTML={{"__html": `globalThis.props = ${JSON.stringify(props).replace(/</g, "\\u003c")}`}} />
      <script dangerouslySetInnerHTML={{"__html": __SOURCES__["a.web.js"]}} />
    </head>
    <body>
      <div id="root" dangerouslySetInnerHTML={{"__html": root}} />
      <script integrity={__DIGESTS__["vendors.js"]} src={`${__webpack_public_path__}/${__FILES__["vendors.js"]}`} crossOrigin="anonymous" />
    </body>
  </html>);
  return {
    body,
    "statusCode": 200,
  };
}
note
- __DIGESTS__,- __FILES__and- __SOURCES__are- react-ssr-webpack-pluginspecific tokens and they will get replaced at build time.- __DIGESTS__is for subresource integrity purpose.
- __FILES__is for getting the hashed filename.
- __SOURCES__is for inlining the chunk to the page.
 
3b. Prepare web entry
a.web.js
import {App} from "./App";
import ReactDOM from "react-dom";
ReactDOM.hydrateRoot(
  document.getElementById("root"),
  <App {...globalThis.props} />
);
4. Configure ReactSSRMiddleware
In order to simulate SSR, you need to use ReactSSRMiddleware with webpack-dev-server. With webpack-dev-server you can enjoy the live reload when not only the client code but also the server code is change.
- webpack-dev-server < 4.7.0
- webpack-dev-server >= 4.7.0
webpack.config.js
{
  "devServer": {
    "onAfterSetupMiddleware": (devServer) => {
      devServer.app.get("*", ReactSSRMiddleware(
        devServer.compiler,
        {
          "reqToProps": (req) => ({"url": url.parse(req.originalUrl, true)})
        }
      ));
    },
  },
}
webpack.config.js
{
  "devServer": {
    "setupMiddlewares": (middlewares, devServer) => ([
    ...middlewares,
      { // this needs to be at the end of middlewares
        "name": "ReactSSRWebpackPlugin",
        "path": "*",
        "middleware": ReactSSRMiddleware(
          devServer.compiler,
          {
            "reqToProps": (req) => ({"url": url.parse(req.originalUrl, true)})
          }
        ),
      },
    ]),
  },
}
5. Check the output

<!DOCTYPE html>
<html>
  <head>
    <meta charSet="utf-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1"/>
    <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkqAcAAIUAgUW0RjgAAAAASUVORK5CYII="/>
    <style>
      /*!******************************************************************************************!*\
      !*** css ./node_modules/css-loader/dist/cjs.js??css-loader!./examples/app/1.0/a/App.css ***!
      \******************************************************************************************/
      * {
        margin: 0;
      }
      html,
      body,
      body > div {
        height: 100%;
      }
    </style>
    <script>
      globalThis.props = {
        "url": {
          "protocol": null,
          "slashes": null,
          "auth": null,
          "host": null,
          "port": null,
          "hostname": null,
          "hash": null,
          "search": null,
          "query": {},
          "pathname": "/a.node",
          "path": "/a.node",
          "href": "/a.node"
        },
        "__VERSION__": "2.0.development"
      }
    </script>
    <script>
      /******/
      (()=>{
        // webpackBootstrap ...
      )();
    </script>
  </head>
  <body>
    <div id="root">
      <div class="common__DivWrapper-sc-1o9hep8-0 App__DivWrapperA-sc-lq4rwr-0 jbWOMA izvJxW">
        ...
      </div>
    </div>
    <script integrity="sha256-Yx4g0nYq75xTOA78jk1GNpDSxEXPGApmhIhok8luDPM=" src="vendors.26771103e691bfcf006c.js" crossorigin="anonymous"></script>
  </body>
</html>