ERR_HTTP_HEADERS_SENT and __next_metadata_boundary__ hydration mismatch when mixing "use cache" with draftMode()/cookies() in same page component
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/lucianobfs/nextjs-use-cache-metadata-boundary-repro
To Reproduce
- Clone the repo and run:
pnpm install pnpm build pnpm start - Visit
http://localhost:3000/posts/1 - The page renders correctly (HTTP 200), but check the server terminal for errors
The pattern that triggers the bug (app/posts/[slug]/page.tsx):
generateMetadata()callsawait connection()— required because the underlying library usescrypto.randomBytes()(e.g., Effect-TS, Prisma)- The page component calls a
"use cache"function first (cachedPostData) - Then accesses
draftMode()andsearchParams— dynamic APIs called outside the cache scope, as the docs recommend
Current vs. Expected behavior
Current:
The page renders correctly (HTTP 200), but the server logs contain two recurring errors:
Error A — Metadata boundary mismatch (most frequent):
```
Error: Expected the resume to render
The tree doesn't match so React will fallback to client rendering.
{ digest: '2161942505' }
```
Error B — ERR_HTTP_HEADERS_SENT (intermittent):
```
Error: Cannot set headers after they are sent to the client
at g.setHeader (.next/server/chunks/ssr/[root-of-the-server]__*.js)
{ code: 'ERR_HTTP_HEADERS_SENT', digest: '1390897066' }
```
Expected:
No server-side errors. The cached portion and the dynamic portion should coexist without hydration mismatches or header conflicts.
Impact
Although pages return HTTP 200, these errors cause:
- Performance degradation: React falls back to full client rendering, discarding streamed SSR output
- Wasted server resources: Full SSR work gets thrown away
- SEO risk: Crawlers may receive incomplete HTML before client JS executes
- Log noise: ~20-30 errors/hour in production with moderate traffic
Observed patterns from production (www.konsi.com.br)
| Error | Frequency | Cache status | Routes |
|---|---|---|---|
__next_metadata_boundary__ mismatch |
~20/3h | STALE and HIT | Pages with generateMetadata + connection() + "use cache" data fn |
ERR_HTTP_HEADERS_SENT |
~2-12/hr (varies) | HIT and PRERENDER | Pages calling draftMode() or cookies() after "use cache" fn |
Key observations:
- Error A happens on both
cache: STALE(during revalidation) andcache: HIT(serving from cache) - Error B happens when the page accesses
cookies()/draftMode()after a"use cache"call, on cached or prerendered responses - The
connection()call ingenerateMetadatais required because the underlying data library (Effect-TS) usescrypto.randomBytes()internally, which is disallowed during prerendering - Both errors come from
source: serverless-middlewareorsource: serverless— framework-level, not application code
Analysis
The root cause appears to be a conflict in how the streaming/resumption protocol handles the transition from a "use cache" boundary to dynamic request APIs within the same server component:
generateMetadataruns withconnection()(making it dynamic) and produces metadata- The page component calls the
"use cache"function, which fills/serves from cache and begins streaming - After the cache boundary, the page calls
draftMode()orcookies(), which attempts to read/set headers - The metadata boundary marker (
<__next_metadata_boundary__>) gets injected at a position that doesn't match what React expects during hydration replay — the cache boundary and metadata boundary interleave in a way that changes the DOM structure
The ERR_HTTP_HEADERS_SENT variant occurs when draftMode()/cookies() tries to set response headers after the cached response has already started streaming.
Provide environment information
```
Operating System:
Platform: linux (Vercel serverless) / macOS (local)
Arch: x64
Binaries:
Node: 24.x
pnpm: 9.15.9
Relevant Packages:
next: 16.2.1
react: 19.1.2
react-dom: 19.1.2
```
Which area(s) are affected?
- App Router: Components, Layouts, Pages
- App Router: Data Fetching (use cache, cacheLife, cacheTag)
- App Router: Metadata (generateMetadata)
Additional context
Workarounds attempted:
- Moving
draftMode()before the"use cache"call — breaks prerendering becausedraftMode()uses crypto internally - Wrapping the dynamic section in
<Suspense>— does not prevent the header conflict - Removing
connection()fromgenerateMetadata— causes prerender errors due to library internals usingcrypto.randomBytes()
The recommended pattern from the docs (call "use cache" functions, then access dynamic APIs) is exactly what triggers this bug. The use cache docs state:
Cached functions and components cannot directly access runtime APIs like cookies(), headers(), or searchParams. Instead, read these values outside the cached scope and pass them as arguments.
We follow this guidance — dynamic APIs are called outside the "use cache" function — but the combination still produces errors when both exist in the same page component.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Clone the linked reproduction, inspect app/posts/[slug]/page.tsx, and run pnpm install, pnpm build, and pnpm start. Visit /posts/1 and inspect the server terminal; done means the page still renders and the metadata-boundary and ERR_HTTP_HEADERS_SENT errors no longer appear.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nextjs, node.js, react
- Domain
- backend, frontend, full-stack, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100