React SSR-first development
If you've built a Contensis React project, you've probably shipped browser-only code at least once. In other words, everything worked fine locally, but when you pushed your code up to production you starting seeing errors.
We recently changed how the react-starter development workflow operates to address that issue, making day-to-day development more straightforward and reducing the number of unpleasant surprises.
The root cause
Contensis React projects are server-side rendered – the server produces the initial HTML, then the browser takes over. That requires two separate builds:
- client bundles served to the user's browser
- a server bundle providing the production framework and component code to produce the server-rendered pages
Both sides need to agree on things like CSS class names and the shape of the initial page markup. However, the old local dev workflow only built one of them.
Running npm start fired up a webpack dev server with a client-only build, meaning there was no server bundle, and no SSR. You were effectively developing against a different rendering mode than the one that your users would initially see.
This meant that browser-only code – anything reaching for window, document, or localStorage without an SSR guard – would run fine locally then throw on the server the moment it deployed. Styles would flash unstyled on first load because CSS wasn't being extracted and injected server-side the way production expected.
With our recent move to React 18 and CRB v4, hydration mismatches became relevant too – React 18 is stricter about the server and client renders agreeing, and a CSR-only dev environment gives you no early warning when they don't. None of these show up when you're only running the client side.
What changed
The development command is now npm run dev, which runs both compilers – client and server – in parallel and boots a local SSR server on port 3001 alongside the webpack dev server on port 3000.
Now, what you see in your browser during development is exactly what you'll get in production – server-rendered HTML, hydrated by the client bundle. If there's a mismatch, you see it immediately, on your own machine, before anything gets committed or deployed.
We've also updated the development workflow so that the browser no longer opens until the server is actually ready. Previously, npm start would open the browser the moment webpack began compiling, rendering a blank page while you waited for the build to finish. Now the browser opens with the working application once the SSR server confirms it's listening on first load.
What you get
SSR on every dev session. Hydration errors, missing globals, SSR faux pas, and CSS mismatches surface locally instead of in a post-deploy investigation. Express.js server features or microservices are now available locally and natively during development.
A predictable startup. The browser opens when the server is ready – no blank pages, no "give it a few seconds".
Node.js debugging without extra setup. The server starts with the Node inspector enabled on port 9229. Attach VS Code to it in one click and you have full server-side debugging everywhere.
CSR still available when you want it. You can hit the SSR server with ?dynamic=true to force a fully client-side render without leaving port 3001, or go directly to port 3000 where the webpack dev server serves a client-only build throughout the session. This is useful for focused component work or testing HMR behaviour in isolation.
One webpack config for everything. Development and production previously relied on separate config files that might drift apart over time. There's now a single webpack.config.js so changes apply consistently across both environments.
How to take advantage of the changes
All new projects seeded from our react-starter will take on this new development workflow by default.
Existing or legacy projects are supported but require changes to the package.json scripts and webpack build configurations. Technical documentation is provided inside the react-starter repository.
- Start making the changes by renaming your existing
webpackfolder towebpack-oldand then copy in the entirewebpackfolder from react-starter. - Read the docs in the new folder and update or remove affected
package.jsonscripts matching those in react-starter. - Test the new build and scripts
npm run build+npm run server; Thennpm run dev; - Add any missing dependencies to your
devDependenciesor withnpm i --save-dev <package-name>. - Ensure any customisations or build tweaks you rely on have been ported into the new unified webpack configuration from
webpack-oldbefore deleting it.


