Cấu trúc thư mục Next.js App Router: Các phương pháp hay nhất & Kiến trúc doanh nghiệp (2026)

Table of Contents
Chuỗi kiến trúc Next.js hiện đại
Khi các ứng dụng React mở rộng quy mô, cấu trúc thư mục của bạn chuyển từ vấn đề thẩm mỹ cá nhân sang một quyết định kiến trúc nền tảng. Với sự ra đời của Next.js App Router, các mô hình tư duy cũ đã bị phá vỡ. Chúng ta đã chuyển từ thư mục pages/ cũ (thường trở thành nơi chứa cả định tuyến và các thành phần UI) sang một hệ thống định tuyến dựa trên quy ước, có ý kiến riêng bên trong app/.
Tuy nhiên, sự linh hoạt của App Router, đặc biệt là khả năng hỗ trợ colocation, là một con dao hai lưỡi. Nếu không có các rào cản cấu trúc rõ ràng, thư mục app/ của bạn sẽ nhanh chóng phình to với hàng trăm tệp hỗn hợp, các dependency vòng tròn và rò rỉ ranh giới Server/Client.
Trong hướng dẫn này, chúng tôi sẽ phân tích các cấu trúc thư mục đã được kiểm nghiệm trong sản xuất cho các ứng dụng Next.js App Router, từ colocation cơ bản đến Thiết kế phân lớp tính năng (FSD) cho các ứng dụng doanh nghiệp.
Sự thay đổi mô hình của App Router: Định tuyến so với Logic nghiệp vụ
App Router coi thư mục app/ nghiêm ngặt là một bộ định tuyến HTTP và bộ soạn trang. Chỉ các tệp quy ước đặc biệt mới có thể truy cập công khai:
page.tsx: UI duy nhất cho một route, có thể truy cập qua URL.layout.tsx: UI được chia sẻ bao bọc các phần tử con và duy trì trạng thái giữa các lần điều hướng.loading.tsx: Dự phòng streaming được hỗ trợ bởi React Suspense.error.tsx: Ranh giới lỗi phía client bắt các lỗi thời gian chạy trong các cây con route.not-found.tsx: UI cho các trạng thái404.route.ts: Endpoint API phía máy chủ (GET, POST, PUT, DELETE).
Mọi thứ khác bên trong một thư mục route về mặt kỹ thuật có thể được colocate:
app/
└── dashboard/
├── page.tsx
├── layout.tsx
├── DashboardGraph.tsx # Colocated component
├── dashboard.module.css # Colocated styling
└── dashboard.test.tsx # Colocated unit test
Cạm bẫy của Colocation thuần túy
Mặc dù việc colocate mọi thứ bên trong app/ hoạt động tốt cho các MVP, nhưng nó lại thất bại trong các dự án vừa và lớn:
- Nút thắt về khả năng tái sử dụng: Khi một route khác (ví dụ:
app/reports/page.tsx) cầnDashboardGraph, nó nằm ở đâu? Di chuyển nó lên trên sẽ tạo ra các đường dẫn import không thể đoán trước. - Cây tệp bị ô nhiễm: Một thư mục
app/với 30 route và 200 tệp được colocate khiến việc tìm kiếm route trong một sự cố trực ban trở nên khó khăn. - Ranh giới bị mờ: Các nhà phát triển vô tình thêm
'use client'vào các wrapper cha, làm giảm hiệu suất streaming của Server Component.
3 Mô hình kiến trúc: Mô hình nào phù hợp với quy mô của bạn?
| Mô hình | Phù hợp nhất cho | Điểm mạnh | Đánh đổi |
|---|---|---|---|
| 1. Ứng dụng Colocated | MVP & Trang web nhỏ (<10 route) | Thiết lập nhanh, không tốn công sức | Import lộn xộn khi chia sẻ mã giữa các route |
| 2. Phân lớp phẳng | Startup & Ứng dụng cỡ trung (10-40 route) | Quen thuộc với các nhóm React tiêu chuẩn | Các thư mục components/ và hooks/ khổng lồ trở thành ngăn kéo chứa đồ linh tinh |
| 3. Phân lớp tính năng (FSD) | Doanh nghiệp & Quy mô lớn (40+ route, đa nhóm) | Ranh giới một chiều nghiêm ngặt, các tính năng tách rời | Đường cong học tập ban đầu cho các nhà phát triển mới |
Nhóm Route so với Thư mục riêng tư
Next.js cung cấp hai quy ước thư mục quan trọng giúp tách biệt rõ ràng định tuyến của bạn khỏi logic nội bộ:
1. Nhóm Route (folder)
Bao bọc tên thư mục trong dấu ngoặc đơn sẽ tổ chức các route một cách hợp lý mà không làm thay đổi URL công khai:
app/
├── (marketing)/
│ ├── layout.tsx # Marketing layout (Navigation bar + Footer)
│ ├── page.tsx # Served at: /
│ └── pricing/
│ └── page.tsx # Served at: /pricing
└── (dashboard)/
├── layout.tsx # Dashboard layout (Sidebar + Account switcher)
├── settings/
│ └── page.tsx # Served at: /settings
└── analytics/
└── page.tsx # Served at: /analytics
Tại sao nên sử dụng Nhóm Route?
- Tạo nhiều layout gốc (ví dụ: trang đích công khai so với bảng điều khiển được xác thực bảo vệ).
- Nhóm các route theo quyền sở hữu của nhóm mà không làm thay đổi các URL nhạy cảm với SEO.
- Cô lập trạng thái tải và lỗi cho từng phần.
2. Thư mục riêng tư _folder
Thêm tiền tố gạch dưới vào tên thư mục sẽ yêu cầu Next.js loại bỏ hoàn toàn thư mục đó khỏi định tuyến:
app/
├── (dashboard)/
│ └── analytics/
│ ├── _components/ # OPTED OUT of routing (/analytics/_components is 404)
│ │ ├── Chart.tsx
│ │ └── MetricCard.tsx
│ ├── _lib/
│ │ └── calculations.ts
│ └── page.tsx # Served at: /analytics
Sử dụng _components hoặc _lib khi bạn muốn colocate mã chỉ riêng tư cho một route duy nhất và sẽ không bao giờ được sử dụng lại ở nơi khác.
Kiến trúc doanh nghiệp được đề xuất: Thiết kế phân lớp tính năng
Đối với các ứng dụng sản xuất quan trọng, chúng tôi tách định tuyến Next.js khỏi logic nghiệp vụ miền bằng kiến trúc 4 lớp:
src/
├── app/ # Layer 1: Next.js routing, layouts, and page composition
├── features/ # Layer 2: Business actions and user workflows
├── entities/ # Layer 3: Domain data models and business entities
└── shared/ # Layer 4: Foundation (UI primitives, API clients, helpers)
Trực quan hóa biểu đồ phụ thuộc
Quy tắc quan trọng nhất trong Thiết kế phân lớp tính năng là các phụ thuộc một chiều. Mã chỉ có thể import từ các lớp nghiêm ngặt bên dưới nó:
Cách mỗi lớp hoạt động
1. Lớp app/: Thành phần thuần túy
Thư mục app/ không chứa logic nghiệp vụ nào. Nó chỉ:
- Khai báo các route và layout lồng nhau.
- Lấy dữ liệu bên trong Server Components.
- Kết hợp các tính năng và thực thể vào mẫu trang.
// src/app/(dashboard)/analytics/page.tsx
import { Suspense } from 'react';
import { AnalyticsMetrics } from '@/features/usage-metrics';
import { getCurrentUser } from '@/entities/user';
import { SkeletonLoader } from '@/shared/ui';
export default async function AnalyticsPage() {
const user = await getCurrentUser();
return (
<main className="container mx-auto p-6 space-y-6">
<header>
<h1 className="text-3xl font-bold tracking-tight">Analytics</h1>
<p className="text-muted-foreground">Welcome back, {user.name}</p>
</header>
<Suspense fallback={<SkeletonLoader count={4} />}>
<AnalyticsMetrics organizationId={user.orgId} />
</Suspense>
</main>
);
}
2. Lớp features/: Luồng công việc người dùng
Các tính năng chứa các luồng người dùng tương tác mang lại giá trị kinh doanh có thể đo lường được (ví dụ: auth-flow, checkout, team-invitation).
src/features/auth-flow/
├── ui/ # React client/server components
│ ├── LoginForm.tsx
│ └── SocialButtons.tsx
├── api/ # Colocated Server Actions
│ └── authenticate.ts
├── model/ # Local state, validation schemas
│ └── schema.ts
└── index.ts # STRICT Public API export
Luôn export thông qua index.ts để giữ các nội bộ riêng tư:
// src/features/auth-flow/index.ts
export { LoginForm } from './ui/LoginForm';
export { authenticateWithOAuth } from './api/authenticate';
3. Lớp entities/: Các thực thể nghiệp vụ cốt lõi
Các thực thể đại diện cho các khái niệm trong thế giới thực trong miền của bạn (ví dụ: User, Workspace, Invoice). Chúng chứa:
- Các hàm truy vấn cơ sở dữ liệu hoặc các endpoint API.
- Các giao diện TypeScript và schema Zod.
- Các phần tử UI gắn liền với một thực thể cụ thể (ví dụ:
<UserAvatar />,<PlanBadge />).
4. Lớp shared/: Nền tảng có thể tái sử dụng
Thư mục shared/ chứa mã không phụ thuộc vào miền mà về lý thuyết có thể được xuất bản dưới dạng một gói npm nội bộ:
shared/ui: Các nguyên thủy nhưButton,Modal,Input,Dropdown(thường là các wrapper Shadcn / Radix).shared/lib: Các bộ định dạng ngày, tiện ích toán học, client mạng.shared/config: Xác thực biến môi trường (Zod) và siêu dữ liệu trang web.
Ranh giới thành phần máy chủ so với máy khách trong cây thư mục
Một trong những nguyên nhân phổ biến nhất gây ra các ứng dụng Next.js chậm là "Nhiễm bẩn thành phần máy khách", nơi việc thêm 'use client' ở đầu trang buộc mọi thành phần con phải chạy trong các gói JavaScript của máy khách.
Để tránh điều này, hãy tuân theo quy tắc "Thành phần máy khách chỉ ở lá" trong cấu trúc thư mục của bạn:
app/(dashboard)/dashboard/page.tsx # [Server Component] Fetches data
├── DashboardSummary.tsx # [Server Component] Pure HTML rendering
└── _components/
└── InteractiveFilter.tsx # ['use client'] ONLY interactive button/state
Bằng cách giữ trang cha là Server Component và import Client Component vào vị trí lá, bạn giảm thiểu gói JavaScript của client và loại bỏ lỗi không khớp hydration của Next.js.
Mẫu sẵn sàng sản xuất (Sao chép & Dán)
Đây là cấu trúc thư mục đã được kiểm nghiệm trong thực tế cho các ứng dụng Next.js App Router doanh nghiệp:
my-app/
├── public/
│ └── static/
├── src/
│ ├── app/
│ │ ├── (auth)/
│ │ │ ├── login/page.tsx
│ │ │ ├── signup/page.tsx
│ │ │ └── layout.tsx
│ │ ├── (dashboard)/
│ │ │ ├── layout.tsx
│ │ │ ├── overview/page.tsx
│ │ │ └── settings/
│ │ │ ├── page.tsx
│ │ │ └── _components/
│ │ ├── api/
│ │ │ └── webhooks/stripe/route.ts
│ │ ├── favicon.ico
│ │ ├── global-error.tsx
│ │ ├── layout.tsx
│ │ └── not-found.tsx
│ │
│ ├── features/
│ │ ├── auth/
│ │ │ ├── ui/
│ │ │ ├── api/
│ │ │ └── index.ts
│ │ └── billing/
│ │ ├── ui/
│ │ ├── api/
│ │ └── index.ts
│ │
│ ├── entities/
│ │ ├── user/
│ │ │ ├── ui/UserAvatar.tsx
│ │ │ ├── model/types.ts
│ │ │ └── index.ts
│ │ └── organization/
│ │
│ └── shared/
│ ├── ui/ # Reusable design system components
│ ├── lib/ # Utilities (cn, formatters)
│ └── hooks/ # Domain-agnostic React hooks
│
├── middleware.ts # Edge routing & auth guards
├── next.config.mjs
└── tsconfig.json
Kiểm tra kiến thức của bạn: Kiến trúc App Router
Bạn cũng có thể thích
- React Testing Library user-event v14: Các phương pháp hay nhất & Di chuyển fireEvent (2026)
- Tăng cường React với Rust và WebAssembly: Hướng dẫn toàn diện
- Rust cho các nhà phát triển Frontend: Hướng dẫn chuyển đổi thực tế
- Intersection Observer so với getBoundingClientRect trong JavaScript: Phân tích hiệu suất chuyên sâu
Câu hỏi thường gặp
(folder) là một Nhóm Route nhóm các route mà không thêm các phân đoạn vào đường dẫn URL, cho phép nhiều layout gốc. _folder là một Thư mục riêng tư hoàn toàn loại bỏ khỏi hệ thống định tuyến, ngăn bất kỳ tệp con nào trở thành endpoint công khai.
Đặt các thành phần UI chung, không phụ thuộc vào miền (như nút, modal, input) trong src/shared/ui/ hoặc src/components/ui/. Các thành phần cụ thể theo miền nên nằm trong src/features/[feature-name]/ui/ hoặc các thư mục _components riêng tư trong một route.
Có. Trong Thiết kế phân lớp tính năng, chúng tôi khuyên bạn nên colocate các tệp hành động 'use server' bên trong features/[feature-name]/api/actions.ts. Điều này giữ các mutation gần với các biểu mẫu UI kích hoạt chúng trong khi vẫn duy trì ranh giới sạch sẽ.
Các route song song sử dụng quy ước @name để hiển thị nhiều trang đồng thời trong cùng một layout (ví dụ: @modal). Các route chặn sử dụng (.)route hoặc (..)route để tải một route từ một phân đoạn khác trong chế độ xem hiện tại mà không thay đổi ngữ cảnh URL, lý tưởng cho các thư viện ảnh và modal đăng nhập.
Next.js app/ được tối ưu hóa cho định tuyến, streaming và cơ chế HTTP. Việc đặt logic miền nặng, các máy trạng thái phức tạp và các luồng công việc nghiệp vụ trực tiếp vào app/ gây ra sự ghép nối tệp lớn, khiến việc kiểm thử đơn vị trở nên khó khăn và dẫn đến các phụ thuộc vòng tròn ngẫu nhiên.
Các phân tích chuyên sâu liên quan
Để tiếp tục nâng cao kiến trúc Next.js sản xuất của bạn, hãy khám phá các hướng dẫn bổ sung này:
- Lỗi Hydration của Next.js: Sửa lỗi "Text Content Mismatch" & 418: Ngăn chặn các lỗi không khớp SSR do trạng thái client và các tiện ích mở rộng của bên thứ ba gây ra.
- Các chiến lược lưu trữ & xác thực lại của Next.js App Router: Nắm vững 4 lớp lưu trữ của Next.js trong sản xuất.
- React 19 Server Actions & Cập nhật lạc quan: Xây dựng các biểu mẫu nhanh như chớp mà không có độ trễ phía client.
- Next.js App Router với Prisma & PostgreSQL: Các mẫu tích hợp cơ sở dữ liệu trực tiếp cho Server Components.
- Next.js Metadata, SEO & Hướng dẫn Open Graph: Tạo các bản xem trước chia sẻ xã hội động và schema JSON-LD.
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Next.js 15 Server Actions vs Route Handlers: So sánh kiến trúc chuyên sâu
Nắm vững khi nào nên chọn Server Actions so với Route Handlers trong Next.js 15, đi sâu vào progressive enhancement, hành vi caching, giao thức RPC và các ranh giới bảo mật.
Read more
Lỗi Hydration trong Next.js: Cách sửa "Text Content Mismatch" & Error 418 (2026)
Hướng dẫn sửa triệt để lỗi Hydration failed (#418), Text content does not match server-rendered HTML, lỗi dark mode flash next-themes và localStorage trong Next.js.
Read more
React 19: Server Actions & Cập nhật Optimistic (Không độ trễ)
Làm chủ useOptimistic và Server Actions với cơ chế rollback tự động khi có lỗi mạng. Bao gồm ví dụ code production, các mẫu transition, và biểu đồ tuần tự.
Read more