Minimal server-side rendering with React, part 1
Published by Simon Ingeson
This is part of a series:
First, let’s cover some caveats:
- While technically possible, this is not meant for a production setup. It’s just a way to demonstrate server-side rendering with React.
- It won’t generate a browser bundle. That means there is no hydration step as no JavaScript will execute in the browser. The upside with this is that the app will be faster.
- This blog post assumes knowledge of modern JavaScript and React.
Let’s get started.
Setup the project
Feel free to adjust these steps according to your needs. For example, swap to yarn
instead of npm
if that’s your thing. Open up your favorite terminal and run these commands:
mkdir basic-ssr
cd basic-ssr
npm init --yes # create package.json using defaults
npx gitignore node # loads basic .gitignore for Node.js projects
Install dependencies
Again, back in your terminal:
npm install \
express \
react \
react-dom \
@babel/core \
@babel/register \
@babel/preset-env \
@babel/preset-react
Usually, you would install the @babel/*
libraries as devDependencies
. However, since this setup will not have a build step, they can be included as regular dependencies
.
You can easily replace express
with something else, say fastify
.
Setup start script
Edit the package.json
file:
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "start": "node --require @babel/register index.js"
},
Note the --require
flag for @babel/register
. That’s what lets us avoid a build step. Another option would be to use @babel/node
.
Add .babelrc
in the project root folder
Set "runtime": "automatic"
to avoid having to import React.
{
"presets": [
"@babel/preset-env",
["@babel/preset-react", { "runtime": "automatic" }]
]
}
Add index.js
in the project root folder
import express from 'express'
import { renderToString } from 'react-dom/server'
const app = express()
function App() {
// To avoid putting all of this in a string, we'll use JSX for html, head, body, etc. too.
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content="Hello World" />
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
)
}
app.get('/', (_, res) => {
// Since we don't care about hydration here, we can safely remove the `data-reactroot` attribute.
const markup = renderToString(<App />).replace(` data-reactroot=""`, '')
// We need to set the doctype here as we can't include it using JSX.
res.send(`<!DOCTYPE html>${markup}`)
})
app.listen(process.env.PORT || 3000)
And that’s it! You can run this with npm start
. In future blog posts, I will improve on this project and move towards a more production-friendly setup. You can find the source code in the GitHub repository.
Cover photo by Christopher Burns.