Next.js — boot warmup
Lazy pools are right for development, but production cold starts shouldn't spawn workers and seed a million records inside the first request. Next.js's instrumentation.ts hook is the answer: register() runs once when the Node.js runtime boots, before the server accepts traffic.
register()
// src/instrumentation.ts — Next.js runs register() once at server boot
export async function register() {
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
const { getDigest } = await import('./app/api/atoll/pool');
const { getJobs } = await import('./app/api/jobs/pool');
const { getIncidentsApi } = await import('./app/api/incidents/pool');
getDigest();
getJobs();
// Kick the expensive part: 1M records seed on workers during startup,
// then a metrics recompute so the read-model endpoints are fully warm.
void getIncidentsApi().client.seedIncidents()
.then(() => getIncidentsApi().client.computeMetrics())
.catch(console.error);
}Three details matter: the NEXT_RUNTIME === 'nodejs' guard keeps it out of the edge runtime; the dynamic imports keep node:worker_threads out of bundle graphs that can't support it; and the seed is fired without await — boot doesn't block on it, it just starts early. Because the seed is dispatched at boot, read-model endpoints report live seedProgress during warmup and are fully warm before most traffic arrives.
Lifecycle
Pool identity lives on globalThis — in dev, module reloads re-run the getters but return the same pool instead of re-spawning workers on every HMR cycle. In production the pool outlives every request and dies with the process; for tests, terminate explicitly rather than leaking threads:
// Tests own their pool lifecycle — terminate so vitest can exit.
const holder = globalThis as { __atollJobs?: { pool: { terminate(): void } } };
afterAll(() => {
holder.__atollJobs?.pool.terminate();
delete holder.__atollJobs;
});pool.terminate() also exists for graceful shutdown in a custom server — see custom server for the production topology that owns the process lifecycle.
What to warm
Anything whose first-call cost matters: worker spawn (~tens of ms each), shared-buffer initialization, and seed/refresh dispatches. Warming is cheap insurance under load — a cold pool under a request burst spawns while queuing requests, compounding latency exactly when you can least afford it.