name: nextjs-architect
version: 1.0.0
description: Next.js 16 standards — App Router only, server components by default with explicit `"use client"` boundaries, server actions for mutations, streaming Suspense, edge vs node runtime, Image/Font/Metadata APIs. Pairs with react-architect. Use when scaffolding or reviewing a Next.js app or auditing server/client boundaries.
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.tsx` wraps it. Both default to server components.
- `loading.tsx` wraps the route in Suspense automatically.
- `error.tsx` is a client component that wraps the route in an ErrorBoundary.
- Route groups (`(name)`) organize without adding URL segments.
- `@slot` is 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_KEY only 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
children props (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.
- `getUser` lives in a server-only module (
@/lib/users.server.ts or marked with the "server-only" import). Prevents accidental bundling of DB code into the client. - Use [sql-architect](../../databases/sql-architect/SKILL.md)'s `psycopg + .sql` pattern when the project's backend convention matches; otherwise use the project's existing data layer.
- `notFound()` triggers the closest
not-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 via
import. - 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` / `revalidateTag` to 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.tsx` is a route-level Suspense. Per-region Suspense gives finer-grained streaming.
- `error.tsx` is a route-level ErrorBoundary. Inline
<ErrorBoundary> (from next/error-boundary or 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.
- Per-route `generateMetadata` — async, runs on the server, has access to params.
- Root `metadata` export in
app/layout.tsx for 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 `width` and `height` (or
fill with a sized parent). Prevents CLS. - `priority` only on above-the-fold images. Every image marked priority defeats the optimization.
- Remote images need `remotePatterns` in
next.config.ts — strict allow-list for security.
next/font
- `next/font/google` for Google Fonts; subsets + variable axes pinned at build time, no external request.
- `next/font/local` for self-hosted fonts.
- Both eliminate FOIT/FOUT. Don't use
@import or <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 events, NDJSON streams.
- Non-React consumers — anything that isn't a form submission from your own UI.
Inside API routes, follow rest-api-architect — same conventions for status codes, errors (RFC 7807), versioning, idempotency.
8. Auth
- Session-based for first-party apps —
next-auth (Auth.js v5) or a hand-rolled cookie session. httpOnly, Secure, SameSite=Lax cookies. - Server-side session check in every protected
layout.tsx or via middleware. Never trust the client. - `middleware.ts` at the project root runs on every matching route — auth redirects, locale detection, feature flags. Edge runtime.
- Server actions re-verify the session — middleware doesn't reach into action handlers reliably.
- External IdP (Auth0, Cognito, Clerk) is the typical upgrade path; same rest-api-architect §11 trade-offs apply.
9. Caching
Next has many cache layers (fetch data cache, full route cache, client router cache, unstable_cache). Be deliberate; profile before tuning. Full cache layers + override syntax in RECIPES § Cache layers reference.
Key rules:
- Don't cache user-specific data globally. Use
cookies() / headers() to opt out, or scope the cache to the user. - `revalidateTag` after mutations so server-rendered data refreshes when the underlying data changes.
- Most apps don't need cache surgery — trust the defaults until something's measurably slow.
10. Testing
Same approach as react-architect §10 plus Next-specific:
- Component tests with Vitest + React Testing Library. Server components are rendered to strings by Next — testable in isolation as async functions returning JSX.
- Server action tests are unit tests of the action function. Call it with a
FormData object, assert the return. - Playwright for end-to-end against
next start (built app) or next dev (in CI: build then start). - API route tests invoke the route handler directly with a
Request object; no HTTP server needed.
11. Deployment
- Vercel is the path of least resistance — first-class App Router, edge, streaming, ISR.
- Self-host via
next start behind a reverse proxy, or output: "standalone" for a tiny Docker image (per docker-architect). - Static export (
output: "export") when there's no dynamic server logic — drops half of Next's features but enables static hosting. - *Always set the `NEXT_PUBLIC_` env conventions** correctly — anything not prefixed is server-only.
12. Cross-skill ties
- react-architect — client patterns inside
"use client" components. - ui-ux-architect — Radix + Tailwind + a11y; design system tokens.
- rest-api-architect — API routes follow the same conventions as backend REST services.
- sql-architect — direct DB access from RSC uses the same pattern (raw SQL + parameter binding).
- security-reviewer — server actions and API routes are the new attack surface; same audit list applies.
- observability-architect — OpenTelemetry instrumentation via
@vercel/otel or generic OTel SDK; trace_id propagated through RSC boundaries.