Link to the code that reproduces this issue
https://github.com/mdotk/next-ppr-metadata-repro
To Reproduce
Clone the repo and run:
npm install
npm run build
npm run start
In another terminal:
Or manually request the PPR route with different user agents:
curl -sS \
-A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/537.36 Chrome/124 Safari/537.36' \
-o /tmp/browser.html \
http://127.0.0.1:3099/posts/alpha
curl -sS \
-A 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Safari/537.36' \
-o /tmp/googlebot.html \
http://127.0.0.1:3099/posts/alpha
The app uses:
const nextConfig = {
cacheComponents: true,
htmlLimitedBots: /.*/,
}
The route is a PPR route in the production build:
└ ◐ /posts/[slug]
├ /posts/[slug]
└ /posts/alpha
Current vs. Expected behavior
Current behavior:
htmlLimitedBots: /.*/ should fully disable streaming metadata per the docs.
- Browser-like UA returns 200, but the initial head does not contain the route metadata and the response contains the hidden streaming metadata wrapper.
- Googlebot/Bingbot return 200 with route metadata in the initial head.
- Empty/no-UA requests also stream metadata outside the initial head.
- The server logs a React resume mismatch:
Error: Expected the resume to render <div> in this slot but instead it rendered <__next_metadata_boundary__>. The tree doesn't match so React will fallback to client rendering.
digest: '2491006542'
Expected behavior:
With htmlLimitedBots: /.*/, the prerender/no-UA path and runtime paths should use a consistent metadata component tree shape, or otherwise avoid a PPR resume mismatch. A documented "fully disable streaming metadata" config should not still allow the PPR shell to be generated with a streaming metadata wrapper.
What appears to be happening
In Next 16.2.3, shouldServeStreamingMetadata(userAgent, ".*") returns false for a non-empty browser UA, Googlebot, and Bingbot. But the app-page template still has a no-UA branch that forces streaming metadata:
const serveStreamingMetadata =
botType && isRoutePPREnabled
? false
: !userAgent
? true
: shouldServeStreamingMetadata(userAgent, nextConfig.htmlLimitedBots)
That means the PPR prerender/export shell can be built with the streaming metadata tree shape, while runtime requests with non-empty UAs can render the blocking metadata tree shape. The metadata component tree changes from a hidden <div> wrapper to direct <__next_metadata_boundary__>, which matches the resume mismatch.
This is in the same problem area as:
#92087
And this closed PR appears directly relevant:
#90259
Provide environment information
Next.js: 16.2.3
React: 19.1.2
React DOM: 19.1.2
Node.js tested locally: 23.3.0
Runtime: next build + next start
App Router: yes
Cache Components: enabled
PPR: enabled through cacheComponents
htmlLimitedBots: /.*/
Which area(s) are affected? (Select all that apply)
App Router, Metadata, Partial Prerendering, Runtime
Which stage(s) are affected? (Select all that apply)
next build, next start
Additional context
The minimal reproduction digest is 2491006542. The important signal is the metadata tree-shape mismatch: expected <div>, got <__next_metadata_boundary__>.
Link to the code that reproduces this issue
https://github.com/mdotk/next-ppr-metadata-repro
To Reproduce
Clone the repo and run:
In another terminal:
Or manually request the PPR route with different user agents:
The app uses:
The route is a PPR route in the production build:
Current vs. Expected behavior
Current behavior:
htmlLimitedBots: /.*/should fully disable streaming metadata per the docs.Expected behavior:
With
htmlLimitedBots: /.*/, the prerender/no-UA path and runtime paths should use a consistent metadata component tree shape, or otherwise avoid a PPR resume mismatch. A documented "fully disable streaming metadata" config should not still allow the PPR shell to be generated with a streaming metadata wrapper.What appears to be happening
In Next 16.2.3,
shouldServeStreamingMetadata(userAgent, ".*")returnsfalsefor a non-empty browser UA, Googlebot, and Bingbot. But the app-page template still has a no-UA branch that forces streaming metadata:That means the PPR prerender/export shell can be built with the streaming metadata tree shape, while runtime requests with non-empty UAs can render the blocking metadata tree shape. The metadata component tree changes from a hidden
<div>wrapper to direct<__next_metadata_boundary__>, which matches the resume mismatch.This is in the same problem area as:
#92087
And this closed PR appears directly relevant:
#90259
Provide environment information
Next.js: 16.2.3 React: 19.1.2 React DOM: 19.1.2 Node.js tested locally: 23.3.0 Runtime: next build + next start App Router: yes Cache Components: enabled PPR: enabled through cacheComponents htmlLimitedBots: /.*/Which area(s) are affected? (Select all that apply)
App Router, Metadata, Partial Prerendering, Runtime
Which stage(s) are affected? (Select all that apply)
next build, next start
Additional context
The minimal reproduction digest is
2491006542. The important signal is the metadata tree-shape mismatch: expected<div>, got<__next_metadata_boundary__>.