Zero-Downtime Deployments with Next.js: Rolling Releases, Asset Skew & Health Probes
Architectural blueprint for shipping continuous updates to production Next.js apps without dropped HTTP connections, 404 chunk mismatch errors, or database migration outages.

1. The Chunk Mismatch Phenomenon (Asset Skew)
When you run `next build`, Next.js splits your JavaScript bundles into hashed chunk files like `_next/static/chunks/main-a1b2c3d4.js`. When a new deployment rolls out, new hashes are generated (e.g., `main-e5f6g7h8.js`).
If a user loaded the previous HTML page five minutes before the rollout and clicks a link right after the deployment completes, their browser requests the old chunk file. If your container stopped serving old static assets, the user hits a `ChunkLoadError: Loading chunk failed (404)`, breaking the interactive SPA experience.
Solving this requires decoupling static asset delivery from application server lifetimes.
2. Decoupling Static Bundles to Object Storage (CDN)
The robust industry solution is setting `assetPrefix` in `next.config.ts` and synchronizing the `.next/static` build directory to a persistent object storage bucket (e.g., Cloudflare R2, AWS S3) served through an immutable CDN cache.
Because old hashed files are never deleted immediately during rollouts, visitors running older client sessions continue fetching historical chunks without disruption for hours or days.
import type { NextConfig } from "next";
const isProd = process.env.NODE_ENV === "production";
const CDN_URL = process.env.NEXT_PUBLIC_CDN_URL || "";
const nextConfig: NextConfig = {
output: "standalone",
assetPrefix: isProd && CDN_URL ? CDN_URL : undefined,
poweredByHeader: false,
compress: true,
};
export default nextConfig;3. Crafting Uncompromising Health Check Probes
In rolling container environments (Kubernetes, AWS ECS, Docker Swarm), orchestrators rely on two distinct probes to govern pod lifetimes:
• Readiness Probe: Checks whether the node process has initialized its database connection pool, warmed in-memory caches, and is prepared to receive traffic. Traffic is only routed once this probe returns HTTP 200.
• Liveness Probe: Checks if the event loop is responsive. If a deadlock occurs, the orchestrator terminates and replaces the container instance.
import { NextResponse } from "next/server";
import { db } from "@/lib/db/client";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const type = searchParams.get("type");
// Shallow liveness check
if (type === "liveness") {
return NextResponse.json({ status: "alive" }, { status: 200 });
}
// Deep readiness check including database ping
try {
await db.$queryRaw`SELECT 1`;
return NextResponse.json(
{ status: "ready", timestamp: new Date().toISOString() },
{ status: 200 }
);
} catch (err) {
return NextResponse.json(
{ status: "unhealthy", reason: "Database unreachable" },
{ status: 503 }
);
}
}4. The Expand-and-Contract Migration Pattern
Never execute breaking database migrations (such as dropping or renaming columns) in the same deployment wave as your code update. Always apply changes in three phases:
1. Expand: Add the new column as optional or nullable while old code continues writing to the legacy schema.
2. Migrate & Mirror: Deploy updated code that reads from the new column and dual-writes to both fields.
3. Contract: Once all services run the new code, safely drop the deprecated column.