Proxy headers

The two directions are easy to confuse. NextResponse.next({ request: { headers } }) makes headers available upstream to the render. NextResponse.next({ headers }) sends them downstream to the client.

You cannot modify request.headers in place. Clone it into a new Headers object first.

What to check

Request side (what the render sees) is the panel below. It is a dynamic hole because headers() is a runtime API, which a cached scope cannot read.

Response side (what the client sees) needs curl, since the page cannot read its own response headers:

curl -sI localhost:3000/proxy/headers | grep -i x-proxy

Correlate them. The x-proxy-invocation response header and the x-from-proxy-invocation request header are set to the same id in one invocation, so they should match for a given request. A match proves both directions came from the same proxy call.

Keep them small. Large headers can trigger 431 Request Header Fields Too Large depending on the upstream server.

Injected request headers

What the render received

suspense fallback

streaming at request time. This is what ships in the static shell.

Reference

headerdirectionhow to read it
x-from-proxy-invocationrequest to renderheaders() in a dynamic scope
x-from-proxy-georequest to renderheaders() in a dynamic scope
x-proxy-invocationresponse to clientcurl -sI, or devtools
x-proxy-branchresponse to clientcurl -sI, or devtools
x-proxy-prefetchresponse to clientcurl -sI with a prefetch header
x-proxy-pathresponse to clientcurl -sI (pre-rewrite pathname)