A minimal full-stack monorepo with out-of-the-box SSR, SSG, and routing — powered by Vite, React, and Express. No rewrites needed. Just configure your routes and loaders.
- Zero learning curve — if you know React and Express, you're ready.
- No frontend rewrites — your existing React code works as-is.
- Route-based rendering — declare which routes are SSR or SSG in one config file.
- Loaders — each route can fetch its own data server-side, just like Remix/Next.js.
- Works with any frontend — swap React for Vue/Svelte and the SSR pipeline still works.
npx start-simple my-app
cd my-app
npm install
npm run devmy-app/
├── packages/
│ ├── frontend/ # Vite + React SSR app
│ │ ├── renderingConfig.js # ✨ SSG & SSR route definitions + loaders
│ │ ├── server.js # Express SSR server (dev & prod)
│ │ ├── scripts/
│ │ │ └── prerender.js # Build-time SSG pre-renderer
│ │ └── src/
│ │ ├── entry-client.jsx
│ │ ├── entry-server.jsx
│ │ ├── App.jsx
│ │ ├── context/
│ │ │ └── LoaderDataContext.jsx
│ │ └── pages/
│ │ ├── Home.jsx
│ │ ├── About.jsx
│ │ └── Post.jsx
│ └── backend/ # Express API server
│ └── index.js
├── package.json # Monorepo root (npm workspaces + Turbo)
└── turbo.json
Run all scripts from the monorepo root (my-app/):
| Script | Description |
|---|---|
npm run dev |
Start the SSR frontend and the backend API on concurrent ports |
npm run frontend |
Start only the frontend SSR dev server (node server) |
npm run backend |
Start only the backend API server |
npm run start |
Start the frontend in production SSR mode (pre-rendered SSG pages served from disk) |
npm run build |
Build the frontend for production + pre-render SSG routes (automatically triggered) |
export const ssgRoutes = [
{
path: "/",
loader: async () => ({ title: "Home" }),
},
];
export const ssrRoutes = [
{
path: "/post/:id",
loader: async ({ params }) => {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
return res.json();
},
},
];import { useLoaderData } from "./context/LoaderDataContext";
export default function Post() {
const data = useLoaderData();
return <h1>{data.title}</h1>;
}- SSR routes — loader runs on the server for every request.
- SSG routes — loader runs at build time, HTML is saved to disk.
- Unlisted routes — treated as SSG by default (client-side only in dev).
| Mode | When Loader Runs | Best For |
|---|---|---|
| SSG | Once at build time | Static pages (home, about, blog index) |
| SSR | Every request | Dynamic pages (user profiles, posts, search results) |
| Default (unlisted) | Client-side only | Pages without data needs |
# 1. Build everything
npm run build
# 2. Start the production SSR server
npm run start
# 3. Start the API server
npm run backend- documentation.md — Full API reference
- tutorial.md — Step-by-step guide to adding routes
- mechanism.md — Deep dive into how SSR/SSG works internally
MIT
