feat(next): prevent admin panel errors when cacheComponents is enabled#16020
Conversation
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
|
vercel/next.js#91662 probably related to failures. When a collection item contained a relationship table, the server function that loads the relationship returns text/html instead of text/x-component. 16.2.1.canary-3 works though. |
| apiRoute, | ||
| path: `${docEndpoint}?${params}`, | ||
| serverURL: serverURL || window.location.origin, | ||
| serverURL: origin, |
There was a problem hiding this comment.
Accessing window.* here breaks with cache components enabled:
Error: Switched to client rendering because the server rendering errored:
window is not defined
at .next/dev/server/chunks/ssr/0tmr_@payloadcms_next_dist_04sey_p._.js:1010
1008 | apiRoute,
1009 | path: `${docEndpoint}?${params}`,
> 1010 | serverURL: serverURL || window.location.origin
| ^
1011 | });
themavik
left a comment
There was a problem hiding this comment.
playwright.config now loads repo-root .env after test.env; duplicate keys in .env will override test.env and can make local E2E differ from CI in non-obvious ways.
|
🚀 This is included in version v3.81.0 |
payloadcms#16020) Fixes payloadcms#8897, addresses payloadcms#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: vercel/next.js#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](vercel/next.js#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](vercel/next.js#86577 (comment))), we cannot _guarantee_ full admin panel compatibility beyond the initial error suppression this PR provides.
|
@AlessioGr should the note here be updated? https://payloadcms.com/docs/getting-started/installation
|
|
@thernstig I think the note is still accurate. What would you update it to? |
|
@AlessioGr nevermind, I thought somehow this fixed compability as well. |
Fixes #8897, partially addresses cache component support (#14460)
Adds initial support for Next.js
cacheComponentsso 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
cacheComponentsis enabled innext.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 enablingcacheComponentsat all if Payload is in the same Next.js app.The fix has two parts. First,
withPayloadnow detectscacheComponentsin the Next.js config and sets aPAYLOAD_CACHE_COMPONENTS_ENABLEDenv var. Second,RootLayoutreads that env var and conditionally wraps its content in<Suspense fallback={null}>above the<html>tag, which suppresses the errors. WhencacheComponentsis not enabled, the Suspense is not used at all and behavior is identical to before.Known Limitations
Page flash on hard refresh
When
cacheComponentsis enabled, hard refresh shows a brief gray flash before the admin panel appears. WithoutcacheComponentsthere is no flash. There is no per-route opt-out for this behavior. Related issue: vercel/next.js#86739HTTP 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) beforenotFound()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.DOM accumulation breaks Playwright selectors
When
cacheComponentsis enabled, Next.js wraps route segments in React's<Activity>component, keeping up to 3 previously visited pages in the DOM withdisplay: none !importantinstead of unmounting them. This means Playwright selectors likepage.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 affecting all Next.js apps usingcacheComponentswith Playwright.Because of this, we cannot reliably run our e2e test suite with
cacheComponentsenabled. Adapting the test suite would require rewriting a large number of selectors across hundreds of tests - most of our e2e tests usepage.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), we cannot guarantee full admin panel compatibility beyond the initial error suppression this PR provides.