Next.js Architecture Enforcement
Overview
Enforces official Next.js architecture rules before code changes. Validate that the target is actually a Next.js project, determine whether App Router is in use, then apply strict rules for routing, Server and Client Component boundaries, server-first data fetching, Server Actions, Route Handlers, Proxy, and environment setup.
This skill is official-first. Treat the Next.js documentation as the source of truth. If a repo-local convention is stricter than the framework, label it clearly instead of silently presenting it as a framework rule.
OPERATING MODE: This skill is self-contained. Do not block on external orchestration just to apply architecture rules. If the user wants exhaustive verification, keep verifying. Otherwise proceed with this skill's own validation flow.
IMPORTANT: App Router is the default path for this skill. If the repo is Pages Router only, apply only the shared platform and boundary checks and do not force App Router-only file conventions unless the user is migrating or explicitly adding
.
IMPORTANT: Prefer Server Actions for internal UI writes, especially forms and app-originated mutations. Use Route Handlers when the surface is genuinely HTTP-native, such as webhooks, feeds, CORS-sensitive endpoints, or machine-readable/public endpoints.
IMPORTANT: Treat every Server Action as a reachable POST entry point. Validation, authentication, authorization, and return-value filtering must happen inside the action or the delegated server-only data layer, not only in the page that renders the form.
Quick Surface Chooser
Use this table before reading the full gates:
| If the task sounds like... | Default surface | Do not default to... |
|---|
Add a form or internal app mutation in App Router
| Server Action | |
Add a webhook, feed, CORS endpoint, or public machine-readable endpoint
| Route Handler | Server Action |
Fetch initial page data for UI
| Server Component | Client-first fetching without a real need |
Need redirect logic before render across many requests
| or Proxy, with Proxy last | Server Action |
Client Component imports DB code or private env
| move the code behind a server-only boundary | leaving secrets in client-reachable code |
Pages Router only repo and no migration requested
| shared Next.js safety checks only | forcing App Router file conventions |
If the task matches one of these rows, start there, then read the linked rule file in Step 2 for detail.
Trigger Examples
Positive
Audit this Next.js app before I add more App Router routes.
Refactor a Next.js feature so Server Components, client boundaries, caching, and server actions follow the official docs.
Add a Next.js Route Handler or Server Action and keep the architecture compliant.
Negative
Create a generic React architecture guide.
Review a Remix or TanStack Start app.
Boundary
-
Make a tiny copy-only text change in a Next.js page.
Direct editing can be enough if no architectural boundary is affected, but touched files still need a quick boundary check.
-
This repo is Pages Router only and I am not migrating to App Router.
This skill still applies for shared Next.js platform, env, and boundary checks, but App Router-specific file rules must be relaxed.
Step 1: Project Validation
Before any work, confirm a Next.js project and detect router mode:
bash
rg -n '"next"' package.json
find . -maxdepth 3 \( -path './app' -o -path './src/app' -o -path './pages' -o -path './src/pages' \)
test -f next.config.ts -o -f next.config.mjs -o -f next.config.js
Interpretation:
- No dependency found: stop, this skill does not apply.
- or present: full App Router mode.
- or present without App Router: shared Next.js mode only.
- Mixed and : prefer App Router rules for touched code and avoid breaking legacy code without explicit migration intent.
Step 2: Read Architecture Rules
Load the detailed rules reference:
REQUIRED: Read
in this skill directory before writing code.
Then read the relevant rule files for the change:
- - App Router structure, special files, route groups, private folders, and segment boundaries
- - Server vs Client Components, , providers, serializable props, and
- - server data fetching, streaming, cache intent, dynamic rendering triggers, and revalidation
- - , validation, auth, authz, DAL delegation, revalidation, redirect ordering, and side-effect rules
- - when is justified, method handling, caching defaults, and HTTP-only surfaces
- - environment variables, , , Proxy, and deployment-sensitive setup
If framework behavior may have drifted, also read:
references/official/nextjs-docs.md
- official doc map for the rules this skill depends on
Step 3: Pre-Change Validation Checklist
Before writing any code, verify the planned change against these gates:
Brownfield Adoption Rule
- Do not treat every untouched legacy deviation as an immediate project-wide failure.
- Safety and boundary issues still block immediately, especially in touched files.
- Legacy code can remain in place when the task is local and non-migratory.
- Any file you touch should be brought into compliance unless that would require a materially risky migration.
Gate 1: Routing and File Conventions
| Check | Rule |
|---|
| , , , , , or placed outside the expected segment structure? | BLOCKED |
| and created at the same route segment? | BLOCKED |
| App Router feature work done in even though already exists for that surface? | BLOCKED unless explicitly requested |
| Route groups or private folders used without understanding URL impact? | WARNING. does not affect URL, stays private |
| Segment needs loading/error/not-found UX but no boundary exists? | WARNING. Add , , or intentionally |
Gate 2: Server and Client Boundaries
| Check | Rule |
|---|
| Interactive component missing ? | BLOCKED |
| added high in the tree without need? | BLOCKED. Keep client boundaries as narrow as possible |
| Client Component imports server-only code, secrets, DB clients, or private values? | BLOCKED |
| Server-only helper missing or equivalent protected placement? | WARNING. Add a clear server-only boundary |
| Client Component props include broad DB records or non-serializable values? | BLOCKED |
| Context provider placed at the document root when a deeper boundary works? | WARNING. Render providers as deep as possible |
Gate 3: Data Fetching and Caching
| Check | Rule |
|---|
| Initial page data fetched in a Client Component when a Server Component can do it? | BLOCKED unless there is a real client-only need |
| Layout reads uncached runtime data and blocks same-segment without a closer boundary? | BLOCKED |
| Cache behavior is accidental or unclear? | BLOCKED. Choose and explain the cache strategy |
| Sensitive or privileged reads happen outside a DAL/server-only module without justification? | WARNING for prototypes, BLOCKED for production-oriented code |
| Mutation completes without , , redirect, or another freshness strategy where the UI depends on new data? | BLOCKED |
Gate 4: Server Actions
| Check | Rule |
|---|
| Internal UI mutation or form submit implemented with even though a Server Action fits? | BLOCKED unless real HTTP semantics are required |
| Action trusts form data, params, headers, or search params without validation or re-verification? | BLOCKED |
| Action relies only on page-level auth checks? | BLOCKED. Re-authorize inside the action |
| Action returns raw database rows or broad internal objects? | BLOCKED |
| Action performs DB or secret-heavy work directly when a server-only DAL exists or should exist? | WARNING for small code, BLOCKED for repeated domain logic |
| Action mutates during rendering instead of from an explicit action path (, event, transition)? | BLOCKED |
| called before required revalidation? | BLOCKED. Revalidate first, then redirect |
Gate 5: Route Handlers and Proxy
| Check | Rule |
|---|
| Internal UI mutation implemented as even though a Server Action fits better? | BLOCKED unless real HTTP semantics are required |
| Route Handler used for webhooks, feeds, CORS, or public machine endpoints? | ALLOWED |
| Route Handler uses to forward like Proxy? | BLOCKED |
| Proxy added when , , headers, or render-time logic would be enough? | BLOCKED. Proxy is last resort |
| not placed at project root or root level next to or ? | BLOCKED |
| Proxy matcher is missing or too broad for the actual need? | BLOCKED |
Gate 6: Platform and Environment
| Check | Rule |
|---|
| files assumed to load from ? | BLOCKED. They belong at project root |
| Client code reads non- env vars? | BLOCKED |
| Runtime client env needed but treated as build-time inlined config? | BLOCKED. Expose via server path/API instead |
Multi-proxy or reverse-proxy deployment uses Server Actions without checking serverActions.allowedOrigins
needs? | WARNING |
| Next config toggles caching, routing, or server action behavior without clear intent? | BLOCKED |
| Typed route safety would materially reduce routing mistakes but is ignored in a TypeScript codebase? | WARNING. Consider enabling it intentionally |
Step 3.5: Auto-Remediation Policy
Auto-fix directly when the issue is local, reversible, and low-risk.
- narrow an overly broad boundary
- add , , or for a touched segment
- move privileged reads into a server-only helper or DAL
- add markers and tighten client props
- add missing revalidation after a Server Action mutation
- move a misused internal mutation to a Server Action when the change is small and local
- tighten Proxy matcher scope or move simple redirects into
- correct / usage and explicit config wiring
Do not auto-apply broad or potentially breaking migrations without explicit justification.
- mass route tree rewrites
- Pages Router to App Router migrations across large surfaces
- sweeping cache model changes
- turning many Route Handlers into Server Actions in one pass
- deployment-sensitive Server Action origin or encryption-key changes
Step 4: Implementation
Carry these acceptance criteria into the active task:
text
- [ ] Next.js project mode validated before editing
- [ ] App Router rules applied only where they actually fit
- [ ] Routing files live in the correct route segment structure
- [ ] Server and Client Component boundaries are explicit and minimal
- [ ] Client code cannot reach server-only data, env, or modules
- [ ] Data fetching and caching strategy is intentional
- [ ] Server Actions are the default surface for internal UI writes
- [ ] Server Actions validate input, re-authorize, and return minimal data
- [ ] Route Handlers exist only for real HTTP-native needs
- [ ] Proxy is used only when simpler surfaces are insufficient
- [ ] Environment handling and next.config setup are boundary-safe
Step 5: Post-Change Verification
After writing code, verify:
- project mode still matches the edited surface (, , or mixed)
- route segment file placement is valid
- boundaries are as small as possible
- client code does not import server-only modules or private env
- data freshness after mutations is explicit (, , redirect flow, or documented alternative)
- Route Handlers and Proxy usage are still justified
- , env loading, and deployment-sensitive settings remain coherent
Quick Reference: App Router Shape
text
app/
├── layout.tsx
├── page.tsx
├── dashboard/
│ ├── page.tsx
│ ├── loading.tsx
│ ├── error.tsx
│ ├── not-found.tsx
│ ├── _components/
│ └── _lib/
├── api/
│ └── webhooks/
│ └── route.ts
└── (marketing)/
└── about/
└── page.tsx
Key meaning:
- organizes routes without affecting the URL
- is a private implementation folder and does not become a route segment
- is for HTTP handling, not page UI
- , , and are route-segment boundaries, not general-purpose components