payloadcms / payloadcms/payload

[BUG] Admin renders blank for all unauthenticated routes on Next.js 16 (login, forgot, create-first-user, logout)

Open
#17,545 1 comment 0 reactions 0 assignees View on GitHub

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 dev and next 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

  1. Payload 3.85.1 on Next.js 16.2.12 or later, standard (payload) route group.
  2. Ensure at least one user exists.
  3. Request /admin/login without 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
  • transpilePackages including @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; auth is just maxLoginAttempts / lockTime (so #8979 does not apply)
  • Custom admin.route (default /admin, so #7401 does not apply)
  • next/navigation redirect() — a minimal test page returns a correct 307 with Location
  • 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.