nextjs-domain-architecture
Skill0 starsChanged 16 days agoMITView on GitHub
Use when creating, moving, or naming files in a full-stack Next.js App Router app organized by business domain, with route groups per domain, underscore-prefixed private folders (_components, _actions, _queries), server actions, and TanStack Query. Best for large apps and teams. Covers layout, import boundaries, data fetching, caching, route handler exceptions, tests, and growth.
- Reads credentials
In this repository7 other files it gives its agents.
The file
Show its rules and text
We split this skill into the rules it gives an agent
23 rules. About 0 are common, 8 are distinctive, and 15 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 17
Read src/app/ and the shared src/ folders before creating files.
- Distinctive: line 51
Read references/full-tree.md when you need the expanded tree.
- Very specific: line 54
Put a file at the lowest level that needs it: route _components, then domain _components, then components/ui.
- Very specific: line 55
Start flat: skip _types/ or _schemas/ until a domain has more than a few of them.
- Distinctive: line 83
Read references/data-fetching.md when writing a read, a mutation, or a prefetch.
- Very specific: line 93
Add app/api/<name>/route.ts only for: an auth library endpoint (Better Auth), tRPC, webhooks, streaming or SSE endpoints, or endpoints for …
- Very specific: line 105
Put tests next to the file: invoice.service.test.ts.
- Very specific: line 106
Put setup and helpers in test/.
- Distinctive: line 107
Test services and actions without rendering.
- Very specific: line 108
Put end-to-end tests in e2e/ at the repo root, grouped by domain.
- Distinctive: line 112
Read references/growth-stages.md when deciding whether to restructure.
- Very specific: line 116
Use nextjs-layered-architecture.
- Very specific: line 120
Use the shared folders plus 2 to 6 domains.
- Distinctive: line 128
Read references/anti-patterns.md when reviewing a layout or before moving files.
- Very specific: line 130
Do not put non-route folders in app/ without a leading _.
- Very specific: line 131
Do not import another domain's _ folders.
- Very specific: line 132
Do not put business logic in actions or components.
- Distinctive: line 133
Do not add a route handler where a server action works.
- Very specific: line 134
Do not call fetch to your own app from a server component.
- Distinctive: line 135
Do not write query keys inline.
- Very specific: line 136
Do not make a query for data a server component can render with 'use cache'.
- Very specific: line 137
Do not put domain code in lib/, components/, or hooks/, or create shared/ or common/.
- Distinctive: line 138
Do not use PascalCase file names.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Next.js domain architecture
Full-stack Next.js App Router app, grouped by business domain. Each domain is a route group in app/ that owns its pages and private _ folders. Shared infrastructure lives in top-level src/ folders (components/, lib/, db/, and so on), outside app/. Mutations use server actions. Use for large apps. For small apps, use nextjs-layered-architecture.
Target version
Written for Next.js 16 (App Router, React 19, TypeScript, Cache Components) and TanStack Query v5 on 2026-10-30. Pages Router is out of scope.
Follow the repo first
Read src/app/ and the shared src/ folders before creating files. If the project already has conventions (domain names, casing, aliases, lint boundaries), use them. Check tsconfig.json for aliases and next.config.ts for cacheComponents.
Default directory layout
src/
app/
layout.tsx, global-error.tsx, not-found.tsx
(auth)/ # one route group per domain
(invoices)/
_components/ # UI shared by routes in this domain
_actions/ # "use server" mutations and client-callable reads
_queries/ # TanStack query keys, queryOptions, hooks
_services/ # server-only business logic and db access
_schemas/ # zod schemas shared by forms and actions
_types/
_public.ts # the only file other domains may import
invoices/
page.tsx
loading.tsx
_components/ # private to this route
[id]/page.tsx
api/ # route handlers, exceptions only (see below)
components/ # shared, domain-free UI
ui/ # primitives (Button, Dialog)
providers/ # client providers (query, theme)
lib/ # infrastructure and pure helpers
auth/ query/ # auth setup, query client
db/ # client, schema, migrations
hooks/ # generic hooks
config/ # env parsing and constants
types/ styles/ test/Read references/full-tree.md when you need the expanded tree.
- Underscore-prefixed folders are private and never routable. Use
_for every non-route folder insideapp/. - Put a file at the lowest level that needs it: route
_components, then domain_components, thencomponents/ui. - Start flat: skip
_types/or_schemas/until a domain has more than a few of them. - Route group names are domain nouns in kebab-case. They do not appear in the URL.
Naming conventions
- Files and folders: kebab-case. Next special files keep their fixed names (
page.tsx,layout.tsx,route.ts). - Components:
InvoiceTableininvoice-table.tsx. Named exports, except where Next requires a default export (pages, layouts). - Actions:
create-invoice.action.ts, exportingcreateInvoiceAction. - Queries:
invoice.queries.ts, exportinginvoiceKeys,invoiceListOptions,useCreateInvoice. - Services:
invoice.service.ts. Schemas:invoice.schema.ts. Types:invoice.types.ts. - Hooks:
use-thing.ts. - Imports:
@/maps tosrc/. Within a domain use at most../or../../. Across domains use@/app/(billing)/_public.
Dependency direction
page -> _components -> _queries -> _actions -> _services -> lib, db- Shared folders (
components,hooks,lib,db,config,types) never import fromapp/. _servicesimportdb,lib,config,_schemas,_types. Mark withimport 'server-only'. Never import_actions,_queries, or components._actionsvalidate, authorize, call_services, then revalidate. No business logic._queriescall_actionsonly. They hold no server code.- Server components may call
_servicesdirectly for reads. - Another domain imports only
_public.ts. Never its_folders.
Data and mutations
Read references/data-fetching.md when writing a read, a mutation, or a prefetch.
- Mutations: server actions. Use
useActionStatefor plain forms. UseuseMutationwhen the client cache must update. - Server cache:
'use cache'withcacheTagin_services. Actions callupdateTagorrevalidateTag. - TanStack Query is for pagination, infinite scroll, lazy loading, polling, and client-side mutations. Do not use it for static, SEO-critical, or read-once data.
- Prefetch in the server page and wrap the client part in
HydrationBoundary. Await only above-the-fold data. Stream the rest by not awaitingprefetchQuery. - Query functions call server actions, not
fetch.
Route handlers: exceptions only
Add app/api/<name>/route.ts only for: an auth library endpoint (Better Auth), tRPC, webhooks, streaming or SSE endpoints, or endpoints for mobile and other platforms. Keep handlers thin and call _services. Everything else is a server action.
Where things live
- Config:
config/env.tsparsesprocess.envwith zod. Split server andNEXT_PUBLIC_vars. Readprocess.envnowhere else. List every var in.env.example. - Query client:
lib/query/get-query-client.ts. Provider incomponents/providers/. - Auth, db client:
lib/auth/,db/. Domain tables and queries stay in the domain's_services. - Proxy (formerly middleware):
src/proxy.ts. - Client state: local first. Domain state in the domain. App-wide state in
lib/orhooks/.
Tests
- Put tests next to the file:
invoice.service.test.ts. - Put setup and helpers in
test/. - Test services and actions without rendering. Test components with a query client wrapper.
- Put end-to-end tests in
e2e/at the repo root, grouped by domain.
Structure at three sizes
Read references/growth-stages.md when deciding whether to restructure.
Prototype
Use nextjs-layered-architecture. Do not start with domains.
Mid-size
Use the shared folders plus 2 to 6 domains. Keep each domain flat. Signal to move up: a domain passes about 25 files, two teams edit one domain, or domains import each other's _ folders.
Large
Split domains in two. Enforce boundaries with lint rules. Signal to extract packages: a second app needs the shared folders, or a mobile app needs the API.
Anti-patterns
Read references/anti-patterns.md when reviewing a layout or before moving files.
- Do not put non-route folders in
app/without a leading_. - Do not import another domain's
_folders. Use_public.ts. - Do not put business logic in actions or components. Use
_services. - Do not add a route handler where a server action works.
- Do not call
fetchto your own app from a server component. - Do not write query keys inline. Use the key factory.
- Do not make a query for data a server component can render with
'use cache'. - Do not put domain code in
lib/,components/, orhooks/, or createshared/orcommon/. - Do not use PascalCase file names.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

