brokeinprod
brokeinprod
Theme

Debugging

A Coming-Soon Gate in Next.js, and the Day Middleware Was Deprecated Out From Under It

A coming-soon gate in Next.js middleware, then Next 16 deprecated middleware and renamed it proxy.ts. The shipped pattern, the migration, and the codemod.

November 10, 2026

The gate was the simplest thing in the whole project: one file that hides the site until launch day. Then I upgraded to Next 16, and the simplest thing in the project started warning me that it was deprecated.

Start here: A coming-soon gate is a proxy.ts (formerly middleware.ts) that intercepts every request and, unless the site is flagged live or the request carries an owner bypass, sends it to a coming-soon page, while letting that page, static assets, and API routes through so you do not loop. In Next.js 16 the middleware file convention is deprecated and renamed to proxy: rename the file and the exported function (there is a codemod), keep your matcher, and note that proxy runs on the Node.js runtime, with no edge option. It still works as middleware with a warning; renamed, not removed. It is a gate, not auth. Do not guard secrets with it.

The setup

Before launch, the site needed to be invisible to the public but fully usable by me. The cleanest way to do that in Next.js is a single interceptor that runs before every route. If the site is not live yet, send visitors to a coming-soon page, let me through, and let the build keep working.

The pattern

The gate runs in one file and does four things: checks a flag, lets the owner through, exempts the gate page and assets so the redirect cannot loop, and sends everything else to the coming-soon page. The shipped file also does one more thing, which I come back to below: it checks for the admin session cookie on /admin routes.

 
 
// src/proxy.ts  (was middleware.ts before Next.js 16)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
// Coming Soon mode is controlled via environment variable.
// Set COMING_SOON_MODE=true in Vercel to enable the gate.
// Leave unset or set to anything else (including 'false') to keep the site live.
const COMING_SOON_MODE = process.env.COMING_SOON_MODE === 'true';
 
export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;
 
  // === ADMIN ROUTE PROTECTION ===
  // Protect /admin routes (except /admin/login)
  if (pathname.startsWith('/admin') && !pathname.startsWith('/admin/login')) {
    const session = request.cookies.get('admin_session');
 
    if (!session?.value) {
      const loginUrl = new URL('/admin/login', request.url);
      return NextResponse.redirect(loginUrl);
    }
 
    // Admin is authenticated, allow access (skip coming soon check)
    const response = NextResponse.next();
    response.headers.set('x-pathname', pathname);
    return response;
  }
 
  // Allow access to admin login page always
  if (pathname.startsWith('/admin/login')) {
    const response = NextResponse.next();
    response.headers.set('x-pathname', pathname);
    return response;
  }
 
  // === COMING SOON MODE ===
  // If Coming Soon mode is disabled, allow all requests
  if (!COMING_SOON_MODE) {
    const response = NextResponse.next();
    response.headers.set('x-pathname', pathname);
    return response;
  }
 
  // Allow access to the coming-soon page itself
  if (pathname === '/coming-soon') {
    const response = NextResponse.next();
    response.headers.set('x-pathname', pathname);
    return response;
  }
 
  // Allow access to static files and API routes
  if (
    pathname.startsWith('/_next') ||
    pathname.startsWith('/api') ||
    pathname.includes('.')
  ) {
    return NextResponse.next();
  }
 
  // Optional: Allow access with a secret preview parameter
  const previewSecret = request.nextUrl.searchParams.get('preview');
  if (previewSecret === '[redacted]') {   // a string literal in the shipped file; see below
    const response = NextResponse.next();
    response.headers.set('x-pathname', pathname);
    return response;
  }
 
  // Redirect all other requests to Coming Soon page
  return NextResponse.redirect(new URL('/coming-soon', request.url));
}
 
export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Three choices in that file are worth naming.

The flag is opt-in. COMING_SOON_MODE=true turns the gate on; anything else, including an unset variable, leaves the site live. Going live is one environment-variable change in Vercel, and a fresh deploy with the variable missing fails open to the real site rather than to the gate.

The exemptions come before the redirect, in order: the gate page itself, then anything under /_next, anything under /api, and any path containing a dot, which covers static files. Miss the first one and every visitor redirects to a page that redirects them again.

The bypass is a query parameter. A request carrying ?preview=<secret> passes through, so I could check the real site from any browser without a cookie. The secret shipped as a string literal in the file. It is now an environment variable, and that was the first edit this article caused.

I liked this pattern enough that it shipped on two builds, same four moves, different file names. When the simplest version of a thing survives contact with a second project, that is a good sign.

Then Next.js 16 deprecated middleware

After the upgrade to Next.js 16, the build log started warning that middleware is deprecated. The accurate version, because a lot of posts overstate it: the middleware file convention is deprecated and renamed to proxy. middleware.ts becomes proxy.ts, and the exported function middleware becomes proxy. It is not removed. The old file still works, with a warning. Next.js ships a codemod that does the rename:

 
 
# rename the file and the export automatically
npx @next/codemod@latest middleware-to-proxy .

Your config and matcher stay the same. The reason for the rename: "middleware" kept getting confused with Express.js middleware, and Next.js wants this layer treated as a last resort rather than a place to put logic.

The one real catch: runtime

proxy runs on the Node.js runtime, and that is not configurable. The edge runtime is not supported in proxy. If your interceptor needs edge, keep it as middleware for now; Next.js has said edge guidance will follow in a later release. For a coming-soon gate, Node is fine.

Why a coming-soon gate is the clean example

Most middleware-migration posts are tangled up in authentication, for good reason. CVE-2025-29927 was a middleware auth-bypass vulnerability, and it is a large part of why Next.js is pushing logic out of this layer toward a thin proxy: check lightly here, validate authoritatively in server components. A coming-soon gate sidesteps all of that, because it protects nothing. That is also the caveat to state plainly. A gate hides the site from casual visitors. It is not a security boundary. Anyone with the bypass value or a deep link to an API route is past it. Gate for presentation, authenticate for protection.

The shipped file does check for an admin session cookie on /admin routes, which looks like the thing the previous paragraph warns against. The difference is what the check does. It tests for the cookie's presence and redirects to the login page if it is missing. It does not validate the session. That validation happens where it belongs, in the admin pages themselves, so the proxy check is a courtesy redirect and nothing more.

What I'd do differently

Two things. Move the preview secret to an environment variable, which is a one-line change and should have been the first version. And consider a rewrite instead of a redirect for the gate: a rewrite keeps the visitor's URL and shows the coming-soon content in place, where a redirect sends everyone to /coming-soon in the address bar. The redirect has worked fine, so this is preference rather than a bug.


The gate was trivial. The lesson was not. "Middleware" is becoming a thin proxy on purpose, and a redirect at the front door is the one job that layer should keep.

FAQ

Is Next.js middleware removed? No. In Next.js 16 it is deprecated and renamed to proxy (middleware.ts to proxy.ts, function middleware to proxy, with a codemod). The old convention still works with a deprecation warning.

Can I still use the edge runtime? Not in proxy. It runs on Node.js, and that cannot be configured. If you need the edge runtime, keep using middleware for now; Next.js has said edge instructions will follow.

Should I put login or auth in proxy? No. CVE-2025-29927 is exactly why auth does not belong in this layer. Do a lightweight check at most, and validate authoritatively in server components. A coming-soon redirect is fine precisely because it guards nothing sensitive.

Rewrite or redirect for the gate? Rewrite keeps the visitor's URL and shows the coming-soon content in place. Redirect sends them to /coming-soon in the address bar. This build redirects. Either way, exempt the gate page, /_next, /api, and static files, or you will loop.

Further reading

Keep reading

Newsletter

New posts in your inbox. No spam — unsubscribe anytime.

Occasional emails when we publish something worth your time. Unsubscribe anytime.

More about what you'll get on the newsletter page.

THEME
↑↓ navigate↵ openesc close