Skip to content

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 arrayView
[] or undefinedDashboardView
[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.

Component props

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.

VexConfig

The resolved server VexConfig, typically the default export of vex.config.server.ts — not the client-safe vex.config.ts.

Promise<{ path?: string[]; }>

Next.js 15 async params { path?: string[] }

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.

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.

Promise<Element>

The appropriate admin view for the current URL path.

app/admin/[[...slug]]/page.tsx
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)}
/>
);
}