đ Introduction
Understanding how a Next.js project is organized is essential for building maintainable, scalable applications. This tutorial provides a comprehensive breakdown of every important file and folder you'll encounter â from the root configuration files to deeply nested feature folders.
You'll learn what each directory is for, which files Next.js treats as special, and how to organize your own custom folders like components, lib, and hooks as your project grows. đī¸
Information
đ§ Understanding the Project Structure
A Next.js project is composed of conventional files and folders (which Next.js recognizes automatically) and custom folders (which you create for your own organization). Understanding this distinction is key to structuring a project well.
đ Root Directory
The root directory contains your project's configuration files, top-level folders, and package metadata. Here's a typical layout for a well-organized project:
đ The app Directory
The app directory is the core routing engine of the App Router. Every folder inside it can represent a route segment, and special files within those folders define UI, layouts, loading states, and more.
The public Directory
Static files such as images, fonts, and favicons are placed here. Anything inside public is served from the root URL â for example, public/logo.png becomes accessible at /logo.png.
The src Directory
An optional top-level folder that houses your application source code, keeping it separate from root-level config files like next.config.ts and package.json.
Tip
The components Directory
Holds reusable UI components shared across pages and features â buttons, modals, cards, form inputs, and layout pieces.
The lib Directory
Contains library code and third-party client configurations â database connections, API clients, authentication setup, and similar integrations.
lib/db.ts
import { PrismaClient } from "@prisma/client";
export const db = new PrismaClient();The hooks Directory
Stores custom React hooks that encapsulate reusable stateful logic across components.
hooks/useDebounce.ts
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}The styles Directory
Houses global stylesheets, Tailwind configuration extensions, and shared CSS modules not tied to a specific component.
The types Directory
Contains shared TypeScript type definitions and interfaces used across multiple parts of the application.
types/user.ts
export interface User {
id: string;
name: string;
email: string;
}The utils Directory
Holds small, pure utility functions â formatters, validators, and helpers that don't belong to any specific feature.
utils/formatDate.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat("en-US").format(date);
}The constants Directory
Centralizes fixed values used throughout the app â configuration constants, enum-like values, and shared strings.
constants/routes.ts
export const ROUTES = {
HOME: "/",
DASHBOARD: "/dashboard",
} as const;The services Directory
Encapsulates logic for communicating with external APIs or backend services, keeping network logic separate from UI code.
services/userService.ts
export async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}The actions Directory
Holds Server Actions â functions marked with "use server" that run exclusively on the server and can be called directly from client components.
actions/createPost.ts
"use server";
export async function createPost(formData: FormData) {
const title = formData.get("title");
// Save to database...
}đ§Š The middleware File
The middleware.ts file runs code before a request completes, allowing you to modify responses, handle redirects, or enforce authentication checks.
middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
return NextResponse.next();
}đ The instrumentation File
The instrumentation.ts file lets you run setup code when the server starts, commonly used for monitoring and observability tooling.
instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./instrumentation-node");
}
}đ Special Files in the app Directory
Next.js reserves certain filenames within the app directory for specific purposes, each rendering automatically at the appropriate point in the route lifecycle.
The app/layout.tsx File
Defines shared UI that wraps child pages, such as navigation bars, footers, and providers. The root layout is required.
app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}The app/page.tsx File
Defines the unique UI for a route and makes that route publicly accessible.
app/page.tsx
export default function HomePage() {
return <h1>Welcome Home</h1>;
}The app/loading.tsx File
Automatically wraps a page in a Suspense boundary, showing a loading UI while the page content streams in.
app/loading.tsx
export default function Loading() {
return <p>Loading...</p>;
}The app/error.tsx File
Automatically wraps a route segment in an error boundary, catching runtime errors and displaying fallback UI. It must be a Client Component.
app/error.tsx
"use client";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<p>Something went wrong!</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}The app/not-found.tsx File
Renders when the notFound() function is called or a route doesn't match any segment, providing a custom 404 experience.
app/not-found.tsx
export default function NotFound() {
return <h2>Page Not Found</h2>;
}The app/global-error.tsx File
Catches errors in the root layout itself. Since it replaces the root layout when active, it must include its own <html> and <body> tags.
The app/template.tsx File
Similar to layout.tsx, but creates a new instance for each child route on navigation, useful for re-triggering animations or effects.
The route.ts File
Defines a Route Handler, allowing you to build custom API endpoints using standard Request and Response APIs.
app/api/users/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ users: [] });
}Important
âī¸ Configuration Files
The next.config.ts File
Customizes Next.js's build and runtime behavior, including redirects, headers, image domains, and experimental features.
The package.json File
Defines project metadata, dependencies, and CLI scripts such as dev, build, and start.
The tsconfig.json File
Configures TypeScript compiler options, including path aliases like @/components for cleaner imports.
tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}The eslint.config.js File
Defines linting rules using ESLint's modern flat config format, ensuring consistent code quality across the codebase.
The .env Files
Store environment-specific variables. Next.js supports multiple variants for different environments:
| File | Purpose |
|---|---|
| .env | Default variables for all environments |
| .env.local | Local overrides, ignored by git |
| .env.development | Development-only variables |
| .env.production | Production-only variables |
Danger
đĸ Organizing Large Projects
As applications grow, a flat folder structure becomes harder to navigate. Larger projects benefit from more deliberate organization strategies.
Feature-Based Folder Structure
Instead of grouping files by type (all components together, all hooks together), a feature-based structure groups files by the feature they belong to.
Best Practice
â Best Practices
- Keep the app directory focused on routing â move business logic into lib, services, or actions.
- Use types for shared interfaces instead of duplicating them across files.
- Group related components, hooks, and logic together for feature-heavy applications.
- Use consistent naming conventions (camelCase for functions, PascalCase for components).
- Configure path aliases in tsconfig.json to avoid deep relative imports like ../../../components.
â ī¸ Common Mistakes
- Placing business logic directly inside page components instead of lib or services.
- Mixing page.tsx and route.ts in the same segment, which Next.js does not allow.
- Forgetting "use client" on interactive components that use state or event handlers.
- Overusing a single flat components folder for a large, multi-feature application.
â Frequently Asked Questions
No â the src directory is optional. It's purely a matter of preference for separating source code from config files.
No â app is a reserved, framework-recognized directory name and cannot be renamed.
Shared, fixed values belong in a dedicated constants directory, separate from utils or types.
đ¯ Summary
With a solid grasp of Next.js project structure, you're ready to build applications that stay organized and maintainable as they scale. Next, explore routing and data fetching in depth! đ