Nitro Server Entry

Use a server entry to create a global middleware that runs for all routes before they are matched.
Nitro v3 Alpha docs are a work in progress — expect updates, rough edges, and occasional inaccuracies.

The server entry is a special handler in Nitro that acts as a global middleware, running for every incoming request before routes are matched (see request lifecycle). It's commonly used for cross-cutting concerns like authentication, logging, request preprocessing, or creating custom routing logic.

Auto-detected server.ts

By default, Nitro automatically looks for a server.ts (or .js, .mjs, .tsx, etc.) file in your project root or scan directories.

If found, Nitro will use it as the server entry and run it for all incoming requests.

export default {
  async fetch(req: Request) {
    const url = new URL(req.url);

    // Handle specific routes
    if (url.pathname === "/health") {
      return new Response("OK", {
        status: 200,
        headers: { "content-type": "text/plain" }
      });
    }

    // Add custom headers to all requests
    // Return nothing to continue to the next handler
  }
}
When server.ts is detected, Nitro will automatically log in the terminal: Using \server.ts` as server entry.`

With this setup:

  • /health → Handled by server entry
  • /api/hello → Server entry runs first, then the API route
  • /about, etc. → Server entry runs first, then continues to routes or renderer

Framework compatibility

The server entry is a great way to integrate with other frameworks such as Elysia, Hono or H3.

server.ts
import { H3 } from "h3";

const app = new H3()

app.get("/", () => "⚡️ Hello from H3!");

export default app;

Custom server entry file

You can specify a custom server entry file using the serverEntry option in your Nitro configuration.

nitro.config.ts
import { defineNitroConfig } from 'nitro/config'

export default defineNitroConfig({
  serverEntry: './nitro.server.ts'
})

Using event handler

You can also export an event handler using defineHandler for better type inference and access to the h3 event object:

server.ts
import { defineHandler } from "nitro/h3";

export default defineHandler((event) => {
  // Add custom context
  event.context.requestId = crypto.randomUUID();
  event.context.timestamp = Date.now();

  // Log the request
  console.log(`[${event.context.requestId}] ${event.method} ${event.path}`);

  // Continue to the next handler (don't return anything)
});
If your server entry returns undefined or doesn't return anything, the request will continue to be processed by routes and the renderer. If it returns a response, the request lifecycle stops there.

Request lifecycle

The server entry is called as part of the global middleware stack, after route rules but before route handlers:

1. Server hook: `request`
2. Route rules (headers, redirects, etc.)
3. Global middleware (middleware/)
4. Server entry ← You are here
5. Routes (routes/)
6. Renderer (renderer.ts or index.html)

Think of the server entry as the last global middleware to run before route matching.

Read more in Architecture > Request lifecycle.

Best practices

  • Use server entry for cross-cutting concerns that affect all routes
  • Return undefined to continue processing, return a response to terminate
  • Keep server entry logic lightweight for better performance
  • Use global middleware for modular concerns instead of one large server entry
  • Consider using Nitro plugins for initialization logic
  • Avoid heavy computation in server entry (it runs for every request)
  • Don't use server entry for route-specific logic (use route handlers instead as they are more performant)