- Use Next.js 16 App Router only (
app/orsrc/app/); never usepages/.- If
src/app/exists, treat it as the routing root. - If only
app/exists, treatapp/as the routing root.
- If
- Recommended structure (root =
app/orsrc/app/):root/– routes, layouts, route handlers (API).components/ui/– base/atomic UI components.components/features/– feature/domain-specific components.lib/– utilities, API/DB clients, validation (Zod), configs.actions/– Server Actions decoupled from components.hooks/– React hooks (client-side only).types/– shared TypeScript types.public/– static assets.
- Every component is a Server Component by default; add
'use client'only where there are hooks or browser APIs. - Use PascalCase for components and camelCase for variables, functions and hooks (
useSomething). - Use TypeScript types for props and object shapes; avoid
anyand keep strict TypeScript. - Prefer named exports for components and utilities; use
export defaultonly forapp/**/page.tsx. - File organization:
- Route/layout files follow Next conventions (
page.tsx,layout.tsx,loading.tsx,error.tsx,not-found.tsx). - Components in
PascalCase.tsxorkebab-case.tsxaccording to your chosen standard, but keep it consistent across the project.
- Route/layout files follow Next conventions (
- Use absolute imports with
@/configured intsconfig.json; avoid deep relative paths (../../..). - Centralize HTTP/REST calls in
lib/modules (for example,lib/api.ts); avoid rawfetchspread across components. - In Server Components, do data fetching with
async/awaitfollowing Next 16 conventions (asyncparamsandsearchParams). - For caching:
- Default: no cache for server fetch in recent versions.
- Explicit cache:
fetch(url, { cache: 'force-cache' }). - Revalidation:
fetch(url, { next: { revalidate: 3600 } }).
paramsandsearchParamsin pages, layouts and route handlers are Promises:- Always use
const { slug } = await params;before accessing.
- Always use
headers()andcookies()are async as well:- Use
const cookieStore = await cookies();.
- Use
- Use route groups and nested layouts to organize sections:
- Examples:
app/(marketing)/,app/(app)/dashboard/.
- Examples:
- Use React Server Components by default; push
'use client'to leaf components that are truly interactive. - Use Server Actions for mutations and form submission whenever possible, avoiding manual API Routes for simple CRUD.
- Use the new hooks:
useActionStateto manage form action state.useFormStatusfor loading and feedback inside<form>.use()to read Promises or Contexts where appropriate.
- API Routes (when really needed) live in
app/api/**/route.ts.- Export HTTP methods (
GET,POST, etc.) asasyncfunctions usingNextResponse. - Validate payload and query with Zod or an equivalent schema before using.
- Export HTTP methods (
- Server Actions:
- Always validate input with Zod.
- Keep sensitive logic on the server, without leaking details to the client.
- Use
server-only(Taint API) to guarantee that sensitive functions (like DB access) are never imported in client-side code.
- Use Tailwind CSS as the styling foundation; keep
globals.csslean (reset, fonts, global tokens). - Combine Tailwind with
clsxandtailwind-mergefor conditional classes and to avoid conflicts. - Images:
- Always use
next/imagewithwidth,heightorfill, andpriorityfor LCP when needed.
- Always use
- Fonts:
- Use
next/fontfor automatic optimization and to avoid layout shift.
- Use
- Use Suspense, streaming and Partial Prerendering (PPR):
- Wrap dynamic sections in Suspense boundaries to enable streaming and progressive loading.
- Install and configure shadcn/ui using the official Next.js guide, ensuring App Router + Tailwind setup is complete before running the CLI.
- Use the latest React 19–compatible preset or configuration when initializing shadcn/ui, following the React 19 notes from the official docs.
- Co-locate shadcn components under
components/ui/, keeping them as Server Components by default, and add'use client'only when using client-only hooks or animations. - Do not edit the core logic of generated shadcn components; extend them via wrappers/composition when customization is needed to simplify future updates.
- Always keep accessibility props (labels, roles, aria-attributes) provided by shadcn primitives and do not strip them when styling.
- Use Framer Motion only in Client Components with
'use client', importing fromframer-motionand wrapping animated elements withmotion.*. - Prefer variants and
transitionobjects defined outside JSX for reuse and to keep components readable. - Use
AnimatePresenceonly when components are conditionally mounted/unmounted and need exit animations, and avoid nesting multipleAnimatePresenceunnecessarily. - Keep animation values GPU-friendly (translate, scale, opacity, rotate) and avoid animating expensive properties like
box-shadowand layout-heavy CSS whenever possible. - Combine Framer Motion with shadcn/ui by wrapping shadcn components in
motion()(for example,const MotionButton = motion(Button)), ensuring focus states, aria attributes and keyboard navigation remain intact.
- Runtime and package manager:
- Use Bun v1.3+ as the default runtime:
buninstead ofnode. - Install:
bun install(do not use npm/yarn/pnpm). - Scripts:
bun run <script>.
- Use Bun v1.3+ as the default runtime:
- Dev server and frameworks:
- Next:
bun --bun next dev.
- Next:
- Files:
- Use
Bun.file()andBun.write()instead offs/fs.promises.
- Use
- Custom HTTP server:
- Prefer
Bun.serve()for auxiliary services independent from Next.
- Prefer
- Utilities:
Bun.password(argon2) for hashing.Bun.sleep(ms)instead ofsetTimeoutin scripts.Bun.envfor environment variables.Bun.gzipSync/Bun.deflateSyncfor compression.
- Tests:
- Use
bun testwithbun:test(describe,test,expect,mock).
- Use
- Follow the existing ESLint/Prettier configuration of the project strictly.
- Do not introduce new dependencies or architectural patterns without explicit instruction.
- Keep components small, cohesive and reusable, prioritizing clarity over premature abstraction.