I am trying to server side render my vite + react application. I followed vite documentation to create the server and now everything seems to run correctly. I am new to server side rendering so I have some doubts. When i see my page source when the app initially renders, it is showing the correct page source as expected. But if I interacts with my app and loads another view, the page source is not updated. Is this the normal behaviour, or is there anything i can do to get the updated page source whenever I change my view from the client side.
This is my server.js
import fs from "node:fs/promises";
import express from "express";
global.window = {};
// Constants
const isProduction = process.env.NODE_ENV === "production";
const port = process.env.PORT || 5173;
const base = process.env.BASE || "/";
// Cached production assets
const templateHtml = isProduction
? await fs.readFile("./dist/client/index.html", "utf-8")
: "";
const ssrManifest = isProduction
? await fs.readFile("./dist/client/.vite/ssr-manifest.json", "utf-8")
: undefined;
// Create http server
const app = express();
// Add Vite or respective production middlewares
let vite;
if (!isProduction) {
const { createServer } = await import("vite");
vite = await createServer({
server: { middlewareMode: true },
appType: "custom",
base,
});
app.use(vite.middlewares);
} else {
const compression = (await import("compression")).default;
const sirv = (await import("sirv")).default;
app.use(compression());
app.use(base, sirv("./dist/client", { extensions: [] }));
}
// Serve HTML
app.use("*", async (req, res) => {
try {
const url = req.originalUrl.replace(base, "");
let template;
let render;
if (!isProduction) {
// Always read fresh template in development
template = await fs.readFile("./index.html", "utf-8");
template = await vite.transformIndexHtml(url, template);
render = (await vite.ssrLoadModule("/src/entry-server.tsx")).render;
} else {
template = templateHtml;
render = (await import("./dist/server/entry-server.js")).render;
}
const rendered = await render(url, ssrManifest);
const html = template
.replace(`<!--app-head-->`, rendered.head ?? "")
.replace(`<!--app-html-->`, rendered.html ?? "");
res.status(200).set({ "Content-Type": "text/html" }).end(html);
} catch (e) {
vite?.ssrFixStacktrace(e);
console.log(e.stack);
res.status(500).end(e.stack);
}
});
// Start http server
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`);
});
My vite.config is as follows
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import viteTsconfigPaths from "vite-tsconfig-paths";
// import macrosPlugin from 'vite-plugin-babel-macros'
import svgrPlugin from "vite-plugin-svgr";
// import path from 'path'
import { splitVendorChunkPlugin } from "vite";
// https://vitejs.dev/config/
// How to migrate from CRA to vite: https://cathalmacdonnacha.com/migrating-from-create-react-app-cra-to-vite
import dns from "dns";
dns.setDefaultResultOrder("verbatim");
export default defineConfig({
define: {
"process.env": {},
},
resolve: {
alias: [
{
find: "stream",
replacement: `stream-browserify`,
},
],
},
plugins: [
react({
babel: {
presets: ["@babel/preset-typescript"],
plugins: [
"@babel/plugin-transform-typescript",
[
"babel-plugin-styled-components",
{
ssr: false,
pure: true,
displayName: true,
fileName: false,
minify: false,
transpileTemplateLiterals: false,
},
],
],
},
}),
viteTsconfigPaths(),
svgrPlugin(),
splitVendorChunkPlugin(),
],
build: {
outDir: "build",
cssCodeSplit: false,
},
ssr: {
noExternal: ["styled-components", "@emotion/*"],
// noExternal: [],
},
server: {
open: true,
port: 5173,
},
preview: {
port: 8000,
},
});
My entry-client.tsx
import React from "react";
import { Provider } from "react-redux";
import { store } from "@store/index";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.hydrateRoot(
document.getElementById("root") as HTMLElement,
<Provider store={store}>
<App />
</Provider>
);
My entry-server.tsx
import React from "react";
import ReactDOMServer from "react-dom/server";
import { Provider } from "react-redux";
import { store } from "@store/index";
import App from "./App";
export function render() {
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<App />
</Provider>
);
return { html };
}