Next.js & ReactNổi bật

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.

Next.jsArchitectureSystem DesignSoftware Engineering

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.tsx chỉ đóng vai trò như các "Controller": nhận request, gọi hàm từ tầng features/ để 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ầng features/.

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 tinThư mục sở hữu phù hợpAi được phép import nó?Nó được phép import ai?
layout.tsx, page.tsxsrc/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 featuresrc/components/ui/*, src/lib/*
button.tsx (Nút bấm nguyên bản)src/components/ui/Toàn bộ hệ thốngKhô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:

  1. Sử dụng công cụ tìm kiếm hệ thống (rtk grep hoặc CodeGraph) để quét toàn bộ kho lưu trữ xem chức năng tương tự đã tồn tại chưa.
  2. 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.
  3. 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, componentslib đượ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.

[ Bài liên quan ]

Next.js & ReactNổi bật

20/06/2026 · 7 phút đọc

Cách dùng URL Params, TanStack Query, Zustand và React Local State trong Next.js

Trong kiến trúc Server-first, quản lý state không còn là gom tất cả vào một kho lưu trữ toàn cục. Đó là nghệ thuật đặt dữ liệu đúng chỗ để tối ưu hóa hiệu năng render và biệt lập hóa phạm vi ảnh hưởng.

FrontendNext.jsState ManagementArchitecture
Xem tất cả
Next.js & ReactNổi bật

20/06/2026 · 6 phút đọc

Kiến trúc Server-first và Luồng Xử lý Dữ liệu Nội bộ trong Next.js App Route

Thiết kế hệ thống không chỉ là viết mã nguồn sạch, mà còn là tối ưu hóa tài nguyên mạng. Khám phá cách xây dựng dịch vụ xử lý Markdown bền vững, loại bỏ overhead từ API nội bộ và quản lý cấu trúc dữ liệu nhất quán.

Next.jsReact Server ComponentsSystem DesignPerformance
Xem tất cả