payloadcms / payloadcms/payload
[BUG] Admin renders blank for all unauthenticated routes on Next.js 16 (login, forgot, create-first-user, logout)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Description
On Next.js 16, every unauthenticated admin route renders a blank page. The response is 200 OK, the server components all execute successfully, and the RSC flight payload contains the complete UI — but none of it reaches the DOM.
Authenticated routes are unaffected: with a valid payload-token cookie the dashboard renders in full. This makes the bug invisible until a session expires, at which point the admin becomes unreachable through the UI because the login page itself is the blank one.
Downgrading only Next.js — same code, same database, same Payload version — fixes it.
Environment
- Payload: 3.85.1 (also reproduced on 3.86.0)
- Next.js: 16.3.0-canary.46 and 16.2.12 (both fail); 15.4.11 works
- React: 19.2.4
- Database:
@payloadcms/db-postgres, PostgreSQL 18 - Node: 24.16.0
- Platform: Linux (WSL2), plain
next devandnext build— no serverless/edge runtime
@payloadcms/next declares next: ">=16.2.6 <17.0.0" as a supported peer range, so 16.2.12 is inside the advertised range.
Version matrix
| Next.js | Payload | Anonymous /admin/login |
|---|---|---|
| 15.4.11 | 3.85.1 | ✅ renders (2 inputs, 2 forms) |
| 16.2.12 | 3.85.1 | ❌ blank |
| 16.3.0-canary.46 | 3.85.1 | ❌ blank |
| 16.3.0-canary.46 | 3.86.0 | ❌ blank |
Reproduction
- Payload 3.85.1 on Next.js 16.2.12 or later, standard
(payload)route group. - Ensure at least one user exists.
- Request
/admin/loginwithout a session cookie.
Result: 200 OK with an empty body.
Every public admin route behaves identically:
/admin/login -> 200, body (scripts stripped) = 233 bytes
/admin/forgot -> 200, 233 bytes
/admin/create-first-user -> 200, 233 bytes
/admin/logout -> 200, 233 bytes
The entire rendered body:
<body>
<div hidden=""><!--$--><!--/$--></div>
<style></style>
<section aria-label="Notifications alt+T" ...></section>
<div id="portal"></div>
</body>
RootLayout renders (#portal and the toaster are present), but <ProgressBar> and the page content — both inside RootProvider — are missing.
The server side is fine
I instrumented @payloadcms/next in node_modules to find where the tree is lost. Every server component executes:
[PROBE] route=/admin/login templateType=minimal viewType=login
hasComponent=true canAccessAdmin=undefined user=false
[PROBE] reached return; templateType=minimal RenderedView=object
[PROBE] MinimalTemplate rendering, hasChildren=true
[PROBE] LoginView rendering, user=false
RootPage reaches its return with a valid RenderedView, MinimalTemplate receives children, and LoginView renders. No errors are logged server-side, and the browser console is clean — no pageerror, no failed requests.
The RSC flight payload embedded in the HTML contains the full UI: Payload's logo SVG, the template-minimal class, and the client reference
I["...@payloadcms/next/dist/views/Login/LoginForm/index.js", [...], "LoginForm", 1]
So the content is serialised and shipped, then discarded during the HTML rendering pass.
Suspense boundary state differs between Next versions
This looks like the crux. Same page, same code:
<!-- Next 15.4.11 — boundary pending, content streams in -->
<div hidden=""><!--$?--><template id="B:0"></template><!--/$--></div>
<!-- Next 16.x — boundary completed, empty -->
<div hidden=""><!--$--><!--/$--></div>
Under Next 16 the boundary resolves with nothing in it rather than streaming the content.
Ruled out
Each of these was tested and eliminated:
- Canary vs stable within Next 16 (16.2.12 fails identically)
- Payload 3.85.1 → 3.86.0
transpilePackagesincluding@payloadcms/next/@payloadcms/ui- Custom
admin.components.providers(removed entirely; still blank) - A pass-through root
app/layout.tsx(removed; still blank) - Custom auth strategies — none used;
authis justmaxLoginAttempts/lockTime(so #8979 does not apply) - Custom
admin.route(default/admin, so #7401 does not apply) next/navigationredirect()— a minimal test page returns a correct307withLocation- Middleware (removed from the project; still blank)
- Duplicate React copies (single
react@19.2.4) PAYLOAD_CACHE_COMPONENTS_ENABLED(unset)- Database state (reproduced on both an empty database and a fully populated one)
- Import map completeness (regenerated; still blank)
Workaround
Authenticate through the REST API and set the cookie manually:
curl -i -X POST https://<host>/api/users/login \
-H 'content-type: application/json' \
-d '{"email":"...","password":"..."}'
Then set the returned payload-token cookie in the browser and load /admin. The dashboard renders normally — only the logged-out views are affected.
Possibly related
#15712 reports blank edit views with the same empty-Suspense-boundary signature, though under @opennextjs/cloudflare on Next 15.4.11 rather than Next 16 on Node. The shared marker suggests a common RSC/Suspense root cause with different triggers.
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
Start by reproducing the anonymous /admin/login case across the listed Next.js versions, then inspect @payloadcms/next around RootPage, MinimalTemplate, LoginView, and the LoginForm client reference. Compare the Suspense boundary and RSC output with the working Next 15 case. Done means all listed unauthenticated admin routes render their UI on Next 16 while authenticated routes remain unaffected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nextjs, react, typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100