Server-Side Rendering (SSR)
This chapter introduces how to implement SSR functionality using Rsbuild.
Please note that Rsbuild itself does not provide out-of-the-box SSR functionality, but instead provides low-level APIs and configurations to allow framework developers to implement SSR. If you require out-of-the-box SSR support, you may consider using full-stack frameworks based on Rsbuild, such as Modern.js.
What is SSR
SSR stands for "Server-Side Rendering". It means that the HTML of the web page is generated by the server and sent to the client, rather than sending only an empty HTML shell and relying on JavaScript to generate the page content.
In traditional client-side rendering, the server sends an empty HTML shell and some JavaScript scripts to the client, and then fetching data from the server's API and fills the page with dynamic content. This leads to slow initial page loading times and is not conducive to user experience and SEO.
With SSR, the server generates HTML that already contains dynamic content and sends it to the client. This makes the initial page loading faster and more SEO-friendly, as search engines can crawl the rendered page.
File Structure
A typical SSR application will have the following files:
- index.html
- server.js # main application server
- src/
- App.js # exports app code
- index.client.js # client entry, mounts the app to a DOM element
- index.server.js # server entry, renders the app using the framework's SSR API
The index.html
will need to include a placeholder where the server-rendered markup should be injected:
<div id="root"><!--app-content--></div>
Create SSR configuration
In the SSR scenario, two types of outputs (web and node targets), need to be generated at the same time, for client-side rendering (CSR) and server-side rendering (SSR) respectively.
At this time, you can use Rsbuild's multi-environment builds capability, define the following configuration:
rsbuild.config.ts
export default {
environments: {
// Configure the web environment for browsers
web: {
source: {
entry: {
index: './src/index.client.js',
},
},
output: {
// Use 'web' target for the browser outputs
target: 'web',
},
html: {
// Custom HTML template
template: './index.html',
},
},
// Configure the node environment for SSR
node: {
source: {
entry: {
index: './src/index.server.js',
},
},
output: {
// Use 'node' target for the Node.js outputs
target: 'node',
},
},
},
};
Custom server
Rsbuild does not have built-in SSR rendering capabilities, but you can implement SSR rendering through Rsbuild's custom server and environment API:
server.mjs
import express from 'express';
import { createRsbuild, loadConfig } from '@rsbuild/core';
// Implement SSR rendering function
const serverRender = (serverAPI) => async (_req, res) => {
// Load Node bundle for SSR
const indexModule = await serverAPI.environments.node.loadBundle('index');
const markup = indexModule.render();
const template = await serverAPI.environments.web.getTransformedHtml('index');
// Insert SSR rendering content into HTML template
const html = template.replace('<!--app-content-->', markup);
res.writeHead(200, {
'Content-Type': 'text/html',
});
res.end(html);
};
// Custom server
async function startDevServer() {
const { content } = await loadConfig({});
const rsbuild = await createRsbuild({
rsbuildConfig: content,
});
const app = express();
const rsbuildServer = await rsbuild.createDevServer();
const serverRenderMiddleware = serverRender(rsbuildServer);
// SSR rendering when accessing /index.html
app.get('/', async (req, res, next) => {
try {
await serverRenderMiddleware(req, res, next);
} catch (err) {
logger.error('SSR render error, downgrade to CSR...\n', err);
next();
}
});
app.use(rsbuildServer.middlewares);
const httpServer = app.listen(rsbuildServer.port, async () => {
await rsbuildServer.afterListen();
});
rsbuildServer.connectWebSocket({ server: httpServer });
}
startDevServer(process.cwd());
Modify startup script
After using a custom Server, you need to change the startup command from rsbuild dev
to node ./server.mjs
.
If you need to preview the online effect of SSR rendering, you also need to modify the preview command. SSR Prod Server example reference: Example.
package.json
{
"scripts": {
"build": "rsbuild build",
"dev": "node ./server.mjs",
"preview": "node ./prod-server.mjs"
}
}
Now, you can run the npm run dev
command to start the dev server with SSR rendering function, and visit http://localhost:3000/
to see that the SSR content has been rendered to the HTML page.
Get Manifest
By default, scripts and links associated with the current page are automatically inserted into the HTML template. At this time, the compiled HTML template content can be obtained through getTransformedHtml.
When you need to dynamically generate HTML on the server side, you'll need to inject the URLs of JavaScript and CSS assets into the HTML. By configuring output.manifest, you can easily obtain the manifest information of these assets. Here's an example:
rsbuild.config.ts
export default {
output: {
manifest: true,
},
};
server.ts
async function renderHtmlPage(): Promise<string> {
const manifest = await fs.promises.readFile('./dist/manifest.json', 'utf-8');
const { entries } = JSON.parse(manifest);
const { js, css } = entries['index'].initial;
const scriptTags = js
.map((url) => `<script src="${url}" defer></script>`)
.join('\n');
const styleTags = css
.map((file) => `<link rel="stylesheet" href="${file}">`)
.join('\n');
return `
<!DOCTYPE html>
<html>
<head>
${scriptTags}
${styleTags}
</head>
<body>
<div id="root"></div>
</body>
</html>`;
}
Examples