- Program/PUAN_TURLERI/DilimKey/RankResults/UniturGrubu/ProgramNetSatiri/ SihirbazFacetleri artık src/types/yokatlas.ts'te; lib/db.ts yalnızca sorgu fonksiyonları barındırıyor - RaporSonuc/RaporKapsam/RaporParams/MaskeliRapor src/features/rapor/types/ rapor.ts'e taşındı; ölü kalan TercihSchema/RaporSchema silindi - 11 lib dosyasına import "server-only" eklendi; scripts'in düz node ile import ettiği dosyalara (db, appdb, rapor-havuzu, tadimlik-havuzu, ai/client, ai/cagri, credits) bilinçli eklenmedi - Davranış değişikliği yok; pnpm build yeşil Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
10 KiB
name, description, license, metadata
| name | description | license | metadata | ||||
|---|---|---|---|---|---|---|---|
| nextjs-app-architecture | Build or audit Next.js 16 App Router apps using a next-beats-style React Server Components architecture. Use when scaffolding a new app, adding a feature, reviewing an existing app, refactoring route-loader-shaped pages into feature-owned async server components, deciding where queries/actions/components live, keeping pages synchronous with `params.then()`, placing Suspense boundaries, choosing the client/server boundary, designing skeletons, preventing CLS, or enabling Cache Components. Also use when the user asks about RSC composition, components receiving IDs instead of route params, `'use cache'`, `cacheTag`, `updateTag`, static-shell prerendering, or making an app easier for AI agents to modify. | MIT |
|
Next.js App Architecture
A workflow for building and auditing Next.js 16+ App Router apps so they follow one consistent, feature-sliced RSC architecture like next-beats.
Follow the workflow below step by step — it produces the invariants by construction. Load the reference a step names for the decision it depends on. Get framework mechanics (API signatures, config options, hook contracts) from the linked docs — don't restate or improvise them.
Prerequisite
Before changing a Next.js app, make sure the project is set up for AI agents to read version-matched docs. Follow the AI Coding Agents guide: prefer the project's AGENTS.md / bundled docs, and create or refresh them when missing. Then use this skill for architecture decisions.
Architecture target
Build pages that describe the loading experience, not pages that act like route loaders:
app/**/page.tsxandlayout.tsxare synchronous composition surfaces: static chrome, section headings,<Suspense>boundaries, error boundaries, and transition wrappers.- Feature components own their reads on the server. They receive minimal stable inputs (
id,slug,handle, parsed filter values) or already-fetched records, never rawparams/searchParams. - Queries and actions live in the feature folder. Components import queries; client leaves import actions directly.
- When server tags and client query keys describe the same feature data, a pure feature-local cache contract owns those identities.
- Skeletons mirror the component tree and live beside the component they represent.
Invariants (what every change must satisfy)
The non-negotiables. The workflow produces them; the final check verifies them.
- Pages compose, they never fetch. A page/layout imports feature components and places
<Suspense>. No queries, no domain logic, no route-specific components defined inline. - Pages stay synchronous. Use
params.then()/searchParams.then(), neverawait paramsat the top — so chrome paints into the static shell and only data-dependent sections suspend. - Feature components receive IDs, not route props. Resolve
params/searchParamsat the page boundary and pass plain values (id,slug,query) into features. - Async server component is the default.
'use client'only for hooks, event handlers, or browser APIs — and only on leaves, never on parents of server content. - The page owns the Suspense boundary; the feature owns the skeleton. Features never pre-wrap themselves in
<Suspense>. - Skeletons live in the same file as the component, exported alongside it, defined at the end.
FeedandFeedSkeletonare siblings. - Queries live in
<domain>-queries.ts(import 'server-only'); actions live in<domain>-actions.ts('use server'). The file name matches the folder, even for sub-concepts. - One feature folder per real domain noun. Sub-concepts (favorite, like, vote, bookmark, search) fold into the parent feature, never their own folder.
- Client components import actions directly — never receive a server action as a prop just to call it.
- Feature-local cache coordination stays with its domain. Put pure tags/keys in
<domain>-cache.ts, client query definitions in<domain>-query-options.ts, hook wrappers inhooks/use-*.ts, and tiny client leaves incomponents/; promote support code only after real cross-feature reuse.
Workflow
Run these in order for build-from-scratch, feature work, or audits. Each step names the reference to consult and the check it must pass.
- Choose mode.
- Build from scratch: sketch routes, real domain nouns, static shell, and expected loading groups before writing code.
- Audit/refactor: scan current
app/pages first; list every async page, page-level query import, route prop leak, missing Suspense boundary, and feature folder mismatch. →references/example.mdfor the target shape;references/feature-folders.mdfor placement. ✓ You know whether you are creating the architecture or converting loader-shaped code into it.
- Place the work. Decide the feature folder before writing anything.
→
references/feature-folders.md(decision tree + merge rules). ✓ A real domain, or folded into the right parent. - Write the query and, when a client cache shares its data, the cache contract. Put server reads in
<domain>-queries.tswithimport 'server-only'; keep shared tag/key identities in a pure<domain>-cache.ts. →references/queries-actions.md; for SWR/TanStack Query →references/single-page-applications.md; withcacheComponents: true, also →references/cache-components.md. ✓ Cache identities are defined once; server reads are server-only, cached/tagged/lifetimed under Cache Components, and return domain types rather than ORM rows. - Write the action (if there's a mutation).
features/<domain>/<domain>-actions.ts,'use server'at the top. →references/queries-actions.md. ✓ Re-checks auth, validates input, invalidates matching cache tags under Cache Components (refresh()only for justified dynamic reads), returns a discriminated union. - Build the component + skeleton.
features/<domain>/components/<name>.tsx: an async server component that awaits its own query from minimal props;'use client'only on interactive leaves. →references/components.md; for a client data library or strict-SPA/CSR feature →references/single-page-applications.md. ✓ Component receives IDs/handles/parsed filters or already-resolved records, notparams; skeleton is a sibling export at the end; no alias skeleton wrappers. - Compose the page.
app/<route>/page.tsx: synchronous,params.then(), place<Suspense fallback={<NameSkeleton />}><Name /></Suspense>, and wrap fallible sections in an error boundary. →references/pages-suspense.md. ✓ The page only composes; the boundary lives here, not in the feature; route props are resolved to plain values before reaching feature components. - Add interaction (if any): optimistic updates, pending state, toasts, confirmation.
→
references/ux-patterns.md. ✓ Feedback isn't doubled; destructive actions confirm; feature-owned client coordination stays with that feature instead of leaking into unrelated domains. - Verify against the checklist below before declaring done.
Verify before done
Inspect the diff against every invariant — each is checkable by reading the changed files:
- No page/layout imports a
*-queriesfile or defines a route-specific component inline. - Every page with params is synchronous and uses
params.then()/searchParams.then(). - Feature components receive plain IDs/handles/parsed filters or resolved records; no feature prop is named
paramsorsearchParams. - Every
<Suspense>for page data sits in the page; no feature pre-wraps itself. - Every component has its real
*Skeletonin the same file, at the end; no tiny skeleton aliases just to pass props. - Every
*-queries.tsstarts withimport 'server-only'; every*-actions.tswith'use server'. - With
cacheComponents: true, reusable reads use'use cache'/cacheTag/cacheLife, or'use cache: private'/'use cache: remote'when appropriate; any dynamic read is intentional and justified. - Mutations touching cached reads call
updateTag()/revalidateTag(..., 'max')for the matching tags;refresh()is not a substitute for tag invalidation. - Action files are named
<folder>-actions.ts; no sub-concept spawned its own folder. - Features with both server tags and client query keys define them once in a pure
<domain>-cache.ts; queries, actions, hydration, query options, and hooks import from it. - Feature-local client-support files sit in the smallest fitting place: query options at the feature root,
use-*hook wrappers inhooks/, leaf components incomponents/, and shared support only after real cross-feature reuse. 'use client'components are leaves — they import actions/hooks/providers, not async server components.- Mutations validate their input and invalidate the affected data.
Reference index
references/feature-folders.md— where code goes: folder layout, cache contracts, naming, and merging sub-concepts.references/queries-actions.md— query/action rules: server-only, dedup, validation, invalidation, return shape.references/components.md— server/client boundary, skeletons,use(), single-use helpers, live data.references/pages-suspense.md— page composition,params.then(), Suspense placement, CLS, error boundaries, prefetch.references/cache-components.md— thecacheComponentsdecisions: which reads to cache, which directive to use, how to invalidate.references/single-page-applications.md— client cache decisions: placement, server seeding, Cache Components coordination, hydration, and mutations.references/ux-patterns.md— interaction decisions: optimistic vs pending vs inline error, toasts, action-prop, confirmations.references/example.md— the next-beats reference app: invariant → file map, for seeing any rule in real code.