Good catch on the second artifact. Three fixes total:
"the an ecommerce business Lead Importer"→"an ecommerce business's Lead Importer""In an ecommerce business I had to use Web Crypto"→"In that earlier build, I had to use Web Crypto"Building `fld-docs` — a client-facing SOP canvas...→"Building a client-facing SOP canvas..."
TLDR: Next 16 killed
middleware.ts. The replacement isproxy.ts— and it needs to live beside yourapp/folder, not at the project root. I learned this viaMODULE_UNPARSABLEat 11pm.
The Pattern I Reach For
Every internal tool I build needs some kind of auth gate.
But not every internal tool needs a full login system — user tables, JWTs, refresh tokens, the whole production.
For single-user or shared-password tools, I use a stateless HMAC-signed cookie instead.
The idea: type the right password → server signs a cookie with a secret key → every subsequent request just verifies that signature.
No database. No session store. No user table. The cookie IS the proof.
I first shipped this in an ecommerce business's Lead Importer (an internal Next.js 15 app for a healthcare client) and it's been my default for small internal tools ever since. Simple, fast, zero infrastructure.
The Wall
Building a client-facing SOP canvas with a RAG (retrieval-augmented generation, basically a smart search layer) chat panel on top.
Needed the gate. Pulled in the pattern. Wired up middleware.ts exactly like I'd done before.
Deployed. Got back a cold MODULE_UNPARSABLE error.
…what?
What Actually Changed in Next 16
Next 16 quietly deprecated middleware.ts.
The replacement is proxy.ts — same concept, different name, different rules.
Apollo (my AI coding agent) placed it at the project root, because that's where middleware.ts used to live.
That is NOT where it goes.
proxy.ts must live at the same level as your app/ directory. If your project uses src/app/, the file goes at src/proxy.ts — not ./proxy.ts at the repo root.
Moved it. Immediately worked.
The Other Gotcha: The Runtime Actually Changed
This one changes the code you write, not just where a file lives.
In Next 15, middleware.ts runs on the Edge runtime — which means no node:crypto. In that earlier build I had to use Web Crypto (crypto.subtle.importKey, crypto.subtle.sign) to do the HMAC signing. It works, but it's ceremonial.
Next 16's proxy.ts runs on Node only. Edge runtime is not supported.
Which means this just… works now:
import { createHmac, timingSafeEqual } from 'node:crypto'
ZERO ceremony. Honestly kind of a relief.
The Four-Line Migration
If you're moving an HMAC auth gate from Next 15 to Next 16:
- Rename
middleware.ts→proxy.ts - Move it to
src/proxy.ts(besidesrc/app/, not project root) - Swap Web Crypto calls for
node:crypto—createHmac,timingSafeEqual - Rename any config flags:
skipMiddlewareUrlNormalize→skipProxyUrlNormalize
Named export or default both work: export function proxy(request: NextRequest).
Why I Keep Reaching for This Pattern
No database migration for a forgotten password. No session table. No token refresh endpoint.
A secret, a signature, a cookie. Done.
The Next 16 rename is a papercut — but it's the kind of thing that costs you 45 minutes if nobody told you, and thirty seconds if someone did.
Now you've been told.