Subchapter 3.5
references/core-rendering.mdMarkdown4 KBView on GitHub
Two special handlers control requests that aren’t matched by routes/: the server entry (runs first, for cross-cutting concerns) and the renderer (catch-all /**, lowest priority).
Matching order: request hook → route rules → middleware → specific routes → → .
Auto-detected from server.ts in the project root. It runs for every request before the renderer. Return a Response to terminate, return nothing to continue.
export default {
async fetch(req: Request) {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response("OK", { status: 200 });
}
// return nothing -> continue to routes/renderer
},
};Or use defineHandler for the H3 event + context:
import { defineHandler } from "nitro";
export default defineHandler((event) => {
event.context.requestId = crypto.randomUUID();
// no return -> continue
});Configure explicitly with serverEntry:
export default defineConfig({
serverEntry: { handler: "./server.ts", format: "web" }, // or false to disable
});Any framework exposing a web fetch(request): Response works as a server entry:
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello from Hono!"));
export default app;For Node-style (req, res) frameworks (Express, Fastify), name the file server.node.ts (or set format: "node") — Nitro converts it via srvx (opens in a new tab):
import Express from "express";
const app = Express();
app.use("/", (_req, res) => res.send("Hello from Express!"));
export default app;A catch-all that serves HTML/SSR for unmatched routes. Configured via renderer, or auto-detected from index.html.
export default defineConfig({
renderer: {
template: "./index.html", // HTML template
handler: "./renderer.ts", // OR a custom handler (template ignored if set)
static: false, // serve template as-is, skip processing
},
});Set renderer: false to disable entirely.
If an index.html exists, Nitro serves it for all unmatched routes — the default SPA behavior with Vite. With a Vite ssr environment, add <!--ssr-outlet--> and Nitro injects SSR output.
export default function renderer({ req }: { req: Request }) {
const url = new URL(req.url);
return new Response(
`<!DOCTYPE html><html><body><h1>${url.pathname}</h1></body></html>`,
{ headers: { "content-type": "text/html; charset=utf-8" } },
);
}HTML templates support the rendu (opens in a new tab) preprocessor:
<h1>Hello {{ $URL.pathname }}</h1>
<? if ($METHOD === "POST") { ?><p>Submitted!</p><? } ?>
<script server>
const data = await fetch("https://api.example.com/data").then((r) => r.json());
</script>
<pre>{{ JSON.stringify(data) }}</pre>{{ expr }} HTML-escaped, {{{ expr }}} / <?= expr ?> raw, <? ... ?> control flow.$REQUEST, $METHOD, $URL, $HEADERS, $RESPONSE, $COOKIES.setCookie, redirect, echo (streaming), htmlspecialchars.Response to stop, or nothing to continue; keep it lightweight (runs every request).server.ts; Node (req,res) ones use server.node.ts.[...].ts catch-all route conflicts with the renderer (Nitro warns).