New Page Scaffold
Create a new Next.js App Router page with all conventions followed.
Steps
Ask which app (
flow-globalorflow-factory) and the route path (e.g.,/orders/[orderId]/details)Determine domain — the top-level route segment determines the domain. List the live domains with
ls apps/<app>/app/— do not rely on a memorized list; domains drift. Every directory is a domain except_-prefixed dirs (_lib,_fonts),api,healthcheck, andpost-login.Create the page at
apps/<app>/app/<route>/page.tsx:- Default to a server component (no
'use client'directive) - Use TypeScript with proper param types for dynamic segments
- Follow existing page patterns in the same domain for consistency
- Default to a server component (no
Fetch data on the server by default. The page (a server component) awaits its data directly. Use
Promise.allfor independent fetches so they don't waterfall. Reserve client-side TanStack Query for genuinely client-driven data — mutations, polling, infinite scroll, or state that reacts to user interaction — not for the page's initial load.Push
'use client'to the smallest leaf. Keep the page and as many children as possible as server components; mark only the interactive child (the one with hooks/handlers) as a client component, and pass server-rendered children into it via props/childrenrather than making a whole subtree client.Create co-located files as needed:
_lib/actions/— server actions ('use server') using@hadrian-mtv/connect-server-actions. Put actions in the_lib/actionsfolder, not a single_lib/actions.ts._lib/queries.ts— only if client-side fetching is actually needed. DefinequeryOptions()factories with hierarchical keys built through the domain's key factory per thetanstack-queryrule (installed at.claude/rules/tanstack-query.md) — never hand-write a literal key.loading.tsx— loading skeleton if the page does async data fetchingerror.tsx— error boundary (must be'use client')
Regenerate route types: Run
pnpm build:typesafe-urlVerify navigation types: Confirm the new route is available in
@hadrian-mtv/flow-navigationtypesCheck domain boundaries: Ensure all imports respect
eslint-plugin-boundariesrules:- Only import from allowed sibling domains + shared layers (
app/_lib/,lib/,utils/) - Use
FlowLink/FlowLinkButtonfor all navigation — nevernext/link - Use
@hadrian-mtv/flow-loggerfor logging — neverconsole.*
- Only import from allowed sibling domains + shared layers (
Verify with
pnpm lint:tscthat the page compiles cleanly
Gotchas
- Domain lists drift — always read the filesystem. A hard-coded list in this skill was missing four live domains within months of being written.