feat: admin framework adapter pattern and tanstack support#16139
Draft
feat: admin framework adapter pattern and tanstack support#16139
Conversation
- Add BaseAdminAdapter and AdminAdapterResult types to packages/payload/src/admin/adapter/types.ts - Add createAdminAdapter helper factory (mirrors createDatabaseAdapter pattern) - Add admin.adapter field to Config type - Wire adapter.init() into Payload lifecycle after db init - Export types and helper from packages/payload/src/index.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add RouterProvider with value-based context (params, pathname, router, searchParams, Link) — avoids dynamic hook calls that react-compiler rejects - Add useRouter, usePathname, useSearchParams, useParams hook wrappers reading from context - Integrate RouterProvider into RootProvider via optional router prop - Export RouterProvider, useRouter, usePathname from public client index Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 2: Replace all next/navigation imports in packages/ui (~41 files) with RouterProvider hooks. Add NavigateOptions to RouterInstance for scroll support. Replace AppRouterInstance, ReadonlyURLSearchParams, and next/link LinkProps type imports with framework-agnostic equivalents. Phase 3: Remove all Next.js dependencies from packages/payload. - Replace @next/env with dotenv + dotenv-expand in loadEnv.ts - Replace Metadata type from 'next' with PayloadMetadata subset - Remove ReadonlyRequestCookies from getRequestLanguage (use Map) Phase 5: Create nextAdapter() factory in packages/next implementing the BaseAdminAdapter interface. Add NextRouterProvider wrapping next/navigation hooks into the RouterProvider context. Wire NextRouterProvider into the Root Layout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 4 (partial): Split Login and NotFound views into server entry (packages/next) + render component (packages/ui). Login server entry handles redirect when user is logged in, delegates rendering to framework-agnostic LoginView in packages/ui. NotFound client component moved to packages/ui. Refactor all 11 client components in packages/next that imported from next/navigation to use RouterProvider hooks from @payloadcms/ui instead. Replace deprecated useSearchParams/useParams exports from SearchParams and Params providers with the Router provider versions. Add views/* export path to @payloadcms/ui package.json. Add ServerNavigation context (provider + hook) for future use by client components that need notFound/redirect access. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ges/ui Move Login, NotFound, ForgotPassword, Logout, Verify, CreateFirstUser, Unauthorized, and ResetPassword views to packages/ui. These views have zero Next.js dependencies and are now fully framework-agnostic. packages/next view files become thin re-exports for backwards compatibility. Login retains its server entry for redirect handling. Also move FormHeader and Logo elements, plus getDocPreferences and getDocumentData utilities to packages/ui. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the entire Dashboard view (18 files, ~2400 lines) from packages/next to packages/ui. Convert all @payloadcms/ui self-imports to relative paths. Move getPreferences utility. Add @dnd-kit/modifiers dependency to packages/ui. packages/next Dashboard entry becomes a thin re-export. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n prompt Add --framework / -f flag with 'next' (default) and 'tanstack-start' options. Add interactive selectFramework() prompt following the same pattern as selectDb(). Add FrameworkType to types and frameworkType to templates and createProject args. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Proof-of-concept AdminAdapter implementation for TanStack Start. Implements the full BaseAdminAdapter interface with stub methods that show the intended @tanstack/react-router and vinxi/http integration. Replace stub hooks in RouterProvider.tsx with real @tanstack/react-router imports to activate. Replace stub methods in adapter/index.ts with vinxi/http cookie calls and TanStack navigation throws. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Test suite covers: - adminAdapter is undefined without explicit configuration (backwards compat) - admin.adapter config field accepts AdminAdapterResult - adapter.init() is called and returns adapter instance - createAdminAdapter helper is an identity function Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a framework-agnostic FrameworkTestHarness interface so e2e
tests can run against any admin adapter, not just Next.js.
- FrameworkTestHarness interface with start()/stop() contract
- NextJsTestHarness wrapping existing dev-server behavior
- getTestHarness() factory reading PAYLOAD_FRAMEWORK env var
- Stub for TanStack Start harness (pending implementation)
Select framework via: PAYLOAD_FRAMEWORK=next|tanstack-start
CI matrix usage:
strategy:
matrix:
framework: [next] # add tanstack-start once harness is implemented
env:
PAYLOAD_FRAMEWORK: ${{ matrix.framework }}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
26-task plan covering: - Moving all framework-agnostic code from packages/next to packages/ui (elements, templates, utilities, view render functions with nav callbacks) - Creating a shared handleServerFunctions dispatcher in packages/ui - Full tanstack-start adapter with vinxi/http, @tanstack/react-router - tanstack-app/ shell (app.config.ts, routes, layouts, entry points) - pnpm dev:tanstack command and test/dev-tanstack.ts entry point Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ith navigation callbacks Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ges/ui Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… shared server function dispatcher Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…and @tanstack/react-router Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add framework-agnostic view utilities: getRouteData, getCustomViewByRoute, getCustomViewByKey, getDocumentViewInfo, isPathMatchingRoute, attachViewActions - Create RenderRoot.tsx accepting initPageResult + notFound/redirect callbacks - Create DocumentView, ListView, BrowseByFolder, CollectionFolders, CollectionTrash framework-agnostic wrappers that don't import from next/navigation - Add views/Root/RenderRoot export to packages/ui package.json - Update packages/next RootPage to delegate to renderRootPage from packages/ui Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Create tanstack-app workspace with package.json, app.config.ts, tsconfig - Add tanstack-app entry points: client.tsx, ssr.tsx, router.tsx, routeTree.gen.ts - Create root layout route (__root.tsx) with server function dispatcher - Create admin (admin.$.tsx) and API (api.$.ts) routes - Add packages/tanstack-start RootLayout server component using vinxi/http - Add packages/tanstack-start RootPage delegating to packages/ui renderRootPage - Add packages/tanstack-start routes re-exporting REST handlers from packages/next - Add dev:tanstack and dev:tanstack:postgres npm scripts - Update initDevAndTest to support TanStack app importMap path (app/importMap.js) - Add tanstack-app to pnpm-workspace.yaml Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Switch tanstack-app from Vinxi/app.config.ts to Vite/vite.config.ts - Use @tanstack/react-start (not @tanstack/start) with @vitejs/plugin-react - Update initReq: getWebRequest() → getRequest() from @tanstack/react-start/server - Update adapter/RootLayout: vinxi/http cookies → @tanstack/react-start/server - Update createServerFn API: validator → inputValidator, new ssr.tsx signature - Add getRouter() function (replaces createRouter()) as required by plugin - Add scss tilde import alias, sharp optimizeDeps.exclude, sass devDependency - Add @payloadcms/next and @payloadcms/richtext-lexical to tanstack-app deps - Fix initDevAndTest.ts importMap path for TanStack apps - Smoke test: pnpm dev:tanstack serves HTTP 200 at /admin Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… as devDep Replaces @tanstack/start and vinxi peer deps with @tanstack/react-start. Adds @tanstack/react-start as devDependency so tests resolve the import. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add tanstackStartCompatPlugin() to stub tanstack-start-injected-head-scripts:v virtual module missing from @tanstack/start-server-core@1.167.9 - Add admin.index.tsx route for /admin base path (splat doesn't match empty) - Fix RenderRoot.tsx: empty segments must pass null path to formatAdminURL to match adminRoute without trailing slash Dashboard, collections list, and create document all working. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Port 3000 for dev:tanstack (same as Next.js, enables running e2e suites) - Auth redirect moved to route loader (loader redirects work in TanStack Router) - admin.index.tsx loader handles unauthenticated access redirect - admin.$.tsx loader handles /collections, /globals → /admin redirect - server-only-stub.js: ESM stub for server-only packages (file-type, pino, mongoose, util) - serverOnlyStubPlugin in vite.config.ts intercepts client bundle for server packages - tanstackStartCompatPlugin stubs tanstack-start-injected-head-scripts:v virtual module - Fix StartClient import: @tanstack/react-start/client (not @tanstack/react-start) - Fix RenderRoot.tsx: empty segments → null path (prevents /admin/ trailing slash) - admin.index.tsx route added for /admin base path Known issue: TanStack Start is isomorphic (all code runs client+server). Server-only Payload code (DB adapters, sharp, etc.) leaks into client bundle. Full RSC isolation requires createServerFn wrappers — tracked for future work. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ldTableData split - Phase 0: extract buildTableData from buildTableState in packages/ui (RSC-free list data) - Task 1: getPageState calls buildTableData for list views, returns listData in SerializablePageState - Task 3: TanStackListView uses ListQueryProvider + SelectionProvider + DefaultListView directly (no server fn call on mount, no Table:ReactNode) - Task 4: TanStackAdminPage passes listData to TanStackListView - Constraint note: packages/ui change is one new file + one export line only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
Rewrite the TanStack app @payload-config alias before Vite starts, support suite#config overrides in dev:tanstack, and make e2e runs launch dev:tanstack when PAYLOAD_FRAMEWORK=tanstack-start.
Replace the server-only TanStack admin route flow with serializable page state, client-side admin rendering, and supporting TanStack root/server-function wiring. Remove the old server-only stubs and update related exports, app routes, and test types.
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Made-with: Cursor
Decouple root, dashboard, and default template rendering from direct server-component resolution so framework adapters can provide their own renderer. This keeps Next on the current RSC path while establishing the shared UI boundary needed for TanStack support.
Continue moving admin composition behind the injected renderer so shared UI no longer owns direct component execution in nav, dashboard, list, and document paths. This keeps the Next runtime behavior intact while reducing the work TanStack needs to replace duplicated admin rendering.
Clarify that the next TanStack step is to collapse `TanStackAdminPage` onto shared template and view contracts instead of extending its custom wrappers.
Route the TanStack admin dashboard, nav, list, and document paths through shared ui entrypoints so adapter-specific code stays thin and framework behavior converges around the same contracts.
Clarify that the immediate migration target is the shared root/bootstrap path needed to load the first admin page, with nav only serving as one example of the boundary problem.
Move root bootstrap resolution and default shell composition into shared UI contracts so Next and TanStack consume the same first-page path.
Clarify that the root-first adapter work must extend across all built-in admin views and that TanStack should adopt a Next-like per-view folder structure instead of growing a monolithic admin page entrypoint.
Explain that reusable admin-side server logic can remain in `@payloadcms/ui`, while framework-specific invocation transport belongs in `@payloadcms/next` and `@payloadcms/tanstack-start` because not every runtime will support server actions.
Move the simplest TanStack auth and minimal view branches into per-view modules and document that shared ui auth view entries should be the long-term implementations reused across Next and TanStack.
Clarify that the same adapter-to-shared-ui structure should apply to dashboard, list, document, edit, account, and related built-in views, not just the auth and minimal view slice.
Clarify that TanStack's current dashboard extraction is only transitional and that non-RSC-specific modular dashboard logic should move from Next into shared ui for reuse across frameworks.
Eliminate the intermediate re-export file for ModularDashboard, allowing direct imports from @payloadcms/ui. Update related documentation to emphasize the importance of avoiding unnecessary indirection in adapter packages. Clean up tests and imports to reflect these changes.
…and imports Eliminate unnecessary components and imports from TanStackAdminPage, simplifying the structure and improving maintainability. This includes the removal of ListView, AccountView, and UnsupportedView, along with related logic, to focus on essential functionality.
Replace ~@payloadcms/ui/scss with relative paths in 28 SCSS files moved from packages/next to packages/ui — the webpack ~ prefix cannot self-resolve within the same package via esbuild's sass-plugin. Fix missing InitPageResult properties (languageOptions, translations, importMap, i18n, payload, viewType) in BrowseByFolder and CollectionFolders server function handlers. Fix TanStackViewType → ViewTypes cast in buildRenderViewArgs.ts.
Add TanStack BrowseByFolder and CollectionFolders view adapters that fetch data via server functions and render shared UI components. Register render-browse-folder and render-collection-folder handlers in the shared server function registry. Update getViewByType dispatcher with folder and version view types. Add architecture overview doc for the framework-agnostic admin pattern.
Update the Dashboard view implementation to introduce a shared async builder (`renderDashboardView`) for data fetching and rendering. Modify the Next.js adapter to utilize this builder, improving the separation of concerns between the shared UI and framework-specific logic. Additionally, update exports to include the new builder and related types, streamlining the integration across frameworks.
Extract fetchListViewData from renderListView into a shared, serializable data function. Move list, document, and account data fetching into the TanStack getPageState loader so views render immediately from loader data instead of showing a LoadingOverlay while useEffect fetches via server function. - List: shell (header, controls, pagination) renders instantly; Table loads async via server function - Document: renders DocumentInfoProvider + DefaultEditView directly from loader data; falls back to server function for version subviews - Account: same pattern as Document with useEffect fallback
Document the two-layer split (data function + builder), the hybrid approach for non-serializable parts, and updated file structure reflecting fetchListViewData and getPageState loader integration.
…oot layout - Fix boolean prefetch attribute warning on TanStack Link by mapping to preload prop - Export RootLayoutData type from client entrypoint - Export getRootLayoutData from server entrypoint - Switch __root.tsx to loader-based data fetching with getRootLayoutData - Add switchLanguage server function for i18n cookie management
Plan for refactoring render-widget, render-list, render-document server functions to return serializable data by default while preserving RSC rendering behavior in Next.js via extraServerFunctions override.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is an experiment for now