Next.js File Structure
Organize Next.js projects for maintainability and performance.
TL;DR
- 01Use app router for file-based routing with folders.
- 02Group related files with parentheses to exclude from routing.
- 03Colocate components, hooks, and utilities near where they're used.
Tips
- 01Organize by feature or domain first, not by file type — this keeps related code together and makes refactoring easier.
Warnings
- 01Don't create too many deeply nested folder levels — keep structures 3–4 levels deep for clarity.
App Router Basics
Create routes by adding a folder and a page.tsx file inside it.
app/ page.tsx # / route about/ page.tsx # /about route blog/ page.tsx # /blog route [slug]/ page.tsx # /blog/:slug routeMap URL path segments directly to folder names in the app directory.
app/users/[id]/posts/page.tsx → /users/:id/postsAdd a root layout.tsx to wrap every page with shared HTML structure.
// app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return <html lang="en"><body>{children}</body></html>; }Use page.tsx for visible UI and route.ts for API route handlers.
app/ api/ users/ route.ts # GET /api/users, POST /api/users users/ page.tsx # /users page rendered in the browserAdd a loading.tsx next to any page to show a Suspense fallback.
// app/blog/loading.tsx export default function Loading() { return <p>Loading posts...</p>; }Use dynamic [slug] segments to match variable URL parts — in Next.js 15, params is a Promise.
// app/blog/[slug]/page.tsx export default async function Post({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return <h1>Post: {slug}</h1>; }
Special Files
Use layout.tsx to share persistent UI across child routes without remounting.
// app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <Header /> {children} <Footer /> </body> </html> ); }Use error.tsx as an error boundary — it must be a Client Component.
// app/blog/error.tsx "use client"; export default function Error({ error, reset }: { error: Error; reset: () => void }) { return ( <div> <h2>Something went wrong</h2> <button onClick={reset}>Try again</button> </div> ); }Use loading.tsx to create an automatic Suspense boundary for the segment.
// app/blog/loading.tsx export default function Loading() { return <p>Loading posts...</p>; }Use not-found.tsx to render a custom 404 for each route segment.
// app/blog/[slug]/not-found.tsx export default function NotFound() { return <h1>Post not found</h1>; }Use template.tsx when the layout must remount on each navigation.
// app/dashboard/template.tsx export default function Template({ children }: { children: React.ReactNode }) { // Re-runs useEffect and re-mounts on every route change return <div>{children}</div>; }
Route Groups
Wrap folder names in parentheses to exclude them from the URL path.
app/ (marketing)/ page.tsx # / (still at root URL) about/page.tsx # /about contact/page.tsx # /contact (dashboard)/ layout.tsx # separate layout, no URL impact page.tsx # /dashboardGive each route group its own layout for auth vs app separation.
app/ (auth)/ layout.tsx # auth layout with centered card login/page.tsx # /login signup/page.tsx # /signup (app)/ layout.tsx # app layout with sidebar nav dashboard/page.tsxScope middleware-like access control by grouping protected routes.
app/ (protected)/ dashboard/page.tsx settings/page.tsx (public)/ page.tsx about/page.tsxDefine a layout per group without affecting the URL structure.
// app/(auth)/layout.tsx export default function AuthLayout({ children }: { children: React.ReactNode }) { return <main className="auth-bg">{children}</main>; }Combine route groups with parallel routes using the @ prefix.
app/ (dashboard)/ @analytics/page.tsx @overview/page.tsx layout.tsx
Module and Export Patterns
Use TypeScript path aliases to avoid deep relative imports across the project.
// tsconfig.json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }Create barrel files with index.ts to expose a clean public API for each feature.
// features/auth/index.ts export { LoginForm } from "./components/LoginForm"; export { useAuth } from "./hooks/useAuth"; export type { AuthUser } from "./types";Mark server-only modules with the server-only package to prevent client imports.
// lib/db.ts import "server-only"; export const db = new PrismaClient();Use a global types/ folder for shared TypeScript interfaces across features.
types/ api.ts # shared API response shapes user.ts # User interface used app-wide post.ts # Post interface used app-wideRe-export shared UI primitives from a single index to keep import paths short.
app/ ui/ Button.tsx Card.tsx Modal.tsx index.ts # export { Button, Card, Modal }
Scaling Patterns
Use feature folders to group all files for one domain in one place.
app/ features/ auth/ page.tsx components/ hooks/ utils.ts posts/ page.tsx components/ hooks/Keep all Next.js special files inside the feature folder.
features/users/ page.tsx layout.tsx error.tsx loading.tsx components/ UserCard.tsx hooks/ useUser.tsCreate barrel files with index.ts to simplify imports from feature folders.
// features/auth/index.ts export { LoginForm } from "./components/LoginForm"; export { useAuth } from "./hooks/useAuth";Place server actions in a dedicated actions/ folder inside each feature.
features/posts/ actions/ createPost.ts deletePost.ts components/ page.tsxPut cross-feature utilities in a root lib/ folder accessible to all routes.
lib/ format.ts # shared date/number formatting fetcher.ts # shared fetch wrapper constants.ts # app-wide constants
FAQ
Page-specific components should live next to the route they belong to, inside the same folder. Shared components used across multiple routes go in a top-level 'components' directory or a '_components' folder inside 'app'.
Folders wrapped in parentheses like '(marketing)' are excluded from the URL path but still organize your file system. Use them to group routes by section or layout without affecting the actual routes users see.
Use the 'app' directory — it's the current standard and enables React Server Components, nested layouts, and streaming. The 'pages' router is still supported but is considered legacy for new projects.
'layout.tsx' persists across navigations and doesn't remount, making it ideal for sidebars and headers. 'template.tsx' creates a fresh instance on every navigation, so use it when you need effects or animations to re-run between routes.
Yes — only files named as special files (page.tsx, layout.tsx, etc.) are treated as routes. You can safely place hooks, utilities, and components alongside your route files and they won't become endpoints.