Skip to content

Commit d25b45e

Browse files
committed
docs: vite + ssr
1 parent 3778307 commit d25b45e

1 file changed

Lines changed: 107 additions & 2 deletions

File tree

docs/1.docs/4.renderer.md

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,111 @@ If you define a catch-all route (`[...].ts`) in your routes, Nitro will warn you
174174

175175
:read-more{to="/docs/lifecycle" title="Lifecycle"}
176176

177+
## Server-Side Rendering (SSR)
178+
179+
Nitro supports full server-side rendering through Vite's [environment API](https://vite.dev/guide/api-environment). With SSR, Nitro renders your application to HTML on the server, sends it to the browser, and the client hydrates the page to make it interactive.
180+
181+
This requires two entry files:
182+
183+
- **Server entry** (`entry-server`) — renders the app to HTML on the server.
184+
- **Client entry** (`entry-client`) — hydrates the server-rendered HTML in the browser.
185+
186+
### Auto-detected entry points
187+
188+
Nitro automatically detects `entry-server` and `entry-client` files in your project's `app/`, `src/`, or root directory. No Vite configuration is needed when you follow this convention.
189+
190+
```
191+
app/
192+
entry-server.ts ← auto-detected as SSR entry
193+
entry-client.ts ← auto-detected as client entry
194+
routes/
195+
api/hello.ts
196+
```
197+
198+
::tip
199+
When entry files are detected, Nitro logs the paths in the terminal:
200+
```
201+
ℹ Using app/entry-server.ts as vite ssr entry.
202+
ℹ Using app/entry-client.ts as vite client entry.
203+
```
204+
::
205+
206+
Supported file extensions: `.ts`, `.js`, `.mts`, `.mjs`, `.tsx`, `.jsx`.
207+
208+
### Server entry
209+
210+
The server entry must export an object with a `fetch` method that receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response):
211+
212+
```ts [src/entry-server.tsx]
213+
export default {
214+
async fetch(req: Request): Promise<Response> {
215+
const html = renderAppToHTML(req);
216+
return new Response(html, {
217+
headers: { "Content-Type": "text/html;charset=utf-8" },
218+
});
219+
},
220+
};
221+
```
222+
223+
### Client entry
224+
225+
The client entry runs in the browser and hydrates the server-rendered HTML:
226+
227+
```ts [src/entry-client.tsx]
228+
import { hydrate } from "my-framework";
229+
import { App } from "./app";
230+
231+
hydrate(document.querySelector("#app"), App);
232+
```
233+
234+
### Asset management with `?assets` imports
235+
236+
In production, Nitro collects CSS and JS assets from each entry point. Use the `?assets` query to import asset manifests in the server entry:
237+
238+
```ts [src/entry-server.tsx]
239+
import clientAssets from "./entry-client?assets=client";
240+
import serverAssets from "./entry-server?assets=ssr";
241+
242+
export default {
243+
async fetch(req: Request) {
244+
const assets = clientAssets.merge(serverAssets);
245+
246+
const html = `<!DOCTYPE html>
247+
<html>
248+
<head>
249+
${assets.css.map((attr) => `<link rel="stylesheet" href="${attr.href}" />`).join("\n")}
250+
${assets.js.map((attr) => `<link rel="modulepreload" href="${attr.href}" />`).join("\n")}
251+
<script type="module" src="${assets.entry}"></script>
252+
</head>
253+
<body>
254+
<div id="app">${await renderToString(req)}</div>
255+
</body>
256+
</html>`;
257+
258+
return new Response(html, {
259+
headers: { "Content-Type": "text/html;charset=utf-8" },
260+
});
261+
},
262+
};
263+
```
264+
265+
The `?assets=client` suffix tells Nitro to collect assets from the client environment, while `?assets=ssr` collects assets from the SSR environment (such as CSS imported only in server components). The `merge()` method combines them into a single manifest with:
266+
267+
- `assets.entry` — the client entry script URL
268+
- `assets.css` — an array of stylesheet attributes (`{ href }`)
269+
- `assets.js` — an array of module preload attributes (`{ href }`)
270+
271+
### Framework examples
272+
273+
See working SSR examples for popular frameworks:
274+
275+
| Framework | Example |
276+
| --- | --- |
277+
| React | [`vite-ssr-react`](https://github.com/nitrojs/nitro/tree/main/examples/vite-ssr-react) |
278+
| Vue + Vue Router | [`vite-ssr-vue-router`](https://github.com/nitrojs/nitro/tree/main/examples/vite-ssr-vue-router) |
279+
| Solid | [`vite-ssr-solid`](https://github.com/nitrojs/nitro/tree/main/examples/vite-ssr-solid) |
280+
| Preact | [`vite-ssr-preact`](https://github.com/nitrojs/nitro/tree/main/examples/vite-ssr-preact) |
281+
177282
## Use Cases
178283

179284
### Single-Page Application (SPA)
@@ -183,6 +288,6 @@ Serve your SPA's `index.html` for all routes to enable client-side routing:
183288
> [!TIP]
184289
> This is the default behavior of Nitro when used with Vite.
185290
186-
<!-- ### Server-Side Rendering (SSR) -->
291+
### Server-Side Rendering (SSR)
187292

188-
<!-- TODO: Add example with ssr-outlet and vite -->
293+
See [Server-Side Rendering](#server-side-rendering-ssr) for more details.

0 commit comments

Comments
 (0)