I been trying to create template with Vite SSR
with react-router-dom
for my requirements.
i know Next JS
is better alternative for Vite SSR
.
heare i have some configs that i made for this template.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
export default defineConfig({
plugins: [react()],
ssr: {
external: [
'react-router-dom'
]
},
resolve: {
alias: {
"@App": path.resolve(process.cwd(), "src"),
"@Components": path.resolve(process.cwd(), "src/Components"),
"@Pages": path.resolve(process.cwd(), "src/pages"),
"@Styles": path.resolve(process.cwd(), "src/Styles"),
"@Public": path.resolve(process.cwd(), "public")
},
},
})
import { BrowserRouter } from "react-router-dom";
function Application() {
return (
<BrowserRouter>
HI
</BrowserRouter>
);
}
export default Application;
yarn create vite
Select Others Select ssr-react Select typescript+swc
Replace src/App.tsx to upper file
Run
yarn dev
vite
vitejs
vitejs ssr
react
react-router
react-router-dom
ssr
js dom
vite config
I Expected to working React application with react-router-dom
.
StaticRouter
next.js ssr
renderingyarn create vite
yarn add compression cross-env sirv express react-router-dom
Files
Root/Server.js
import fs from "node:fs/promises";
import path from "path";
import express from "express";
import { createServer as createViteServer } from "vite";
const isProduction = process.env.NODE_ENV === "production";
const Port = process.env.PORT || 3000;
const Base = process.env.BASE || "/";
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;
const app = express();
let vite;
// ? Add vite or respective production middlewares
if (!isProduction) {
vite = await createViteServer({
server: { middlewareMode: true },
appType: "custom",
});
app.use(vite.middlewares);
} else {
const sirv = (await import("sirv")).default;
const compression = (await import("compression")).default;
app.use(compression());
app.use(Base, sirv("./dist/client", {
extensions: [],
gzip: true,
}));
}
// ? Add Your Custom Routers & Middlewares heare
app.use(express.static("public"));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get("/api", (req, res) => {
res.json({ message: "Hello World" });
});
// ? SSR Render - Rendring Middleware
app.use("*", async (req, res, next) => {
// ! Favicon Fix
if (req.originalUrl === "/favicon.ico") {
return res.sendFile(path.resolve("./public/vite.svg"));
}
// ! SSR Render - Do not Edit if you don't know what heare whats going on
let template, render;
try {
if (!isProduction) {
template = await fs.readFile('./index.html', 'utf-8');
template = await vite.transformIndexHtml(req.originalUrl, 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({ path: req.originalUrl }, ssrManifest);
const html = template.replace(`<!--app-html-->`, rendered ?? '');
res.status(200).setHeader("Content-Type", "text/html").end(html);
} catch (error) {
// ? You can Add Something Went Wrong Page
vite.ssrFixStacktrace(error);
next(error);
}
});
// ? Start http server
app.listen(Port, () => {
console.log(`Server running on http://localhost:${Port}`);
});
Root/src/entry-server.tsx
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import { Router } from "./router";
interface IRenderProps {
path: string;
}
export const render = ({ path }: IRenderProps) => {
return ReactDOMServer.renderToString(
<StaticRouter location={path}>
<Router />
</StaticRouter>
);
};
Root/src/entry-client.tsx
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { Router } from "./router";
ReactDOM.hydrateRoot(
document.getElementById("app") as HTMLElement,
<BrowserRouter>
<Router />
</BrowserRouter>
);
Root/src/router.tsx
import { Routes, Route } from "react-router-dom";
import { Home } from "./pages/Home";
import { Other } from "./pages/Other";
import { NotFound } from "./pages/NotFound";
export const Router = () => {
return (
<Routes>
<Route index element={<Home />} />
<Route path="/other" element={<Other />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
};
Root/package.json - Edit Scripts only
{
"scripts": {
"dev": "cross-env NODE_ENV=development node ./server.js",
"build": "npm run build:client && npm run build:server",
"build:client": "vite build --ssrManifest --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
"serve": "cross-env NODE_ENV=production node ./server.js"
}
}
Root/index.html - Required to Edit
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="app">
<!--app-html-->
</div>
<script type="module" src="/src/entry-client.tsx"></script>
</body>
</html>
Root/src/pages/Home.tsx - Optional Normal React Component
export const Home = () => {
return <div>This is the Home Page</div>;
};
Root/src/pages/Other.tsx - Optional Normal React Component
export const Other= () => {
return <div>This is the Another Page</div>;
};
Root/src/pages/NotFound.tsx - Optional Normal React Component
export const NotFound = () => {
return <div>Not Found</div>;
};
yarn dev