From frontend-react
Next.js App Router project layout conventions — directory structure, co-location, feature-based organisation, shared components, hooks, utilities, monorepo patterns with Turborepo.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend-react:react-project-structureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```
my-app/
├── app/ ← App Router root
│ ├── layout.tsx ← Root layout (required: html + body)
│ ├── page.tsx ← Home route (/)
│ ├── loading.tsx ← Root loading UI
│ ├── error.tsx ← Root error boundary
│ ├── not-found.tsx ← 404 page
│ └── globals.css ← Global styles imported in root layout
├── components/ ← Shared UI components
├── hooks/ ← Shared custom hooks
├── lib/ ← Utility functions and configurations
├── types/ ← Shared TypeScript types
├── public/ ← Static assets (images, fonts, favicons)
├── e2e/ ← Playwright tests
├── next.config.ts
├── tsconfig.json
├── tailwind.config.ts ← if using Tailwind
├── vitest.config.ts
└── package.json
Placing all application code under src/ prevents root-level clutter and makes project root a configuration-only zone:
my-app/
├── src/
│ ├── app/
│ ├── components/
│ ├── hooks/
│ ├── lib/
│ └── types/
├── public/
├── next.config.ts
├── tsconfig.json
└── package.json
Next.js automatically detects and supports src/app. Choose one convention (with or without src/) and apply it consistently across the project.
Each folder under app/ is a route segment. Organise by domain, not by file type:
app/
├── layout.tsx ← Root layout
├── page.tsx ← /
├── (marketing)/ ← Route group — no URL segment
│ ├── layout.tsx ← Marketing-specific layout
│ ├── about/
│ │ └── page.tsx ← /about
│ └── pricing/
│ └── page.tsx ← /pricing
├── (app)/ ← Authenticated app group
│ ├── layout.tsx ← Auth-required layout
│ ├── dashboard/
│ │ ├── layout.tsx
│ │ ├── page.tsx ← /dashboard
│ │ ├── loading.tsx
│ │ └── error.tsx
│ └── settings/
│ ├── page.tsx ← /settings
│ └── profile/
│ └── page.tsx ← /settings/profile
└── api/
└── posts/
└── route.ts ← POST /api/posts
For medium-to-large apps, organise features as self-contained directories within app/:
app/
├── (features)/
│ ├── dashboard/
│ │ ├── _components/ ← Private components (only for this route)
│ │ │ ├── DashboardChart.tsx
│ │ │ └── MetricCard.tsx
│ │ ├── _hooks/ ← Private hooks
│ │ │ └── useDashboardData.ts
│ │ ├── page.tsx
│ │ └── loading.tsx
│ ├── posts/
│ │ ├── _components/
│ │ │ ├── PostCard.tsx
│ │ │ └── PostEditor.tsx
│ │ ├── [id]/
│ │ │ ├── _components/
│ │ │ │ └── CommentThread.tsx
│ │ │ └── page.tsx
│ │ └── page.tsx
│ └── users/
│ ├── _components/
│ └── [userId]/
│ └── page.tsx
Prefix private directories with _ to signal they are not route segments and should not be imported from outside the feature.
Keep related files together. A component, its tests, its styles, and its types live in the same directory:
components/
└── Button/
├── Button.tsx ← Component
├── Button.test.tsx ← Unit test
├── Button.module.css ← Styles (if CSS Modules)
├── Button.stories.tsx ← Storybook (optional)
└── index.ts ← Re-export for clean imports
Index file pattern for clean imports:
// components/Button/index.ts
export { Button } from "./Button";
export type { ButtonProps } from "./Button";
Organise shared components by category:
components/
├── ui/ ← Generic, reusable design primitives
│ ├── Button/
│ ├── Card/
│ ├── Input/
│ ├── Modal/
│ └── Table/
├── shared/ ← Higher-order, business-aware components
│ ├── UserAvatar/
│ ├── NotificationBell/
│ └── ConfirmDialog/
├── layouts/ ← Layout components
│ ├── PageLayout/
│ ├── SidebarLayout/
│ └── AuthLayout/
└── forms/ ← Form-specific components
├── FormField/
└── FileUpload/
components/ui/ components must be pure and have no business logic.components/shared/ may import from context or query hooks.components/shared/ inside components/ui/.hooks/
├── useDebounce.ts
├── useLocalStorage.ts
├── useMediaQuery.ts
├── useWindowSize.ts
└── useIntersectionObserver.ts
Rules:
useCurrentUser, usePosts) live inside their feature directory..test.ts file.lib/
├── api/
│ ├── client.ts ← Configured API client (e.g., axios instance)
│ └── endpoints.ts ← API endpoint constants
├── auth/
│ ├── session.ts
│ └── permissions.ts
├── db/
│ └── index.ts ← Prisma or Drizzle client (server-only)
├── validations/
│ ├── post.schema.ts ← Zod schemas
│ └── user.schema.ts
└── utils/
├── cn.ts ← clsx + tailwind-merge
├── date.ts ← Date formatting helpers
└── format.ts ← Number/currency formatting
Mark server-only files:
// lib/db/index.ts
import "server-only"; // throws at build time if imported in Client Component
import { PrismaClient } from "@prisma/client";
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const db =
globalForPrisma.prisma ?? new PrismaClient({ log: ["query"] });
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;
public/
├── images/
│ ├── logo.svg
│ └── og-image.png
├── fonts/ ← Self-hosted fonts (alternative: next/font)
└── icons/
└── favicon.ico
For optimised images, use next/image with the public/ directory:
import Image from "next/image";
<Image src="/images/logo.svg" alt="Company Logo" width={120} height={40} priority />
For fonts, prefer next/font over manual public/fonts/ to avoid FOUT:
// app/layout.tsx
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html className={inter.variable}><body>{children}</body></html>;
}
my-app/
├── next.config.ts ← Next.js configuration
├── tsconfig.json ← TypeScript configuration with path aliases
├── tailwind.config.ts ← Tailwind CSS configuration
├── vitest.config.ts ← Vitest configuration
├── playwright.config.ts ← Playwright E2E configuration
├── .eslintrc.json ← ESLint rules
├── .prettierrc ← Prettier formatting
├── .env.local ← Local env vars (gitignored)
├── .env.example ← Documented env vars (committed)
└── package.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/hooks/*": ["./src/hooks/*"],
"@/lib/*": ["./src/lib/*"],
"@/types/*": ["./src/types/*"]
}
}
}
For projects requiring multiple apps or shared packages:
my-monorepo/
├── apps/
│ ├── web/ ← Next.js app (frontend)
│ │ ├── app/
│ │ ├── package.json
│ │ └── next.config.ts
│ └── admin/ ← Separate Next.js admin panel
│ ├── app/
│ └── package.json
├── packages/
│ ├── ui/ ← Shared UI components
│ │ ├── src/
│ │ ├── package.json ← name: "@my-org/ui"
│ │ └── tsconfig.json
│ ├── config/ ← Shared configs (ESLint, TS, Tailwind)
│ │ ├── eslint/
│ │ ├── typescript/
│ │ └── tailwind/
│ └── utils/ ← Shared utility functions
│ ├── src/
│ └── package.json
├── turbo.json
└── package.json ← Workspace root
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
// packages/ui/package.json
{
"name": "@my-org/ui",
"version": "1.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
}
// apps/web/package.json
{
"dependencies": {
"@my-org/ui": "*"
}
}
Use barrel files (index.ts) for public APIs of directories. Avoid deep barrel nesting that obscures the module graph:
// components/ui/index.ts — DO
export { Button } from "./Button";
export { Card } from "./Card";
export { Input } from "./Input";
// DON'T: barrel files that re-export from other barrels create circular dependency risk
// components/index.ts → re-exports from components/ui/index.ts → anti-pattern for large apps
| Do | Don't |
|---|---|
| Group by domain/feature, not by file type | Create components/, hooks/, utils/ only at top level for all code |
| Co-locate tests, styles, and types with the component | Put all tests in a separate top-level __tests__/ directory |
Use _ prefix for private route-level component directories | Import route-private components from outside the feature |
Mark server-only modules with import "server-only" | Import DB/auth modules from Client Components |
Use path aliases (@/) for cross-feature imports | Use deep relative paths (../../../lib/utils) |
Use next/font for optimised font loading | Manually reference font files from public/ |
Define a consistent src/ or non-src/ convention | Mix both conventions within the same project |
npx claudepluginhub gagandeepp/software-agent-teams --plugin frontend-reactGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.