Bài viết trước giới thiệu kiến trúc tổng thể gồm 2 tầng: Frontend (Next.js)Backend (Firebase). Bài này đi sâu vào tầng Frontend — giải thích cách bố cục và tổ chức một dự án Next.js theo 3 nhóm chính: Pages, ComponentsServices.
Dành cho ai? Bạn không cần biết code. Bài viết giải thích bằng ngôn ngữ đời thường, dùng ví dụ thực tế từ ứng dụng quản lý công việc (task management). Mục tiêu: giúp bạn hiểu cấu trúc dự án để mô tả yêu cầu cho Claude Code chính xác hơn.

Tổng quan: 3 nhóm chính trong dự án Next.js

Mọi dự án Next.js đều được tổ chức thành 3 nhóm rõ ràng, mỗi nhóm có vai trò riêng:

Pages

Các trang giao diệnMỗi trang = một URL. Người dùng truy cập trực tiếp.

Components

Khối xây dựng giao diệnMảnh ghép tái sử dụng: nút bấm, form, card, bảng dữ liệu.

Services

Kết nối dữ liệuCode giao tiếp với Firebase: đọc/ghi database, đăng nhập, upload file.
Phép so sánh — Xây nhà:
  • Pages = Các phòng trong nhà (phòng khách, phòng ngủ, nhà bếp)
  • Components = Nội thất (bàn, ghế, tủ — có thể dùng ở nhiều phòng)
  • Services = Hệ thống kỹ thuật (điện, nước, internet — chạy ngầm phục vụ cả nhà)

Cấu trúc thư mục tổng thể

Mỗi nhóm thư mục giải quyết một bài toán khác nhau. Khi cần thay đổi giao diện nút bấm → vào components/ui/. Khi cần thêm trang mới → vào app/. Khi cần sửa cách đọc dữ liệu → vào features/tasks/services/.

1. Pages — Các trang giao diện

Pages là gì?

Pages là các trang mà người dùng truy cập qua URL. Trong Next.js, mỗi file page.tsx trong thư mục app/ tạo ra một URL tương ứng.
Phép so sánh: Pages giống các phòng trong nhà. Phòng khách (trang chủ), phòng làm việc (dashboard), phòng hồ sơ (danh sách tasks). Mỗi phòng có địa chỉ riêng (URL) và mục đích riêng.

Cấu trúc thư mục trình bày các trang (pages)

Giải thích các khái niệm trong Pages

Quy tắc đơn giản: cấu trúc thư mục chính là URL.Không cần cấu hình routing — thêm thư mục mới = có URL mới.
Dấu ngoặc vuông [id] tạo trang dùng cho nhiều URL khác nhau. Thay vì tạo riêng 1 trang cho mỗi task, bạn tạo 1 trang duy nhất hiển thị nội dung theo ID.
Cùng 1 file page.tsx nhưng hiển thị nội dung khác nhau dựa trên id trong URL.
Dấu ngoặc tròn (auth) tạo nhóm logic nhưng không ảnh hưởng URL.
Dùng để nhóm các trang liên quan (ví dụ tất cả trang xác thực) mà không thêm thư mục phụ vào URL. Giúp project gọn gàng hơn.
Layout là phần giao diện bao bọc các trang con, giữ nguyên khi chuyển trang.
Ví dụ: Khi người dùng chuyển từ /tasks sang /crm, layout gốc (header, sidebar, theme) giữ nguyên — chỉ phần nội dung chính thay đổi.
Thư mục api/ cho phép tạo API endpoint ngay trong dự án Next.js. Code này chạy trên server (Vercel), không lộ ra trình duyệt — phù hợp cho logic cần giữ bí mật.

Mỗi Page chứa gì?

Một file page.tsx thường chứa 3 phần:
Page không tự vẽ giao diện chi tiết. Nó dùng Hook để lấy dữ liệu, rồi ghép các Components lại thành trang hoàn chỉnh — giống người quản lý sắp xếp nội thất vào phòng.

2. Components — Khối xây dựng giao diện

Components là gì?

Components là mảnh ghép giao diện có thể tái sử dụng. Tạo một lần, ghép vào nhiều trang khác nhau, và khi sửa thì chỉ cần sửa ở một chỗ.
Phép so sánh: Components giống nội thất trong nhà. Một chiếc bàn (component Table) có thể đặt ở phòng khách, phòng ăn, hoặc phòng làm việc. Nếu muốn đổi kiểu bàn, bạn thay 1 lần — các phòng tự cập nhật.

Cấu trúc thư mục Components

Dự án chia components thành 2 nơi với vai trò khác nhau:
Phân biệt 2 nơi chứa components:
  • components/ui/ → Viên gạch cơ bản, không biết nghiệp vụ (Button chỉ là nút bấm)
  • features/tasks/components/ → Khối nghiệp vụ, hiểu dữ liệu task (TasksTable biết cách hiển thị danh sách tasks)

2 loại Components

Đây là các component nhỏ nhất, đơn giản nhất — nút bấm, ô input, badge, dropdown. Lấy từ thư viện shadcn/ui (bộ component có sẵn, được tùy biến theo dự án).Đặc điểm: Không biết gì về nghiệp vụ — Button chỉ biết nó là nút bấm, không biết bấm để làm gì. Tính năng do component cha quyết định.Ngoài ra, theme-provider.tsx quản lý chuyển đổi dark/light theme cho toàn ứng dụng.
Feature components ghép nhiều UI components lại để tạo thành một khối chức năng cụ thể. Chúng hiểu nghiệp vụ, biết dữ liệu trông như thế nào.Ví dụ — TaskFormDialog ghép từ nhiều UI components:
Ví dụ — TasksToolbar ghép từ nhiều UI components:

Components lồng nhau — Xây từ nhỏ đến lớn

Components được ghép lồng vào nhau từ đơn giản đến phức tạp, tạo thành giao diện hoàn chỉnh: Đọc từ trên xuống:
  1. Page (app/tasks/page.tsx) sử dụng Layout gốc (layout.tsx)
  2. Page ghép Feature components (TasksToolbar, TasksTable, TaskFormDialog)
  3. Mỗi Feature component bên trong sử dụng các UI components (Button, Table, Badge, Dialog)

3. Services — Kết nối dữ liệu

Services là gì?

Services là code giao tiếp với Firebase (hoặc bất kỳ nguồn dữ liệu nào). Chúng chứa các hàm đọc/ghi dữ liệu mà Hooks và Components gọi đến khi cần.
Phép so sánh: Services giống hệ thống kỹ thuật trong nhà — đường ống nước, dây điện, đường dây internet. Bạn không nhìn thấy chúng, nhưng khi vặn vòi nước (bấm nút lấy dữ liệu) thì nước chảy (dữ liệu hiện ra). Nếu cần sửa ống nước, bạn sửa ở hệ thống kỹ thuật — không cần đập tường phòng khách.

Cấu trúc thư mục Services

Trong dự án này, services nằm bên trong thư mục feature, không phải ở cấp gốc:
Tổ chức theo feature: Thay vì gom tất cả services vào 1 thư mục services/ ở gốc, dự án này đặt services cạnh components và hooks của cùng tính năng. Cách này giúp mỗi feature (tasks, crm, hrm) hoạt động như một module độc lập.

Mỗi Service chứa gì?

Mỗi file service chứa một hàm thực hiện một thao tác cụ thể. Ví dụ cho tính năng Tasks:

Chi tiết từng nhóm

File này chạy một lần duy nhất khi ứng dụng khởi động. Nó đọc thông tin cấu hình từ biến môi trường (.env.local) và tạo kết nối đến Firebase.
Các service files import kết nối từ lib/firebase.ts để sử dụng.
Mỗi file xử lý một thao tác riêng biệt:
File route.ts trong thư mục api/ tạo API endpoint chạy trên server (Vercel). Code này không lộ ra trình duyệt — phù hợp cho logic cần giữ bí mật.

Tại sao tách riêng Services?


4. Hooks — Logic tái sử dụng

Hooks là đoạn logic mà nhiều components cùng cần, được gói gọn vào hàm riêng để tái sử dụng. Trong dự án này, hooks nằm trong thư mục feature:
Hook useTasks làm gì? Nó gọi service get-tasks.ts bên trong, rồi bổ sung quản lý loading state, error handling. Khi trang Tasks cần dữ liệu, chỉ cần gọi useTasks() — có ngay danh sách tasks kèm trạng thái loading.
Khi dự án lớn hơn, mỗi feature sẽ có thêm hooks riêng. Ví dụ features/crm/hooks/use-customers.ts cho tính năng CRM.

Cách các nhóm phối hợp với nhau

Đây là phần quan trọng: Pages, Components, Hooks và Services không hoạt động độc lập — chúng phối hợp theo luồng rõ ràng.

Luồng hiển thị trang danh sách tasks

Luồng tạo task mới

Quy tắc phối hợp


Ví dụ thực tế: Trang danh sách tasks

Gộp tất cả lại, đây là cách trang danh sách tasks được “lắp ráp”:

Mô tả cho Claude Code

Khi bạn hiểu cấu trúc Pages / Components / Services, việc mô tả yêu cầu cho Claude Code trở nên chính xác hơn: Thêm trang mới:
Thêm feature component:
Thêm feature mới (CRM):

Câu hỏi thường gặp

Tạo component riêng khi:
  • Code đó sẽ dùng lại ở 2 nơi trở lên
  • Khối giao diện phức tạp (nhiều hơn 30-40 dòng)
  • Muốn tách isolate logic (form, bảng, bộ lọc)
Viết thẳng trong page khi:
  • Code đơn giản, chỉ dùng 1 lần
  • Chỉ là vài dòng ghép components có sẵn
Khi không chắc → bắt đầu viết trong page, tách ra sau khi thấy cần tái sử dụng.
Lý do 1 — Tái sử dụng: Nhiều components cùng cần gọi getTasks(). Nếu code nằm trong component, phải copy-paste.Lý do 2 — Dễ thay đổi: Nếu sau này chuyển từ Firebase sang Supabase, bạn chỉ sửa file service — không cần sửa bất kỳ component nào.Lý do 3 — Dễ test: Service có thể được test độc lập, không cần render giao diện.
Thực tế: hooks gọi services. Hook useTasks() bên trong gọi service get-tasks.ts, rồi bổ sung quản lý loading state, error handling.
Claude Code biết các quy ước tổ chức thư mục Next.js tiêu chuẩn. Khi bạn mô tả rõ tính năng, Claude Code sẽ tự tạo file ở đúng vị trí.Tuy nhiên, ban đầu nên yêu cầu Claude Code thiết lập cấu trúc thư mục trước:
Sau đó, mỗi lần thêm tính năng, Claude Code sẽ đặt file vào đúng thư mục.

Tóm tắt

Điểm mấu chốt: Dự án Next.js được tổ chức theo nguyên tắc feature-based — mỗi tính năng (tasks, crm) gom components, hooks và services vào cùng một thư mục. UI components dùng chung nằm trong components/ui/, cấu hình chung nằm trong lib/. Hiểu cấu trúc này giúp bạn mô tả yêu cầu cho Claude Code chính xác hơn — biết cần thêm component ở đâu, sửa service nào, tạo page mới ra sao.

Thực hành tạo dự án

Phần này hướng dẫn bạn tạo một dự án quản lý công việc từ đầu — từ khởi tạo dự án Next.js đến kết nối Firebase. Mỗi bước kèm prompt mẫu để bạn gửi cho Claude Code.
1

Khởi tạo dự án

Mở Terminal (trong VS code) và chạy lệnh sau để tạo dự án Next.js với shadcn/ui:
Lệnh này tự động tạo thư mục dự án với cấu trúc Next.js + shadcn/ui đã cấu hình sẵn.
2

Mở dự án trong VS Code

Trong VS Code, chọn File → Open Folder và mở thư mục vừa tạo ở bước trước.
3

Chạy dự án

Mở Terminal trong VS Code và chạy:
Truy cập http://localhost:3000 để xem kết quả.
4

Tạo trang login

Gửi prompt sau cho Claude Code:
Claude Code sẽ tạo file app/login/page.tsx với form đăng nhập sử dụng các UI components từ shadcn/ui.
5

Tạo trang quản lý công việc

Gửi prompt sau cho Claude Code:
Ở bước này Claude Code sẽ tạo trang với mock data (dữ liệu mẫu giả). Bước 7 sẽ thay thế bằng dữ liệu thật từ Firebase.
6

Chuẩn hóa components theo feature

Sau khi trang tasks hoạt động, yêu cầu Claude Code tách code thành các feature components riêng biệt:
Claude Code sẽ tách code từ page.tsx ra thành các component riêng theo cấu trúc feature-based đã giới thiệu ở phần trước.
7

Kết nối Firebase

Trước tiên, tạo dự án Firebase:
  1. Truy cập Firebase Console
  2. Tạo project mới, đặt tên: claude-code-tasks-management
  3. Tạo Firestore Database
  4. Vào Project Settings → General → Your apps → Web và thêm web app
  5. Copy các thông số config
Sau đó gửi prompt cho Claude Code:
Thay "..." bằng giá trị thật từ Firebase Console. Claude Code sẽ tạo lib/firebase.ts để khởi tạo kết nối và các service files trong features/tasks/services/ để thay thế mock data bằng dữ liệu thật từ Firestore.