React - Server-side Rendering (SSR)
code snippet
A sample from a DEV Community post 1.
# init
npm init
# https://koajs.com/
npm install @koa/router@12.0.0
npm install koa@2.14.2
npm install koa-bodyparser@4.4.1
# https://react.dev/
npm install react@18.2.0
npm install react-dom@18.2.0
# https://babeljs.io/docs/babel-cli
# https://babeljs.io/docs/babel-register
# https://www.npmjs.com/package/babel-plugin-transform-react-jsx
npm install --save-dev @babel/cli@7.22.10
npm install --save-dev babel-plugin-transform-react-jsx@6.24.1
npm install --save-dev @babel/register@7.22.5
.babelrc
{
"plugins": ["transform-react-jsx"]
}
app.js
const React = require('react');
const App = () => {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>React SSR</title>
</head>
<body>
<div id="root">
<h1>Hello, world!</h1>
</div>
</body>
</html>
);
}
module.exports = App
client.js
const React = require('react');
const { hydrateRoot } = require('react-dom/client');
const App = require('./app');
hydrateRoot(document.getElementById('root'), <App />);
router-ssr.js
const React = require('react');
const { renderToPipeableStream } = require('react-dom/server');
const App = require('./app');
const router = async (ctx) => {
let didError = false;
try {
// Wraps into a promise to force Koa to wait for the render to finish
return new Promise((_resolve, reject) => {
const { pipe, abort } = renderToPipeableStream(
<App />,
{
bootstrapModules: ['./client.js'],
onShellReady() {
ctx.respond = false;
ctx.status = didError ? 500 : 200;
ctx.set('Content-Type', 'text/html');
pipe(ctx.res);
ctx.res.end();
},
onShellError() {
ctx.status = 500;
abort();
didError = true;
ctx.set('Content-Type', 'text/html');
ctx.body = '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>';
reject();
},
onError(error) {
didError = true;
console.error(error);
reject();
}
},
);
setTimeout(() => {
abort();
}, 10_000);
})
} catch (err) {
console.log(err);
ctx.status = 500;
ctx.body = 'Internal Server Error';
}
};
module.exports = router
server.js
const Router = require('@koa/router');
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const ssr = require('./router-ssr')
const router = new Router();
const app = new Koa();
router.get('/app', ssr);
router.get('/(.*)', async (ctx) => {
ctx.status = 200;
ctx.body = 'OK';
});
app.use(bodyparser());
app.use(router.routes());
app.use(router.allowedMethods());
module.exports = app;
index.js
require("@babel/register");
const http = require('http');
const _server = require('./server');
const currentHandler = _server.callback();
const server = http.createServer(_server.callback());
server.listen(4001, (error) => {
console.log('listening...');
console.error(error);
});
After creating all files, the repo should look like below.
|
|-/.babelrc
|-/app.js
|-/client.js
|-/router-ssr.js
|-/server.js
|-/index.js
|-/package-lock.json
|-/package.json
# run the server
node index.js
# GET request
curl localhost:4001/app