Skip to content

feat: add TanStack Start framework adapter - #16139

Merged
jacobsfletch merged 147 commits into
mainfrom
experiment/framework-adapter-pattern
Jul 2, 2026
Merged

feat: add TanStack Start framework adapter#16139
jacobsfletch merged 147 commits into
mainfrom
experiment/framework-adapter-pattern

Conversation

@r1tsuu

@r1tsuu r1tsuu commented Apr 2, 2026

Copy link
Copy Markdown
Member

Adds @payloadcms/tanstack-start — the first non-Next.js framework adapter for Payload's admin panel.

Background

The framework adapter pattern already landed across four PRs:

This pushed every Next-specific concern (routing, request init, server functions, HMR) behind typed contracts in payload and made @payloadcms/ui framework-agnostic.

This PR is the payoff: a working adapter built entirely on that abstraction, proving the admin panel renders on a non-Next stack with no forks of the UI.

Motivation

  • Prove the abstraction. The adapter contracts are only as good as a second implementation. TanStack Start exercises every seam — a different renderer, a different server-function transport, a different build tool — and validates that @payloadcms/ui carries no hidden Next.js assumptions.
  • Meet users where they are. Not every project is on Next.js. Decoupling the admin panel opens Payload to the broader React SSR ecosystem, with TanStack Start as the first proof point.
  • Keep one UI. Both adapters render the same @payloadcms/ui components and data fetchers. There is no TanStack fork of the admin panel — only a thin adapter package that satisfies the contracts.

How it differs from the Next.js adapter

Same UI, different plumbing behind the contracts:

Next.js TanStack Start
Server rendering RSC flight payloads SSR + route loaders
Server functions 'use server' actions createServerFn
Request init next/headers @tanstack/react-start/server
Build / HMR Webpack / Turbopack Vite

Integration touch points

Wiring Payload into a TanStack Start app is a handful of file routes, similar to the Next.js app dir shipped since Payload v3:

app/
├── __root.tsx                      # root shell — withPayloadRoot swaps in the admin document on /admin
├── _frontend.tsx                   # your app's layout route
├── _frontend/                      # your app's routes
├── _payload.tsx                    # admin layout route — mounts Payload providers
└── _payload/
    ├── admin.index.tsx             # /admin
    ├── admin.$.tsx                 # /admin/* (splat)
    ├── api.$.ts                    # /api/* — Payload REST handlers
    └── server.functions.ts         # config + importMap injection; server functions

Root shell — This is the highest level touchpoint that affects your app. In your root route file, add the withPayloadRoot shell component:

// app/__root.tsx
import { withPayloadRoot } from "@payloadcms/tanstack-start/client";

export const Route = createRootRoute({
  shellComponent: withPayloadRoot(MarketingRoot),
});

For all other file contents, see the app-tanstack directory in the monorepo (subject to change). Docs to be provided in the future.

Status

Experimental. Ships as a new package alongside the Next.js adapter; nothing in the existing Next path changes at runtime.

@jmbockhorst

Copy link
Copy Markdown
Contributor

Hey, I am excited about this work! I had experimented with a similar approach a few months ago. I'm sure you might be aware that TanStack Start is planning to add RSC support soon (there is an draft blog post here, not sure how up to date it is with their current plans). I was wondering if this would make a difference in the approach to the framework adapter pattern, and if RSCs in Start make this significantly easier. If you haven't already, it might be good to have some communication with the TanStack team about this. Thanks for your work on this!

@r1tsuu

r1tsuu commented Apr 3, 2026

Copy link
Copy Markdown
Member Author

Hey, I am excited about this work! I had experimented with a similar approach a few months ago. I'm sure you might be aware that TanStack Start is planning to add RSC support soon (there is an draft blog post here, not sure how up to date it is with their current plans). I was wondering if this would make a difference in the approach to the framework adapter pattern, and if RSCs in Start make this significantly easier. If you haven't already, it might be good to have some communication with the TanStack team about this. Thanks for your work on this!

I'm aware, currently I want to see if it is possible so we don't rely on whether a framework supports RSC or not (while still maintaining 100% the same approach in Next.js). This is a bit more complex indeed but would allow room for any React framework (or even a custom one on top of Vite), not just Next/Tanstack. In this case - when Tanstack will add RSC support and if we want to use it - the only place we'd have modify is the adapter itself.

@paulpopus paulpopus mentioned this pull request Apr 7, 2026
@r1tsuu
r1tsuu force-pushed the experiment/framework-adapter-pattern branch 2 times, most recently from edd5952 to 22b4583 Compare April 9, 2026 19:19
@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 201.84 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.40 MB ⚠️ +336 B (+0.0%)
packages/payload/meta_shared.json esbuild/exports/shared.js 213.01 KB ⚠️ +20.08 KB (+10.4%)
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 285.25 KB ⚠️ +77 B (+0.0%)
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 36.24 KB ✅ -1.36 MB (-97.4%)
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.65 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▋ }}}$ 98.9%, 197.86 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 663 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.3%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.3%, 526 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.2%, 409 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.1%, 964.27 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 44.03 KB
dist/collections/operations ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 42.69 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 16.00 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.69 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.36 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.27 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 12.63 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.66 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.86 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.21 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.07 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.96 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.80 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.64 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.55 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.92 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 6.12 KB
dist/queues/config ${{\color{Goldenrod}{ }}}$ 0.4%, 5.59 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.43 KB
(other) ${{\color{Goldenrod}{ ███████▋ }}}$ 30.9%, 431.69 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████ }}}$ 72.0%, 150.13 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▎ }}}$ 5.1%, 10.66 KB
dist/fields/config ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 5.81 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 4.44 KB
dist/collections/config ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 3.22 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.69 KB
dist/config/client.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.68 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.41 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 1.04 KB
dist/globals/config ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 939 B
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.4%, 793 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.3%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.3%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/errors/ValidationError.js ${{\color{Goldenrod}{ }}}$ 0.3%, 577 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
(other) ${{\color{Goldenrod}{ ███████ }}}$ 28.0%, 58.41 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▎ }}}$ 13.2%, 37.20 KB
dist/lexical/ui ${{\color{Goldenrod}{ ███ }}}$ 12.1%, 34.16 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.7%, 32.93 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▍ }}}$ 9.7%, 27.22 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.7%, 18.82 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▍ }}}$ 5.9%, 16.58 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 14.09 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.9%, 11.08 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▉ }}}$ 3.6%, 10.02 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 9.61 KB
dist/features/converters ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 8.36 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.14 KB
dist/features/lists ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.64 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.28 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.23 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 2.83 KB
dist/lexical/nodes ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.66 KB
(other) ${{\color{Goldenrod}{ █████████████████████▋ }}}$ 86.8%, 244.85 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/exports/client ${{\color{Goldenrod}{ █████████████████████████ }}}$ 100.0%, 26.70 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.9%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▋ }}}$ 14.7%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██ }}}$ 8.4%, 1.51 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.3%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.8%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 419 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 157 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 148 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 146 B
(other) ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.1%, 12.46 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@r1tsuu

r1tsuu commented Apr 10, 2026

Copy link
Copy Markdown
Member Author

🧪 E2E Test Results — TanStack Start Adapter

Run: 24249234229 · 2026-04-10
Commit: c9a6ffe


Pass Rates

Suites

Framework ✅ Passing ❌ Failing ⏱ Cancelled Total Rate
TanStack Start 9 67 23 (timeout) 102 (†) 8.8%
Next.js 96 6 0 102 94.1%

(†) 23 TanStack suites were cancelled before finishing due to the 45-minute CI job limit.

Individual Tests

Framework ✅ Passed ❌ Failed ⏭ Skipped Tests ran Rate
TanStack Start 451 476 49 927 48.7%
Next.js 1,561 11 61 1,572 99.3%

TanStack's 23 cancelled suites account for ~566 tests that never ran. Counting those as not-passing: ~29% of all 1,548 tests.


TanStack — Suites With 0% Pass Rate (completely broken)

22 suites ran at least one test and passed nothing:

Suite Tests failed Next.js
fields-relationship 34 ✅ 33 pass
lexical/Lexical__e2e__main 34 ✅ 29 pass
folders 34 (2 pass) ✅ 35 pass
lexical/Lexical__e2e__blocks 29 ✅ 27 pass
lexical/Lexical__e2e__blocks#blockreferences 29 ✅ 27 pass
access-control 2 ✅ 101 pass
i18n 7 ✅ 7 pass
lexical/LexicalLinkFeature 6 ✅ 6 pass
lexical/LexicalFullyFeatured__db 6 ✅ 6 pass
storage-s3__client-uploads 4 ✅ 4 pass
storage-vercel-blob__client-uploads 4 ✅ 4 pass
auth 1 ✅ 2 pass
auth-basic 2 ✅ 2 pass
admin-bar 1 ✅ 1 pass
fields__collections__UI 1 ✅ 1 pass
fields__collections__UploadMultiPoly 2 ✅ 2 pass
fields__collections__UploadPoly 1 ✅ 1 pass
lexical/LexicalHeadingFeature 1 ✅ 1 pass
lexical/LexicalJSXConverter 1 ✅ 1 pass
lexical/LexicalListsFeature 1 ✅ 1 pass
plugin-cloud-storage 1 ✅ 3 pass
server-url 2 ✅ 2 pass
sort 2 ✅ 1 pass

TanStack — Suites Cancelled (45-min timeout, 0 tests ran)

Suite Next.js tests
versions 120
plugin-multi-tenant#conditionalProvider 30
plugin-multi-tenant 28
joins 28
plugin-import-export 38
query-presets 26
localization 46
group-by 38
dashboard 20
admin-root 11
lexical/LexicalFullyFeatured 14
lexical/OnDemandForm 7

TanStack — Partial Results (some tests pass)

33 suites with mixed pass/fail
Suite TanStack Next.js Rate
bulk-edit 1F / 19P 20P 95%
fields__collections__Text 1F / 18P 19P 95%
locked-documents 3F / 42P 45P 93%
field-error-states 1F / 9P 10P 90%
fields__collections__Tabs 1F / 7P 8P 88%
fields__collections__JSON 1F / 6P 8P 86%
fields__collections__Array 4F / 23P 28P 85%
fields__collections__Blocks 6F / 30P 36P 83%
fields__collections__Blocks#blockreferences 6F / 30P 35P 83%
plugin-form-builder 1F / 4P 5P 80%
fields__collections__ConditionalLogic 3F / 11P 14P 79%
fields__collections__Point 1F / 3P 4P 75%
admin__e2e__list-view 18F / 49P 87P 73%
fields__collections__Checkbox 1F / 2P 3P 67%
live-preview 11F / 16P 28P 59%
admin__e2e__document-view 9F / 13P 64P 59%
fields__collections__Email 4F / 6P 9P 60%
fields__collections__Select 2F / 3P 6P 60%
admin__e2e__general 13F / 16P 82P 55%
field-paths 1F / 1P 2P 50%
fields__collections__Radio 3F / 4P 7P 57%
fields__collections__Relationship 11F / 4P (+1 shard cancelled) 33P 27%
fields__collections__Collapsible 2F / 1P 4P 33%
plugin-nested-docs 2F / 1P 3P 33%
plugin-redirects 2F / 1P 3P 33%
a11y 36F / 18P 54P 33%
trash 13F / 7P (+1 shard cancelled) 40P 35%
plugin-seo 4F / 1P 3P 20%
fields__collections__Upload 8F / 1P 9P 11%
lexical/RichText 11F / 1P 9P 8%
uploads 75F / 5P 80P 6%
form-state 16F / 1P 16P 6%
folders 34F / 2P 35P 6%

TanStack — Fully Passing Suites (9)

_community · fields__collections__CustomID · fields__collections__Date · fields__collections__Indexed · fields__collections__Number · fields__collections__Row · fields__collections__Tabs2 · hooks · queues


Next.js — Regressions

6 failing Next.js suites (11 individual tests):

Suite Failed Assessment
auth 2 ⚠️ Real regressionInitialized lexical RSC field without a field name in create-first-user view
admin__e2e__general (shards 1 + 2) 4 Likely pre-existing flake — shard 3 passes
admin__e2e__document-view (shard 1) 2 Likely flake — shards 2 + 3 pass
admin__e2e__list-view (shard 4) 1 Likely flake — shards 1–3 pass
access-control (shard 1) 2 Likely flake — shard 2 passes

Action needed: auth failure (Initialized lexical RSC field without a field name) is a new regression from this PR affecting the create-first-user page with a Lexical richtext field. Must fix before merge.


@tannerlinsley

Copy link
Copy Markdown

Vinxi is dead. It's just vite + @tanstack/react-start/server (this uses H3 under the hood, but that's irrelevant). If there's anything else you'd like us to review, please come join our discord. I think we have a private payload channel but no one is there yet.

@r1tsuu
r1tsuu force-pushed the experiment/framework-adapter-pattern branch from 7ef54a3 to b741714 Compare April 15, 2026 15:15
@r1tsuu

r1tsuu commented Apr 15, 2026

Copy link
Copy Markdown
Member Author

@tannerlinsley I removed all the references to vinxi! thank you for looking at this so much!

🧪 E2E Test Results — TanStack Start Adapter (Updated)

Run: 24477822883 · 2026-04-15
Commit: d297a1b

Suite-level pass rates

Framework ✅ Passing ❌ Failing �� Cancelled Total Rate
TanStack Start 33 51 22 (timeout) 106 (†) 31.1%
Next.js 100 6 0 106 94.3%

Individual test-level pass rates (estimated)

Framework ✅ Passed ❌ Failed ⏭ Skipped Total ran Rate
TanStack Start ~540 ~410 ~55 ~950 ~57%
Next.js ~1,575 ~9 ~65 ~1,584 ~99.4%

Progress vs April 10 run

April 10 April 15 Δ
TanStack passing suites ~10 / 102 33 / 106 +22
TanStack suite pass rate ~10% 31% +20pp
TanStack test pass rate ~49% ~57% +8pp
Next.js auth Lexical regression ❌ failing fixed

@r1tsuu

r1tsuu commented Apr 21, 2026

Copy link
Copy Markdown
Member Author

🧪 E2E Test Results — TanStack Start Adapter (Updated — April 21)

Run: 24743389080 · 2026-04-21
Commit: f5870b3

Suite-level pass rates

Framework ✅ Passing ❌ Failing ⏱ Cancelled Total Rate
TanStack Start 42 48 16 106 39.6%
Next.js 99 7 0 106 93.4%

Individual test-level pass rates

Framework ✅ Passed ❌ Failed ⏭ Skipped 🔁 Flaky Total ran Rate
TanStack Start 817 318 96 8 1,231 72.0%
Next.js 1,614 11 61 29 1,686 99.3%

Progress vs April 15 run

April 15 April 21 (this) Δ
TanStack passing suites 33 / 106 42 / 106 +9
TanStack suite pass rate 31.1% 39.6% +8.5pp
TanStack tests passed ~540 817 +277
TanStack tests ran ~950 1,231 +281
TanStack test pass rate ~57% 72.0% +15pp
Next.js suite pass rate 94.3% 93.4% −0.9pp
Next.js test pass rate ~99.4% 99.3% −0.1pp

Introduce framework-agnostic type contracts that decouple the admin panel
from Next.js-specific APIs, enabling alternative framework adapters.

Key changes:
- Add `admin/adapters.ts` with RouterAdapter, ServerAdapter, ComponentRenderer,
  DevReloadStrategy, and LinkAdapter type contracts
- Replace Next.js `Metadata` type with framework-agnostic `AdminMeta`
- Add `Plugin` type extensions (slug, order, options) and `PluginsMap`
- Add `renderComponent` to `ServerProps` for adapter-injected rendering
- Add `SidebarTab` type for extensible sidebar tab system
- Replace hardcoded Next.js HMR WebSocket with pluggable `DevReloadStrategy`
- Add `isRSCEnabled()` utility for RSC feature detection
- Add `ServerFunctionMode` to server function types
- Extend shared exports with `extractJWT`, error types, `canAccessAdmin`
- Update `loadEnv.ts` and `resolveImportMapFilePath` for adapter flexibility
@r1tsuu

This comment was marked as outdated.

@r1tsuu r1tsuu closed this May 15, 2026
@r1tsuu

This comment was marked as outdated.

@r1tsuu r1tsuu reopened this May 15, 2026
@r1tsuu
r1tsuu force-pushed the experiment/framework-adapter-pattern branch from bfc2491 to 8fd93c0 Compare May 15, 2026 17:09
Introduce PAYLOAD_FRAMEWORK env variable and switch dispatch in test/dev.ts
so the e2e and integration test runners can boot against pluggable framework
adapters. Extract the existing Next.js boot logic into
test/adapters/nextDevServer.ts behind a shared DevServerResult contract,
add the @payloadcms/ui/server path mapping, and add the framework adapter
pattern plan document.

- Add test/adapters/nextDevServer.ts (extracted from test/dev.ts)
- Rewrite test/dev.ts to dispatch on PAYLOAD_FRAMEWORK
- Add @payloadcms/ui/server path mapping in tsconfig.base.json
- Add docs/plans/framework-adapter-pattern.md
Phase 2 of the framework adapter pattern. Replace direct next/navigation
and next/link imports with a framework-agnostic RouterAdapterContext
defined in packages/ui, and provide the Next.js implementation in
packages/next.

- Add packages/ui/src/providers/RouterAdapter: framework-agnostic context
  exposing useRouter / useSearchParams / usePathname / Link primitives
- Add packages/next/src/elements/RouterAdapter: NextRouterAdapter that
  wires next/navigation hooks and next/link into the context

Subsequent commits will replace next/* imports across packages/ui with
this adapter and drop next from peerDependencies.
…I to packages/ui

Phase 3 of the framework adapter pattern. Move framework-agnostic admin
panel UI from packages/next to packages/ui and reduce packages/next to a
thin Next.js adapter. Replace next/* imports in packages/ui with the
RouterAdapter context introduced in the previous commit.

Major moves (packages/next -> packages/ui):
- elements/Nav, DocumentHeader, FormHeader, Logo
- templates/Default, templates/Minimal
- views/Login, ResetPassword, ForgotPassword, Unauthorized, Logout, Verify,
  CreateFirstUser (orchestrators)
- views/API, Account sub-components
- views/NotFound, Version, Versions client components
- widgets/CollectionCards (sync parts)

Other Phase 3 additions in packages/ui:
- utilities/routeResolution: framework-agnostic route matching + custom
  view resolution extracted from packages/next
- utilities/serverFunctionRegistry: shared registry of server function
  handlers consumable by any adapter
- New @payloadcms/ui/views/* exports for moved views

packages/next changes:
- Original element/template/view files reduced to thin re-exports of the
  packages/ui implementations
- handleServerFunctions uses the shared registry

Subsequent commit adds Phase 4 work (RSC abstraction, data-first pattern,
RenderServerComponent split, ComponentRenderer threading).
Phase 4 (partial) of the framework adapter pattern. Move the RSC
flight-path renderer out of packages/ui, extract data fetchers from view
components, and thread a pluggable ComponentRenderer through serverProps
so non-RSC adapters can render the same view tree with their own
rendering primitive.

Adapter boundary:
- Move RenderServerComponent's canonical implementation from packages/ui
  to packages/next/src/elements/RenderServerComponent
- Retain @payloadcms/ui/elements/RenderServerComponent as a deprecated
  re-export and add @payloadcms/ui/elements/RenderServerComponent/clientOnly
  exporting the framework-agnostic RenderClientComponent
- Add packages/ui/src/exports/server.ts entrypoint for server-only utils
- Add @payloadcms/next/elements/RenderServerComponent export

Data-first view pattern (extract async data fetchers from packages/ui views
and pair them with index.client.tsx client components — orchestrators in
packages/next call the fetcher then render the client component):
- Root, Dashboard, Account, Login, Document, List, Version, Versions,
  Verify, CreateFirstUser data fetchers
- Nav.getNavData, CollectionCards data fetcher
- ModularDashboard data fetcher + client component

Server-function data-only path:
- Add packages/ui/src/utilities/dataOnlyHandlers/* and
  dataOnlyServerFunctions.ts so non-RSC adapters can call render-document,
  render-list, render-widget, render-field, render-document-slots,
  getDefaultLayout and receive JSON (no React flight payload)

ImportMap as a client provider:
- Move ImportMap into packages/ui/src/providers/ImportMap so adapters
  hydrate the import map through a framework-agnostic context

richtext-lexical:
- Adopt the injected ComponentRenderer in rscEntry and generateImportMap
- Add browser-safe rsc.browser entry and clientEntry for non-RSC adapters
The Phase 1 commit replaced @next/env with dotenv + dotenv-expand in
loadEnv.ts but did not update package.json. Drop @next/env, add the
dotenv packages, and refresh the lockfile.
Reconcile main's UI4 css migrations and view redesigns (NotFound,
CreateFirstUser, Login, Unauthorized) with Phase 3 element/template/view
moves from packages/next to packages/ui. Pull main's css into the moved
ui locations, drop the original scss files, and keep main's redesigns
as the canonical implementation behind thin next-side re-exports. Take
main's Document/index.tsx orchestrator as-is to avoid regressing
Next.js behavior; the data-first refactor stays available in
packages/ui for non-Next adapters but does not back the Next view.
When buildFormState was called without an explicit renderComponent
(the Next.js path), the fallback was RenderClientComponent which strips
serverProps from RSC components. This dropped clientField on RSC field
renderers (e.g. lexical's RscEntryLexicalField), producing
'Initialized lexical RSC field without a field name'. Default to the
RSC-aware RenderServerComponent so Next.js form-state builds keep
working; non-Next adapters still inject their own ComponentRenderer
explicitly.

Also flip @payloadcms/ui exports from index.scss to index.css for the
view paths Phase 3 moved next->ui (Login, LoginForm, CreateFirstUser,
Unauthorized) to match main's UI4 css conversions.
Phase 3 auto-created next-side stubs that re-export from @payloadcms/ui
for every moved element/template/view. 64 of these are never imported
from anywhere — neither internally in packages/next, nor from other
packages, nor via @payloadcms/next/* package.json exports. Delete them.

Kept: thin re-exports that are still imported by next-side orchestrators
(Document/index.tsx, Account/index.tsx, etc.) via relative paths.
Removing those would require adding new @payloadcms/ui/* package.json
exports for each (e.g. ./views/Document/getDocPreferences); deferred.

Breaking change for external consumers doing deep imports under
@payloadcms/next/elements, /templates, or /views — none of these paths
are in packages/next/package.json's exports field, so they were
unsupported deep imports.
… directly

Replace 34 thin re-export files in packages/next/src with direct
@payloadcms/ui/* imports in the orchestrators and exports/* barrels.
Add the 8 missing @payloadcms/ui package.json exports needed for these
direct imports (Document/getDocPreferences|getDocumentData|getIsLocked
|getVersions, List/enrichDocsWithVersionStatus|resolveAllFilterOptions
|transformColumnsToSelect, elements/DocumentHeader).

Net effect: packages/next contains only orchestrators + adapter-specific
files (RouterAdapter, RenderServerComponent). All framework-agnostic
elements/templates/views are consumed from @payloadcms/ui directly.

Breaking change for deep imports under @payloadcms/next/{elements,
templates,views} — those paths were never in package.json exports
field, so they were already unsupported.
@jacobsfletch
jacobsfletch marked this pull request as ready for review July 2, 2026 18:30
@jacobsfletch jacobsfletch changed the title feat: tanstack framework adapter feat: add TanStack Start framework adapter Jul 2, 2026
@jacobsfletch
jacobsfletch merged commit 5081ad4 into main Jul 2, 2026
536 of 550 checks passed
@jacobsfletch
jacobsfletch deleted the experiment/framework-adapter-pattern branch July 2, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants