Files
kolaytercih/.claude/skills/nextjs-app-architecture/references/feature-folders.md
bilalgursen 820eea7eca refactor: ortak tipler @/types/yokatlas + features/rapor/types'a çıkarıldı, server-only korumaları eklendi (Faz 1)
- 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>
2026-08-08 01:06:06 +03:00

7.5 KiB

Feature folders

How to organize code under features/ and app/.

Folder layout

features/<domain>/
  <domain>-cache.ts     # Pure server tags + client query keys, when shared
  <domain>-queries.ts   # Server-only queries
  <domain>-actions.ts   # Server actions
  <domain>-query-options.ts # Client data-library query definitions, when needed
  components/           # Server + client components, each with its skeleton
  types/                # Feature-local public types, when needed by multiple files
  hooks/                # Actual feature-local React hooks and hook wrappers
  providers/            # Feature-local providers, only when the provider belongs to this domain

The folder name is the domain. The query and action filenames match the folder.

How many features?

Keep the feature list short. One folder per domain noun a user would recognize, not per database table or technical concern.

A new folder is justified when all three are true:

  1. The concept has its own queries.
  2. The concept has its own pages or routes.
  3. The concept is referenced from at least two other features.

If you find yourself making a feature folder with one query, one action, and one button, fold it into the parent feature instead.

Merge aggressively

Concepts that exist only in service of a parent entity belong inside the parent's feature folder:

  • A favorite or bookmark concept that only attaches to one parent entity (events, posts) → inside that parent's folder.
  • A like, repost, vote, or reaction concept on a piece of content → with that content's feature.
  • auth / session / current user → a single user folder, not split.
  • A cross-cutting concern like search folds into the primary content feature it queries (searchTracks in features/track/), not a features/search/ folder — the page composes it.

Concrete example: toggleFavorite is a mutation about events ("I favorite an event"), not its own domain. It lives in features/event/event-actions.ts, not features/favorite/favorite-actions.ts.

File naming

Filenames inside the folder always start with the folder name:

features/event/
  event-queries.ts
  event-actions.ts
  components/
    event-grid.tsx
    event-details.tsx
    favorite-button.tsx     ← OK: a component, not a "favorite" feature
  • <folder>-queries.ts — even if the file has only one query.
  • <folder>-actions.ts — even if a mutation is about a sub-concept.
  • Other <folder>-*.ts files are fine when the folder needs them (playlist-constants.ts, <folder>-schema.ts), as long as they keep the folder-name prefix. Don't put reusable domain types in a root *-types.ts file; use features/<domain>/types/ once a type is imported by multiple files.
  • Component files use any descriptive name. The component (not the feature) is the unit here.

Local vs shared support folders

Use a local support folder when the code belongs to one feature:

features/message/
  message-cache.ts
  message-query-options.ts
  types/
    message.ts
  hooks/
    use-message-mutations.ts
    use-message-draft.ts
  providers/
    message-draft-provider.tsx

Feature-owned client coordination stays with the feature. Place each file by the shape it exports and the domain it belongs to:

  • Client data-library cache contracts and query definitions follow references/single-page-applications.md.
  • Mutation wrappers that export hooks live in hooks/use-*.ts (use-message-mutations.ts exporting useSendMessage).
  • Browser-only state helpers live in hooks/ when their public API is a hook (use-thread.ts, use-message-draft.ts).
  • Client leaf components that coordinate a server write live in components/ next to the UI they support (mark-activity-read.tsx posts read activity in the background while the current /activity tree stays stable).

Keep the file prefix aligned with the feature folder when a file exports a grouped feature contract (workspace-cache.ts and workspace-query-options.ts, not activity-cache.ts in features/workspace/). Support code for a sub-concept still lives with the parent feature: reactions on messages belong in features/message/; unread activity chrome belongs in features/workspace/.

Promote only when there are real cross-feature consumers:

  • types/ at the project root — shared domain/application types imported across features.
  • hooks/ at the project root — shared client hooks used across features.
  • app/providers.tsx or components/*-provider.tsx — app-shell providers that wrap the whole app.

Avoid root-level miscellany like message-types.ts, shared-hooks.ts, or common-provider.tsx; the folder name should explain the scope.

What goes in components/

Each component file exports the main component plus its skeleton:

// features/event/components/event-grid.tsx
export async function EventGrid(...) { ... }
export function EventGridSkeleton() { ... }

Group related components in one file when they're always used together or one is a natural building block for another. A card and its grid live together. For example, genre-card.tsx exports GenrePill, GenreCard, GenreGrid, GenreGridSkeleton.

Split into separate files only when:

  • A component is consumed by multiple sibling components (one shared use is not enough — wait until three call sites need it).
  • A component is 'use client' and a sibling is a server component (the server/client boundary forbids sharing a file).

See references/components.md for inlining rules and the skeleton design checklist.

What pages do

Pages in app/ compose feature components with Suspense and transition wrappers. They never:

  • Contain domain logic
  • Define new components except thin transition wrappers (e.g. <ViewTransition>)
  • Fetch data directly
  • Inline route-specific components — extract them into the feature folder

See references/pages-suspense.md for page composition details.

Top-level layout

app/                  # Pages and layouts
features/             # Domain folders
components/           # UI primitives, theme, and app-shell singletons
hooks/                # Shared client hooks used across features
types/                # Shared cross-feature types only
lib/                  # Utilities and cohesive non-domain subsystems

lib/ holds flat helpers (db.ts, utils.ts) but may also group a cohesive non-domain subsystem in its own subfolder (e.g. lib/audio/ for an audio engine). Cross-feature client hooks live in top-level hooks/; a hook used by a single feature co-locates in that feature's hooks/. Types follow the same rule: shared types at top-level types/, feature-only exported types in features/<domain>/types/.

components/ holds:

  • components/ui/ — primitives. Low-level building blocks and action-prop components.
  • components/theme/ — theme provider and toggle, paired.
  • Top-level files (site-header.tsx, auth-gate.tsx, poller.tsx) — app-shell singletons used once each. No common/ folder — "common" is not a category. If a component is used everywhere it's a primitive (→ ui/); if it's used once it lives at the top level.
  • Purpose-named subfolders are fine when several files share a clear technical role — e.g. components/scripts/ for pre-hydration inline <script> seed components. This is distinct from the rejected common/: a scripts/ folder names what the files are, not "miscellaneous."

Conventions for filenames and casing live in the project's AGENTS.md. This skill doesn't impose one.