-
Notifications
You must be signed in to change notification settings - Fork 30.5k
Description
Link to the code that reproduces this issue
https://github.com/bbrouse/nextjs-content-length-repro
To Reproduce
The (admittedly vibe coded) reproduction tests can be run as follows:
- Clone https://github.com/bbrouse/nextjs-content-length-repro
cd after && npm install && npx next build && npx next start -p 4200curl -sD - -o /dev/null "http://localhost:4200/_next/data/$(cat .next/BUILD_ID)/index.json"- Observe Transfer-Encoding: chunked with no Content-Length or ETag
- Repeat with the before/ directory (v15.4.0) -- Content-Length and ETag are present
- Repeat with after-patched/ directory (v15.4.1 patched) -- Content-Length and ETag are restored
Current vs. Expected behavior
We run a self-hosted Next.js Pages Router application on AWS ECS behind cloudfront. We leverage cloudfronts ability to compress content on the fly, so next.js compression is disabled.
After upgrading from v15.4.0 to v15.4.1, we noticed that /_next/data/ JSON responses (from getStaticProps pages with revalidate) were no longer gzipped by cloudfront (the initial indicator being a noticeable increase in data transfer over cloudfront).
The cause: these responses no longer include a Content-Length header. Instead they are streams that send Transfer-Encoding: chunked. CloudFront requires Content-Length to compress origin responses, so the JSON payloads are now served uncompressed.
Expected: /_next/data/ responses for cached/ISR pages include Content-Length and ETag, matching the behavior in v15.4.0 and earlier.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 23.5.0: Wed May 1 20:13:18 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6030
Available memory (MB): 36864
Available CPU cores: 12
Binaries:
Node: 24.13.0
npm: 6.14.18
Yarn: N/A
pnpm: N/A
Relevant Packages:
next: 15.3.9
eslint-config-next: 15.5.2
react: 18.3.1
react-dom: 18.3.1
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
Pages Router, Connection, Headers
Which stage(s) are affected? (Select all that apply)
Other (Deployed), next start (local)
Additional context
Debugging on our end:
We (and by we I mean me and my helpful robot friends) traced this to PR #80189 ("Add response handling inside handlers"), which moved response handling into the Pages route handler template. The new code in packages/next/src/build/templates/pages.ts constructs the data response as:
new RenderResult(Buffer.from(JSON.stringify(result.value.pageData)), { ... })
The previous code in base-server.ts used:
RenderResult.fromStatic(JSON.stringify(cachedData.pageData))
The difference is that Buffer.from() is not a string, which makes RenderResult.isDynamic return true (it checks typeof this.response !== 'string'). This causes sendRenderResult to skip Content-Length and ETag generation and pipe the response instead.
We verified this locally by patching the ESM template to remove Buffer.from():
- new RenderResult(Buffer.from(JSON.stringify(result.value.pageData)), {
+ new RenderResult(JSON.stringify(result.value.pageData), {
After rebuilding, Content-Length and ETag are restored. The after-patched/ directory in the reproduction repo automates this test.
Additional context of our application:
We run a self-hosted Next.js Pages Router site on AWS ECS behind CloudFront. Our Docker image is based on node:24-alpine3.23 and we use a custom Express server with compress: false, since CloudFront handles compression at the edge and we're happy to offload those cycles off the containers to cloudfront.