nextjs - 💡(How to fix) Fix PPR resume mismatch on RSC prefetch with htmlLimitedBots renders __next_metadata_boundary__ [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#93370Fetched 2026-04-30 06:17:35
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×3closed ×1commented ×1locked ×1

Error Message

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: '3219170805'

Fix Action

Fix / Workaround

Reference workaround guard script: https://gist.github.com/mdotk/3c05d464194466800a8ec56df817dd7d

The production workaround was two-layered:

Code Example

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
  htmlLimitedBots: /.*/,
  experimental: {
    ppr: true,
  },
};

export default nextConfig;

---

curl -i \
  -H 'RSC: 1' \
  -H 'Next-Router-Prefetch: 1' \
  -H 'Accept: text/x-component' \
  https://example.com/some-public-route

---

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: '3219170805'

---

Next.js: 16.2.3
React: version bundled by Next 16.2.3
Node.js: 20.20.2
Runtime: self-hosted next start
App Router: yes
Cache Components: enabled
PPR: enabled
htmlLimitedBots: /.*/

---

const link = document.createElement("link");
link.rel = "prefetch";
link.as = "document";
link.href = href;
link.dataset.gtaboomDocumentPrefetch = "true";
document.head.appendChild(link);

---

function isAppRouterRscPrefetchRequest(request: NextRequest) {
  return (
    request.headers.get("rsc") === "1" &&
    request.headers.get("next-router-prefetch") === "1"
  );
}

if (!isAdminPath && isAppRouterRscPrefetchRequest(request)) {
  return new Response(null, {
    status: 204,
    headers: {
      "Cache-Control": "no-store",
      "CDN-Cache-Control": "no-store",
      "X-GTA-Boom-Prefetch": "rsc-disabled",
    },
  });
}
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

I do not have a small public repo ready yet. This was reproduced in a production Next.js 16.2.3 App Router application, and the minimal request/config shape is below.

Reference workaround guard script: https://gist.github.com/mdotk/3c05d464194466800a8ec56df817dd7d

To Reproduce

Use an App Router application with Cache Components / PPR enabled and metadata streaming disabled globally with htmlLimitedBots: /.*/.

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
  htmlLimitedBots: /.*/,
  experimental: {
    ppr: true,
  },
};

export default nextConfig;

Create any public PPR route that renders metadata and contains a client-side path that triggers App Router prefetch, for example router.prefetch("/some-public-route") from a client component after hover/focus.

Then request the same public route through the RSC prefetch request shape:

curl -i \
  -H 'RSC: 1' \
  -H 'Next-Router-Prefetch: 1' \
  -H 'Accept: text/x-component' \
  https://example.com/some-public-route

Normal document requests to the same URL return clean HTML and complete metadata in <head>. Bot-like document requests also return clean HTML when htmlLimitedBots: /.*/ is set.

Current vs. Expected behavior

Current behavior:

The server logs a React resume mismatch on the RSC prefetch path:

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: '3219170805'

Expected behavior:

The RSC prefetch path should not render a different metadata subtree than the route's prerender/resume expectation. With htmlLimitedBots: /.*/, metadata rendering should be invariant for this route, or the prefetch response should avoid producing a resume tree that collides with __next_metadata_boundary__.

Provide environment information

Next.js: 16.2.3
React: version bundled by Next 16.2.3
Node.js: 20.20.2
Runtime: self-hosted next start
App Router: yes
Cache Components: enabled
PPR: enabled
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 start (local), Other self-hosted production runtime

Additional context

This was initially suspected to be a route-level Cache Components boundary problem, but the error reproduced only on the App Router RSC prefetch request shape. Normal browser HTML, Googlebot HTML, and Facebook external hit HTML were clean.

The production workaround was two-layered:

  1. Stop using router.prefetch() on public content links and use document prefetch instead:
const link = document.createElement("link");
link.rel = "prefetch";
link.as = "document";
link.href = href;
link.dataset.gtaboomDocumentPrefetch = "true";
document.head.appendChild(link);
  1. Add a proxy/middleware safety valve for public RSC prefetch requests:
function isAppRouterRscPrefetchRequest(request: NextRequest) {
  return (
    request.headers.get("rsc") === "1" &&
    request.headers.get("next-router-prefetch") === "1"
  );
}

if (!isAdminPath && isAppRouterRscPrefetchRequest(request)) {
  return new Response(null, {
    status: 204,
    headers: {
      "Cache-Control": "no-store",
      "CDN-Cache-Control": "no-store",
      "X-GTA-Boom-Prefetch": "rsc-disabled",
    },
  });
}

We also added a guard script in our app to prevent public router.prefetch() from being reintroduced while this class of issue exists. The guard scans public source for router.prefetch( and fails pre-deploy if found: https://gist.github.com/mdotk/3c05d464194466800a8ec56df817dd7d

extent analysis

TL;DR

To fix the React resume mismatch issue on App Router RSC prefetch requests, consider disabling router.prefetch() on public content links and using document prefetch instead, along with adding a proxy/middleware safety valve for public RSC prefetch requests.

Guidance

  • Identify and replace all instances of router.prefetch() on public content links with document prefetch to prevent metadata subtree mismatches.
  • Implement a proxy/middleware safety valve to detect and handle App Router RSC prefetch requests, returning a 204 response with appropriate cache control headers.
  • Use a guard script to prevent public router.prefetch() from being reintroduced in the codebase.
  • Verify that the fix works by testing RSC prefetch requests and checking for the absence of React resume mismatch errors.

Example

// Replace router.prefetch() with document prefetch
const link = document.createElement("link");
link.rel = "prefetch";
link.as = "document";
link.href = href;
link.dataset.gtaboomDocumentPrefetch = "true";
document.head.appendChild(link);

// Proxy/middleware safety valve example
function isAppRouterRscPrefetchRequest(request: NextRequest) {
  return (
    request.headers.get("rsc") === "1" &&
    request.headers.get("next-router-prefetch") === "1"
  );
}

if (!isAdminPath && isAppRouterRscPrefetchRequest(request)) {
  return new Response(null, {
    status: 204,
    headers: {
      "Cache-Control": "no-store",
      "CDN-Cache-Control": "no-store",
      "X-GTA-Boom-Prefetch": "rsc-disabled",
    },
  });
}

Notes

This fix assumes that the issue is specific to App Router RSC prefetch requests and that disabling router.prefetch() on public content links is a viable workaround. Additional testing and verification may be necessary to ensure that this fix does not introduce

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING