Next.js Architecture
Targets Next.js 16 with React 19, TypeScript strict, App Router only. Builds on react-architect for client-side patterns; this skill covers the Next-specific layers — server components, server actions, routing, data fetching, edge vs node, deployment. Concrete skeletons in RECIPES.md; pinned dependencies in STACK.md.
1. App Router only
The Pages Router is legacy. New code lives in app/; if you inherit a Pages Router app, don't mix unless mid-migration. Folder layout reference in RECIPES § App Router folder layout.
Key conventions:
page.tsx= the route's UI.layout.tsxwraps it. Both default to server components.loading.tsxwraps the route in Suspense automatically.error.tsxis a client component that wraps the route in an ErrorBoundary.- Route groups (
(name)) organize without adding URL segments. @slotis a parallel route — independent loading/error states, rendered into named slots in the parent layout.
2. Server components by default, client when needed
Every component is a server component unless you opt out with "use client". The choice belongs to the leaf component that needs the client behavior — not the root.
When to stay server
- Pure rendering — no state, no effects, no event handlers.
- Data fetching —
await db.query(...),await fetch(...). - Secrets / server-only logic —
process.env.STRIPE_SECRET_KEYonly loaded in server components. - Markdown rendering, syntax highlighting, large dependencies you don't want shipped to the browser.
When to flip to "use client"
useState,useEffect,useReducer, any other hook.- Browser-only APIs (
window,localStorage,IntersectionObserver). - Event handlers (
onClick,onChange, etc.) — must be in a client boundary. - Third-party libraries that use any of the above (most chart libraries, animation libraries).
The boundary rule
- Server passes data to client as props — props must be JSON-serializable. Pass IDs, not class instances; pass plain objects, not Date (use ISO strings).
- One
"use client"directive at the top of a file makes that file and everything imported into it run client-side. Hoist the directive to the smallest leaf. - Don't nest client → server. Server components can be rendered inside client trees only as
childrenprops (slot pattern).
Skeleton: RECIPES § Server / client boundary.
3. Data fetching
Reads — direct from server components (hybrid default)
Per the project choice: direct DB access from RSC for reads, API routes for writes. Cleaner UX (single round-trip), type-safe end-to-end.
getUserlives in a server-only module (@/lib/users.server.tsor marked with the"server-only"import). Prevents accidental bundling of DB code into the client.- Use sql-architect's
psycopg + .sqlpattern when the project's backend convention matches; otherwise use the project's existing data layer. notFound()triggers the closestnot-found.tsx— proper 404 with the right HTTP status.
When to flip to API routes for reads: any of the following — and document the choice per project:
- You want a separate microservice topology (Next is the frontend BFF, backend is its own service).
- The same data is consumed by mobile clients or other web frontends; one HTTP API beats two implementations.
- Auth requirements force the boundary (e.g. backend has access controls direct DB calls can't replicate).
Skeleton: RECIPES § Read directly from a server component.
Writes — server actions
Server actions are the canonical mutation pattern. No client-side fetch, no manual JSON handling.
"use server"at the top of a file marks every export as a server action — accessible from client components viaimport.- Always re-authenticate inside the action. Don't trust the calling context; verify the session.
- Validate with Zod at the boundary — same discipline as REST handlers per rest-api-architect §7.
revalidatePath/revalidateTagto refresh server-rendered data after a mutation. Without this, the user sees stale state.- Return discriminated unions (
{ok: true, ...} | {ok: false, ...}) so the client renders success vs. errors based on the field.
Skeleton: RECIPES § Server action with Zod validation + form binding.
TanStack Query — when?
The react-architect §5 recommendation stands inside client components — but in Next.js the trade-off shifts:
- Server components fetch on the server. TanStack Query's "server state cache" benefits don't apply when the page is server-rendered fresh.
- TanStack Query earns its place in Next when: there's substantial client-side data fetching (autocomplete, real-time updates, infinite scroll, polling), or you want optimistic UI for mutations beyond what server actions give.
4. Streaming with Suspense
Next App Router streams the HTML as RSC chunks resolve. Pair with <Suspense> for granular per-region loading.
loading.tsxis a route-level Suspense. Per-region Suspense gives finer-grained streaming.error.tsxis a route-level ErrorBoundary. Inline<ErrorBoundary>(fromnext/error-boundaryor a custom one) for finer scoping.- Don't over-stream. A page with 12 Suspense boundaries appears janky as chunks fill in. Target the genuinely slow regions.
Skeleton: RECIPES § Per-region streaming with Suspense.
5. Edge vs Node runtime
Each route can declare its runtime via export const runtime = "edge" | "node". Defaults to node.
- Default
node— full Node API, npm packages with native modules, deepest compatibility. runtime = "edge"for low-latency, geographic-edge routes — auth verification, redirects, feature flags, A/B routing.- Edge restrictions: no Node-only modules, no native bindings, smaller bundle limit. DB drivers vary — standard Postgres drivers don't work on edge.
Declaration + edge-compatible driver notes in RECIPES § Edge runtime declaration.
6. Metadata, Images, Fonts
Metadata
- Per-route
generateMetadata— async, runs on the server, has access to params. - Root
metadataexport inapp/layout.tsxfor defaults (title template, OG defaults). - Don't hand-write
<head>— Next manages it for SEO + social embed correctness.
Example in RECIPES § Metadata via generateMetadata.
next/image
- Always
next/image, never<img>in App Router code. Handles responsive sizes, modern formats (AVIF/WebP), lazy loading, no layout shift. - Set
widthandheight(orfillwith a sized parent). Prevents CLS. priorityonly on above-the-fold images. Every image marked priority defeats the optimization.- Remote images need
remotePatternsinnext.config.ts— strict allow-list for security.
next/font
next/font/googlefor Google Fonts; subsets + variable axes pinned at build time, no external request.next/font/localfor self-hosted fonts.- Both eliminate FOIT/FOUT. Don't use
@importor<link rel="stylesheet">for fonts.
7. API routes — when
Server actions cover most mutations. API routes (app/api/.../route.ts) earn their place for:
- Webhooks — external services (Stripe, GitHub, etc.) POST to your API.
- External clients — mobile, public API, partner integrations.
- Streaming responses — server-sent