20/06/2026 · 6 phút đọc
Ownership Rules trong Next.js: Ranh giới tuyệt đối giữa App, Features, Components và Lib
Khi dự án Frontend production lớn dần, sai lầm phổ biến nhất là đặt code sai chỗ. Thiết lập ranh giới sở hữu rõ ràng giúp mã nguồn dễ dự đoán đối với con người và an toàn khi cộng tác cùng AI Agent.
Bài toán "Chao ôi, đặt file này ở đâu bây giờ?"
Trong các dự án Frontend nhỏ, cấu trúc thư mục thường không phải là vấn đề lớn. Bạn có thể ném tất cả các hàm hook vào src/hooks, mọi component vào src/components, và ứng dụng vẫn chạy mượt mà.
Tuy nhiên, khi dự án đạt đến quy mô hàng chục nghìn dòng code, hàng trăm route, và có sự tham gia đồng thời của nhiều kỹ sư (hoặc sự xuất hiện của các AI Coding Agent), cấu trúc kiểu "bỏ chung một rổ" (Shared-by-default) sẽ nhanh chóng sụp đổ.
Hệ quả là:
- Tính kết dính thấp (Low Cohesion): Để sửa một tính năng nhỏ, bạn phải mở 5 file nằm ở 5 thư mục xa nhau tít tắp (
src/pages,src/hooks,src/types,src/components,src/utils). - Nợ kỹ thuật tăng phi mã: Các AI Agent khi không có ranh giới rõ ràng sẽ xu hướng tạo ra các tệp tin trùng lặp vì chúng không thể quét hết hàng trăm file utility dùng chung.
- Blast Radius vô định: Sửa một component ở nơi này vô tình làm hỏng (break) giao diện ở một trang hoàn toàn không liên quan.
Để giải quyết triệt để, hệ thống cần một Bản đồ quyền sở hữu (Ownership Rules) với ranh giới tuyệt đối. Mã nguồn phải được tổ chức theo nguyên tắc: Cô lập theo Domain trước, Dùng chung sau (Locality-first, Shared-second).
Bản đồ Ranh giới 5 Tầng Kiến trúc
Cấu trúc thư mục chuẩn production phân định quyền sở hữu nghiêm ngặt cho từng Layer, phân tách rõ ràng giữa Tầng điều phối (Orchestration), Tầng nghiệp vụ (Domain Logic), và Tầng hạ tầng UI (Primitives).
src/
├── app/ # Tầng Điều phối (Routing & Metadata) - Mỏng
├── features/ # Tầng Nghiệp vụ (Business Domains) - Dày
├── components/ # Tầng Hạ tầng UI (Shared & Primitives) - Câm
├── lib/ # Tầng Tiện ích thuần túy (Pure Utilities) - React-free
└── types/ # Tầng Hợp đồng dữ liệu (Contracts) - Type-only
1. src/app/* — Tầng Điều phối (Routing & Page Composition)
Thư mục app nắm quyền sở hữu tối cao về mặt cấu trúc URL của Next.js App Router. Tuy nhiên, tầng này phải giữ trạng thái cực kỳ mỏng.
- Quyền sở hữu: Hệ thống định tuyến (File-system Routing), Layout toàn cục, Cấu hình SEO Metadata (
generateMetadata), Route Handlers (route.ts), các điểm bọc Error/Suspense Boundaries, và việc nhận tham số đầu vào từ URL (params,searchParams). - Quy tắc nghiêm ngặt: Không viết business logic tại đây. Các file
page.tsxchỉ đóng vai trò như các "Controller": nhận request, gọi hàm từ tầngfeatures/để lấy dữ liệu, gom (compose) các khối UI lại và render.
2. src/features/* — Tầng Nghiệp vụ (The Core Domain)
Đây là nơi chứa toàn bộ linh hồn và giá trị cốt lõi của sản phẩm. Thư mục này được chia nhỏ theo từng Domain chức năng độc lập (ví dụ: blog, profile, projects, contact).
- Quyền sở hữu: Logic nghiệp vụ, định dạng dữ liệu (Normalization), các custom hooks riêng biệt cho domain, các hàm gọi API/Service, và các Component có nhận thức về mặt nghiệp vụ (Smart Components).
- Cấu trúc nội bộ của một Feature:
src/features/blog/
├── components/ # Các UI component chỉ phục vụ riêng cho Blog
├── hooks/ # Hooks xử lý logic riêng của Blog
├── blog-service.ts # Xử lý đọc/ghi, filter, sort dữ liệu (React-free)
└── index.ts # Điểm xuất khẩu (Public API) duy nhất của Feature
- Quy tắc nghiêm ngặt: Một Feature không được phép import trực tiếp từ bên sâu trong cấu trúc nội bộ của một Feature khác. Nếu cần giao tiếp chéo, chúng phải đi qua điểm xuất khẩu
index.ts(Public API) được định nghĩa tường minh.
3. src/components/* — Tầng Hạ tầng UI (Presentation Layer)
Thư mục này nắm quyền sở hữu các khối gạch xây dựng nên giao diện, hoàn toàn không có nhận thức về mặt nghiệp vụ (Domain-agnostic).
- Quyền sở hữu: Các UI Primitives tái sử dụng toàn hệ thống (như nút bấm, input, modal, dropdown - thường cấu hình từ
shadcn/ui, Radix UI), và các khối layout dùng chung (Header, Footer, Sidebar). - Quy tắc nghiêm ngặt: Các component tại đây phải là Component Câm (Dumb Components). Chúng chỉ nhận dữ liệu qua
props, kích hoạt sự kiện qua callback, không tự ý gọi API, không đọc file hệ thống, và không import bất kỳ mã nguồn nào từ tầngfeatures/.
4. src/lib/* — Tầng Tiện ích (Cross-feature Utilities)
Nơi lưu trữ các công cụ bổ trợ kỹ thuật cho toàn bộ hệ thống.
- Quyền sở hữu: Cấu hình Client khởi tạo bên thứ ba (Firebase, Prisma, Supabase client), các bộ xử lý chuỗi/ngày tháng dùng chung, thư viện xử lý markdown thô.
- Quy tắc nghiêm ngặt: Thư mục này bắt buộc phải React-free (không chứa JSX/TSX, không sử dụng React Hooks). Nó hoạt động như một thư viện nội bộ cung cấp các hàm thuần túy (Pure Functions).
Bảng Đối chiếu Trách nhiệm và Ranh giới Thực thi
Để loại bỏ hoàn toàn sự mơ hồ khi tạo mới một tệp tin, hãy đối chiếu nhanh qua bảng quy chuẩn dưới đây:
| Loại tệp tin | Thư mục sở hữu phù hợp | Ai được phép import nó? | Nó được phép import ai? |
|---|---|---|---|
layout.tsx, page.tsx | src/app/** | Không ai cả (Next.js Engine) | src/features/*, src/components/*, src/lib/* |
blog-card.tsx (Chỉ dùng ở trang blog) | src/features/blog/components/ | src/app/blog/* hoặc các file trong cùng feature | src/components/ui/*, src/lib/* |
button.tsx (Nút bấm nguyên bản) | src/components/ui/ | Toàn bộ hệ thống | Không ai cả (Chỉ chứa primitive styles/Tailwind) |
markdown-parser.ts (Parse chuỗi thô) | src/lib/ | src/features/*, src/app/* | Không ai cả (Chỉ import thư viện npm gốc) |
Quy tắc "Do Not Create": Khóa van an toàn cho AI Agent
Khi bạn làm việc với một AI Coding Agent có năng lực tự động sửa đổi file (như Cursor Agent hoặc GitHub Copilot Workspace), việc đặt ra quy tắc "Do Not Create" (Không tự ý tạo mới bừa bãi) là lá chắn tối thượng để bảo vệ cấu trúc hệ thống.
Trong file chỉ dẫn Agent (AGENTS.md hoặc .cursorrules), hãy ép Agent tuân thủ quy trình kiểm tra mã nguồn trước khi sinh code:
QUY TẮC "DO NOT CREATE" DÀNH CHO AGENT: Trước khi tạo mới bất kỳ một Custom Hook, một hàm Utility, một Variant của Button, hoặc một Zustand Store nào:
- Sử dụng công cụ tìm kiếm hệ thống (
rtk grephoặcCodeGraph) để quét toàn bộ kho lưu trữ xem chức năng tương tự đã tồn tại chưa.- Nếu đã tồn tại thực thi tương đương (ví dụ: hàm format ngày tháng), bắt buộc phải tái sử dụng hoặc mở rộng thực thi cũ. Tuyệt đối không tạo ra file trùng lặp logic.
- Nếu tác vụ yêu cầu chỉnh sửa UI, hãy ưu tiên mở rộng hệ thống Design System hiện có (
src/components/ui/) thay vì tự chế ra các class CSS biệt lập.
Kết luận
Bản đồ quyền sở hữu không sinh ra để làm hạn chế tốc độ viết code của kỹ sư, mà nó tạo ra một sự nhất quán trong tư duy.
Khi ranh giới giữa app, features, components và lib được vạch ra một cách tuyệt đối, bạn sẽ không còn phải mất thời gian suy nghĩ xem nên đặt file mới ở đâu. Codebase trở nên vô cùng trực quan: nhìn vào cấu trúc thư mục là nhìn thấy ngay mô hình kinh doanh của sản phẩm. Đây chính là bệ phóng vững chắc nhất để hệ thống Frontend sẵn sàng mở rộng quy mô một cách an toàn và bền vững.

