Using the App Router
Foundation is mostly built on the Next.js Pages Router (src/pages), but it also supports the App Router (src/app). This page walks through Foundation's own App Router reference implementation.
Reference implementation: the demo onboarding wizard​
Foundation has a working App Router example at apps/foundation/src/app/demo-onboarding. It's a multi-step form wizard (quote → tariffs → details → review → success) built to prove out createNextWizard from Blueprint's shared onboarding module on the App Router, using Server Actions instead of an API route.
Layout​
app/demo-onboarding/layout.tsx wraps every step in the shared UIProvider and page chrome, the same role _app.tsx plays for the Pages Router.
Shared wizard config​
The wizard's steps, form types, and validation are split into three files under domains/onboarding/shared/utils, so isomorphic code never accidentally pulls in server-only APIs:
wizard-common.ts— form value types, enums, and (de)serialization for storing state in a cookie. Shared by both routers.wizard-app-router.ts— the App Router's step list and yup validation schemas. Client-safe (nonext/headers).wizard-app-router-server.ts— the App Router'sdemoWizardAppRouterinstance, built withcreateNextWizardfrom Blueprint's onboarding module (server-only entry point). Server-only, becausecreateNextWizardreads/writes cookies vianext/headers.
A step page​
Each step folder has a page.tsx (server component) and a *Client.tsx (client component). quote/page.tsx shows the pattern:
export default async function QuotePage() {
const state = await demoWizardAppRouter.getCookieState();
async function submitStep(newState: DemoOnboardingState) {
"use server";
await demoWizardAppRouter.setCookieState({ state: newState });
}
return (
<QuoteClient
serializedState={demoWizardAppRouter.serializeState(state)}
submitStep={submitStep}
/>
);
}
The server component reads the wizard's cookie state and passes it down as serialized data, along with an inline Server Action for saving it back. QuoteClient.tsx is a "use client" component that renders the Formik form; on submit it calls submitStep (the Server Action) to persist state, then router.push to move to the next step client-side.
Later steps (details, review) use a standalone actions.ts file with a top-level "use server" Server Action instead of an inline one — either pattern works, use whichever reads more clearly for the page.
See also​
- Setting up auth (App Router) — how the pieces of Blueprint's shared auth module fit together in an App Router app
- Building forms (App Router) — step-by-step form patterns using Server Actions, validation, and accessibility
- Onboarding wizards overview — App Router and Pages Router wizard utilities, plus the client-side
useWizardhook createNextWizard(App Router) API and App Router examples