mirror of
https://github.com/payloadcms/payload.git
synced 2026-03-26 19:38:18 +00:00
Fixes https://github.com/payloadcms/payload/issues/8897, addresses https://github.com/payloadcms/payload/discussions/14460 Adds initial support for Next.js `cacheComponents` so users who enable it for their frontend don't get errors from the Payload admin panel. This PR addresses the obvious breakage but does not guarantee full compatibility - see the "Known Limitations" section below. When `cacheComponents` is enabled in `next.config`, Next.js throws "Data that blocks navigation was accessed outside of `<Suspense>`" errors because the admin layout reads cookies, headers, and does auth queries at the top level. This prevents users from enabling `cacheComponents` at all if Payload is in the same Next.js app. The fix has two parts. First, `withPayload` now detects `cacheComponents` in the Next.js config and sets a `PAYLOAD_CACHE_COMPONENTS_ENABLED` env var. Second, `RootLayout` reads that env var and conditionally wraps its content in `<Suspense fallback={null}>` above the `<html>` tag, which suppresses the errors. When `cacheComponents` is not enabled, the Suspense is not used at all and behavior is identical to before. ## Known Limitations These are all caused by Next.js's `cacheComponents` and likely cannot be fixed from our side. ### Page flash on hard refresh When `cacheComponents` is enabled, hard refresh shows a brief gray flash before the admin panel appears. Without `cacheComponents` there is no flash. There is no per-route opt-out for this behavior. Related issue: https://github.com/vercel/next.js/issues/86739 ### HTTP status codes (404 returns 200) With `cacheComponents`, `notFound()` returns HTTP 200 instead of 404. This happens because the Suspense boundary above `<html>` causes Next.js to commit response headers (with status 200) before `notFound()` runs inside the suspended content. The not-found UI still renders correctly - only the HTTP status code is wrong. This is a [documented Next.js streaming limitation](https://nextjs.org/docs/app/api-reference/file-conventions/loading#status-codes). ### DOM accumulation breaks Playwright selectors When `cacheComponents` is enabled, Next.js wraps route segments in React's `<Activity>` component, keeping up to 3 previously visited pages in the DOM with `display: none !important` instead of unmounting them. This means Playwright selectors like `page.locator('#field-title')` resolve to multiple elements (the visible one and hidden copies from cached pages), causing strict mode violations. This is a [known issue](https://github.com/vercel/next.js/issues/86577) affecting all Next.js apps using `cacheComponents` with Playwright. Because of this, we cannot reliably run our e2e test suite with `cacheComponents` enabled. Adapting the test suite would require rewriting a large number of selectors across hundreds of tests - most of our e2e tests use `page.locator()` with ID selectors, which would all break when Activity duplicates the DOM. Until the Next.js team provides a per-route opt-out for Activity (which they are [actively exploring](https://github.com/vercel/next.js/issues/86577#issuecomment-3801284197)), we cannot _guarantee_ full admin panel compatibility beyond the initial error suppression this PR provides.