•15 min read

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)

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)
Next.js App Router Scalable Folder Structure Architecture
Audio Briefing
0:00 / 0:00
Part of a Series

Chuỗi kiến trúc Next.js hiện đại

Part 2 of 3
Part 2:Cấu trúc thư mục sạch cho Next.js App Router
You are here

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ái 404.
  • 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:

  1. 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ần DashboardGraph, 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.
  2. 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.
  3. 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.

Advertisement

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ìnhPhù 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.

Advertisement

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

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:

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement