NextAdminPage
NextAdminPage(
props):Promise<Element>
Defined in: packages/next/src/NextAdminPage.tsx:257
VexCMS admin page server component for Next.js.
An async server component that routes by the [[...slug]] catch-all
params, prefetches Convex data via fetchQuery, and renders the correct
view component. Does not include a layout wrapper — NextAdminLayout
in app/admin/layout.tsx owns the persistent shell.
Route mapping:
path array | View |
|---|---|
[] or undefined | DashboardView |
[collectionSlug] | CollectionListView with preloaded docs |
[collectionSlug, "new"] | CollectionEditView (empty form) |
[collectionSlug, documentId] | CollectionEditView with preloaded doc |
Takes tokenPromise/authPromise, not resolved values (perf fix, developer-binding): the
caller (page.tsx) starts both without awaiting them immediately, so NextAdminPage’s own
data fetches can begin as soon as tokenPromise resolves (what every fetchQuery call
actually needs) rather than waiting on the slower authPromise (which itself depends on
the token, plus one more Convex round trip). The caller’s own page-level access gate
(canAccessAdminPanel) should await authPromise and redirect BEFORE rendering
NextAdminPage at all (Polish Decision 8, developer-reported — a generic page-level
skeleton rendered while the gate was still in flight made the loading state worse than no
gate-level fallback at all: shifts, row-selection spinners showing where real checkboxes
belong, skeletons with no matching settled element, content popping in unannounced).
NextAdminPage streams its own real chrome (title/tabs/toolbar/controls) immediately once
rendered and wraps only its OWN data-dependent regions (e.g. table rows) in their own
<Suspense> boundaries — no page-level fallback needed or wanted.
Parameters
Section titled “Parameters”Component props
authPromise?
Section titled “authPromise?”Promise<VexApiAuth>
The in-flight (or already-resolved — e.g. Promise.resolve(auth)
after the caller’s own gate already awaited it) server-resolved caller (Decision 44),
OPTIONAL. Wraps the entire return in VexAuthProvider and narrows the collection list
route’s search field by read permission. Omit it when config.access is absent or
disabled — nothing is restricted either way. An access-enabled project that omits it is
treated as an anonymous caller (fail-closed) with a one-time dev console warning.
config
Section titled “config”The resolved server VexConfig, typically the default export of
vex.config.server.ts — not the client-safe vex.config.ts.
params
Section titled “params”Promise<{ path?: string[]; }>
Next.js 15 async params { path?: string[] }
searchParams?
Section titled “searchParams?”Promise<Record<string, string | string[] | undefined>>
Next.js’s own searchParams prop, forwarded by the app route (AdminPage). Only the
collection list route reads it (parseListViewUrlState, Design Decision 40) — every other
route ignores it.
tokenPromise?
Section titled “tokenPromise?”Promise<string | undefined>
The in-flight (or already-resolved) session token (e.g.
getToken()), forwarded to every fetchQuery call for authenticated reads. Optional —
a project with no auth at all omits it, every fetchQuery call runs unauthenticated.
Returns
Section titled “Returns”Promise<Element>
The appropriate admin view for the current URL path.
Example
Section titled “Example”import { canAccessAdminPanel } from "@vexcms/core";import { NextAdminPage } from "@vexcms/next/server";import { redirect } from "next/navigation";import { getToken, getUserOrg } from "~/auth/server";import config from "../../../../vex.config.server";
export default async function AdminPage({ params,}: { params: Promise<{ path?: string[] }>;}) { const token = await getToken(); if (!token) redirect("/auth/sign-in?redirectTo=/admin"); const auth = await getUserOrg(); if ( !canAccessAdminPanel({ access: config.access, user: auth.user, organization: auth.organization }) ) { redirect("/unauthorized"); } return ( <NextAdminPage config={config} params={params} tokenPromise={Promise.resolve(token)} authPromise={Promise.resolve(auth)} /> );}