recipes / graceful-shutdown / tanstack-start

Graceful shutdown with TanStack Start and Nitro

Nitro's node server drains in-flight requests on SIGTERM. Tune its timeout and add readiness in the server entry.

The node-server preset of Nitro listens for SIGTERM and SIGINT, stops accepting connections, waits for in-flight requests, and exits. You configure it with environment variables and add readiness in the server entry.

Environment

env:
  - name: NITRO_SHUTDOWN_TIMEOUT
    value: "10000"   # ms to wait for in-flight requests, default 30000

Keep preStop sleep + NITRO_SHUTDOWN_TIMEOUT below terminationGracePeriodSeconds.

Readiness in the server entry

src/server.ts is evaluated once at startup, so it is the place for process-level code.

// src/server.ts
import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
import { state } from "./lib/state";

for (const signal of ["SIGTERM", "SIGINT"] as const) {
  process.on(signal, () => {
    state.ready = false; // /api/readyz starts returning 503
  });
}

export default createServerEntry({
  fetch(request) {
    return handler.fetch(request);
  },
});
// src/routes/api/readyz.ts
import { createFileRoute } from "@tanstack/react-router";
import { state } from "../../lib/state";

export const Route = createFileRoute("/api/readyz")({
  server: {
    handlers: {
      GET: () => new Response(state.ready ? "ready" : "not ready", { status: state.ready ? 200 : 503 }),
    },
  },
});

Notes

  • NITRO_SHUTDOWN_SIGNALS (default SIGINT SIGTERM) and NITRO_SHUTDOWN_DISABLED=true exist, but you should not need them.
  • Close database pools in a Nitro plugin’s close hook, or in your own listener before Nitro’s timeout fires.
  • Containerizing TanStack Start is its own recipe: Containerize a TanStack Start app with Nitro.

See it applied

Updated 2026-09-10 · tags: tanstack, nitro, signals, shutdown · edit on GitHub