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ể

my-task-app/

├── app/                    ← 📄 PAGES (các trang giao diện)
│   ├── layout.tsx
│   ├── page.tsx
│   ├── (auth)/
│   │   ├── login/
│   │   └── register/
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── tasks/
│   │   ├── projects/
│   │   └── settings/
│   └── api/

├── components/             ← 🧩 COMPONENTS (khối giao diện tái sử dụng)
│   ├── ui/
│   ├── layout/
│   ├── tasks/
│   └── shared/

├── services/               ← 🔌 SERVICES (kết nối Firebase)
│   ├── firebase.ts
│   ├── auth.ts
│   ├── tasks.ts
│   ├── projects.ts
│   └── storage.ts

├── hooks/                  ← 🪝 HOOKS (logic tái sử dụng)
│   ├── useAuth.ts
│   └── useTasks.ts

├── types/                  ← 📋 TYPES (định nghĩa cấu trúc dữ liệu)
│   ├── task.ts
│   └── user.ts

├── .env.local              ← Biến môi trường
└── package.json
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/. Khi cần thêm trang mới → vào app/. Khi cần sửa cách đọc dữ liệu → vào 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 Pages

app/
├── page.tsx                         →  yourapp.com/
├── (auth)/                          ← Nhóm route (không tạo URL)
│   ├── login/
│   │   └── page.tsx                 →  yourapp.com/login
│   └── register/
│       └── page.tsx                 →  yourapp.com/register

├── dashboard/
│   ├── layout.tsx                   ← Layout dùng chung cho mọi trang dashboard
│   ├── page.tsx                     →  yourapp.com/dashboard
│   ├── tasks/
│   │   ├── page.tsx                 →  yourapp.com/dashboard/tasks
│   │   └── [id]/
│   │       └── page.tsx             →  yourapp.com/dashboard/tasks/abc123
│   ├── projects/
│   │   ├── page.tsx                 →  yourapp.com/dashboard/projects
│   │   └── [id]/
│   │       └── page.tsx             →  yourapp.com/dashboard/projects/xyz789
│   └── settings/
│       └── page.tsx                 →  yourapp.com/dashboard/settings

└── api/                             ← API Routes (backend nhẹ)
    └── notifications/
        └── route.ts                 →  POST /api/notifications

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.
Thư mụcURL
app/page.tsx/ (trang chủ)
app/dashboard/page.tsx/dashboard
app/dashboard/tasks/page.tsx/dashboard/tasks
app/dashboard/settings/page.tsx/dashboard/settings
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.
app/dashboard/tasks/[id]/page.tsx

→ yourapp.com/dashboard/tasks/task_001    ← Hiển thị task "Làm báo cáo Q1"
→ yourapp.com/dashboard/tasks/task_002    ← Hiển thị task "Review thiết kế"
→ yourapp.com/dashboard/tasks/task_003    ← Hiển thị task "Họp với khách hàng"
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.
app/(auth)/login/page.tsx   → yourapp.com/login      (không có /auth/)
app/(auth)/register/page.tsx → yourapp.com/register   (không có /auth/)
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.
app/layout.tsx           ← Layout gốc: Header, font, theme (áp dụng tất cả trang)
app/dashboard/layout.tsx ← Layout dashboard: Sidebar (áp dụng mọi trang /dashboard/*)
Ví dụ: Khi người dùng chuyển từ /dashboard/tasks sang /dashboard/projects, Sidebar giữ nguyên — chỉ phần nội dung chính thay đổi.
┌──────────────────────────────────────────┐
│  Header (logo, thông báo, avatar)        │ ← layout.tsx (gốc)
├──────────┬───────────────────────────────┤
│          │                               │
│ Sidebar  │   Nội dung trang              │ ← dashboard/layout.tsx
│          │   (thay đổi theo URL)         │
│ - Tasks  │                               │
│ - Projects                               │
│ - Settings                               │
│          │                               │
└──────────┴───────────────────────────────┘
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.
Khi nào dùng API RouteKhi nào dùng Firebase trực tiếp
Cần giấu API key bí mậtĐọc/ghi dữ liệu Firestore
Gọi dịch vụ thanh toán (Stripe, VNPay)Đăng nhập/đăng ký
Gọi AI (Claude, OpenAI)Upload file lên Storage
Gửi email (Resend, SendGrid)Lắng nghe realtime

Mỗi Page chứa gì?

Một file page.tsx thường chứa 3 phần:
┌─────────────────────────────────┐
│  1. Lấy dữ liệu                │  ← Gọi Service để đọc từ Firebase
│     (getProjects, getTasks)     │
├─────────────────────────────────┤
│  2. Ghép các Components         │  ← Lắp ráp từ khối có sẵn
│     TaskFilter                  │
│     TaskTable data={tasks}      │
│     CreateTaskButton            │
├─────────────────────────────────┤
│  3. Trả về giao diện hoàn chỉnh│  ← Next.js hiển thị trang
└─────────────────────────────────┘
Page không tự vẽ giao diện chi tiết. Nó lấy dữ liệu từ Services, 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

components/
├── ui/                              ← 🎨 Components cơ bản (từ shadcn/ui)
│   ├── button.tsx                   ← Nút bấm
│   ├── dialog.tsx                   ← Hộp thoại (modal/popup)
│   ├── badge.tsx                    ← Nhãn trạng thái
│   ├── input.tsx                    ← Ô nhập liệu
│   ├── select.tsx                   ← Dropdown chọn
│   ├── table.tsx                    ← Bảng dữ liệu
│   ├── avatar.tsx                   ← Ảnh đại diện
│   ├── date-picker.tsx              ← Chọn ngày
│   └── sidebar.tsx                  ← Thanh menu bên

├── layout/                          ← 🏗️ Components bố cục
│   ├── AppHeader.tsx                ← Header toàn app (logo, notifications, user menu)
│   ├── AppSidebar.tsx               ← Sidebar navigation
│   └── PageHeader.tsx               ← Tiêu đề + action buttons cho mỗi trang

├── tasks/                           ← 📋 Components cho tính năng Tasks
│   ├── TaskCard.tsx                 ← Thẻ hiển thị 1 task
│   ├── TaskTable.tsx                ← Bảng danh sách tasks
│   ├── TaskForm.tsx                 ← Form tạo/sửa task
│   ├── TaskFilter.tsx               ← Bộ lọc trạng thái, người phụ trách
│   ├── TaskStatusBadge.tsx          ← Badge hiển thị trạng thái
│   └── TaskDeleteDialog.tsx         ← Hộp thoại xác nhận xóa task

├── projects/                        ← 🏢 Components cho tính năng Projects
│   ├── ProjectCard.tsx
│   ├── ProjectMemberList.tsx
│   └── ProjectForm.tsx

└── shared/                          ← 🔄 Components dùng chung
    ├── UserAvatar.tsx               ← Ảnh đại diện người dùng
    ├── EmptyState.tsx               ← Giao diện khi không có dữ liệu
    ├── LoadingSpinner.tsx           ← Hiệu ứng đang tải
    └── ConfirmDialog.tsx            ← Hộp thoại xác nhận chung

3 loại Components

Đây là các component nhỏ nhất, đơn giản nhất — nút bấm, ô input, badge, dropdown. Thường lấy từ thư viện shadcn/ui (bộ component có sẵn, được tùy biến theo dự án).
ComponentCông dụng
ButtonNút bấm (Lưu, Xóa, Thêm mới)
InputÔ nhập liệu (tiêu đề, mô tả)
SelectDropdown (chọn trạng thái, chọn người)
BadgeNhãn nhỏ (In Progress, Completed)
DialogPopup/modal (form tạo task, xác nhận xóa)
TableBảng dữ liệu (danh sách tasks)
AvatarẢnh đại diện tròn
DatePickerChọn ngày (deadline)
Đặ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.
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ụ — TaskCard ghép từ nhiều UI components:
┌──────────────────────────────────────┐
│  ☑️  Làm báo cáo Q1                 │  ← Checkbox + Text
│                                      │
│  👤 Nguyễn Văn A    🏷️ In Progress  │  ← Avatar + Badge
│  📅 30/04/2026                       │  ← DatePicker (read-only)
│                        [Sửa] [Xóa]  │  ← Buttons
└──────────────────────────────────────┘
TaskCard sử dụng Avatar, Badge, Button, Checkbox — nhưng biết cách hiển thị thông tin task cụ thể.Ví dụ — TaskForm ghép từ nhiều UI components:
┌──────────────────────────────────────┐
│  Tạo task mới                        │  ← Dialog title
│                                      │
│  Tiêu đề:  [________________]        │  ← Input
│  Mô tả:    [________________]        │  ← Textarea
│  Phụ trách: [Chọn người ▼]           │  ← Select
│  Deadline:  [Chọn ngày 📅]           │  ← DatePicker
│  Trạng thái: [Todo ▼]               │  ← Select
│                                      │
│            [Hủy]  [Lưu]             │  ← Buttons
└──────────────────────────────────────┘
Shared components dùng ở nhiều tính năng khác nhau, không thuộc riêng Tasks hay Projects.
ComponentCông dụngDùng ở đâu
UserAvatarHiển thị ảnh + tên người dùngTaskCard, ProjectCard, Header
EmptyStateGiao diện khi không có dữ liệuTaskTable, ProjectList
LoadingSpinnerVòng xoay khi đang tảiMọi nơi cần chờ Firebase
ConfirmDialog”Bạn có chắc muốn xóa?”Xóa task, project, user

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 sử dụng Layout components (Header, Sidebar)
  2. Page ghép Feature components (PageHeader, TaskFilter, TaskTable)
  3. TaskTable chứa nhiều dòng, mỗi dòng dùng TaskCard
  4. TaskCard lại chứa các UI components nhỏ hơn (Badge, Avatar, Button)

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à Pages 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

services/
├── firebase.ts          ← Cấu hình kết nối Firebase (khởi tạo 1 lần)
├── auth.ts              ← Đăng nhập, đăng ký, đăng xuất
├── tasks.ts             ← CRUD tasks (tạo, đọc, sửa, xóa)
├── projects.ts          ← CRUD projects
├── users.ts             ← Đọc/cập nhật thông tin users
└── storage.ts           ← Upload/download file

Mỗi Service chứa gì?

Mỗi file service chứa các hàm thực hiện một nhóm thao tác liên quan. Ví dụ tasks.ts:
HàmCông dụng
getTasks()Lấy danh sách tasks từ Firestore
getTaskById(id)Lấy chi tiết 1 task theo ID
createTask(data)Tạo task mới, ghi vào Firestore
updateTask(id, data)Cập nhật 1 task (đổi trạng thái, sửa tiêu đề)
deleteTask(id)Xóa 1 task
onTasksChange()Lắng nghe và cập nhật realtime khi có thay đổi

Chi tiết từng Service

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.
.env.local (file cấu hình):
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSy...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=my-task-app
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=my-task-app.firebaseapp.com
...

      ↓ firebase.ts đọc các biến này

Tạo kết nối → Firestore (database)
Tạo kết nối → Authentication (đăng nhập)
Tạo kết nối → Storage (file)
Các service khác (tasks.ts, auth.ts) import kết nối từ file này để sử dụng.
Quản lý toàn bộ quy trình đăng nhập/đăng xuất.
HàmCông dụng
signInWithGoogle()Mở popup Google, chọn tài khoản, đăng nhập
signInWithEmail(e,p)Đăng nhập bằng email + password
signUpWithEmail(e,p)Tạo tài khoản mới
signOut()Đăng xuất
getCurrentUser()Lấy thông tin người dùng hiện tại
onAuthStateChange()Theo dõi trạng thái đăng nhập
Ai gọi? Trang Login gọi signInWithGoogle(), Header gọi getCurrentUser() để hiển thị avatar, trang bảo vệ gọi onAuthStateChange() để kiểm tra quyền.
CRUD (Create, Read, Update, Delete) cho tasks.
HàmCông dụng
getTasks(filters)Lấy danh sách tasks (theo bộ lọc)
getTaskById(id)Lấy chi tiết 1 task
createTask(data)Tạo task mới vào Firestore
updateTask(id, data)Cập nhật task (đổi status, sửa title)
deleteTask(id)Xóa task
onTasksChange(callback)Lắng nghe realtime, gọi callback khi có thay đổi
Ai gọi?
  • TaskTable gọi getTasks() để hiển thị danh sách
  • TaskForm gọi createTask() khi bấm Lưu
  • TaskCard gọi updateTask() khi đổi trạng thái
  • TaskDeleteDialog gọi deleteTask() khi xác nhận xóa
Upload và download file từ Firebase Cloud Storage.
HàmCông dụng
uploadFile(file, path)Upload file lên Storage, trả về URL
deleteFile(path)Xóa file
getDownloadUrl(path)Lấy URL để hiển thị hoặc download
Ai gọi? TaskForm gọi uploadFile() khi người dùng đính kèm file vào task. TaskCard gọi getDownloadUrl() để hiển thị ảnh đính kèm.

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

Không tách (code lẫn lộn)Tách riêng Services
Code đọc Firebase nằm rải rác khắp nơiCode Firebase tập trung một chỗ
Đổi database → sửa hàng chục fileĐổi database → chỉ sửa file service
Khó tìm lỗi khi dữ liệu saiDễ debug: lỗi dữ liệu → mở service tương ứng
Claude Code viết code lặp lạiClaude Code tái sử dụng hàm có sẵn
Một số dự án đặt Services trong thư mục lib/ thay vì services/. Cả hai cách đều đúng — điều quan trọng là tách riêng code kết nối dữ liệu ra khỏi giao diện.

4. Hooks và Types — Hai thư mục bổ trợ

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.
hooks/
├── useAuth.ts        ← Quản lý trạng thái đăng nhập
│                        (user hiện tại, đang loading, đã login chưa)

├── useTasks.ts       ← Quản lý dữ liệu tasks
│                        (danh sách tasks, loading, lỗi, hàm tạo/sửa/xóa)

└── useDebounce.ts    ← Trì hoãn thao tác (tìm kiếm khi ngừng gõ)
Ví dụ thực tế: Cả trang Dashboard và trang Tasks đều cần biết “user hiện tại là ai”. Thay vì viết lại logic đọc thông tin user ở 2 nơi, dùng useAuth() — gọi 1 dòng, có ngay thông tin user.

Types — Định nghĩa cấu trúc dữ liệu

Types mô tả dữ liệu trông như thế nào — giúp Claude Code viết code chính xác hơn và phát hiện lỗi sớm.
types/
├── task.ts           ← Task gồm những trường nào?
│                        title: string (tiêu đề)
│                        status: "todo" | "in_progress" | "completed"
│                        assignee: string (ID người phụ trách)
│                        deadline: Date (ngày hết hạn)

└── user.ts           ← User gồm những trường nào?
                         name: string
                         email: string
                         role: "admin" | "member"
                         avatarUrl: string
Bạn không cần viết Types — Claude Code sẽ tự tạo dựa trên mô tả dữ liệu của bạn. Nhưng biết thư mục này tồn tại giúp bạn hiểu dự án rõ hơn.

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

Đây là phần quan trọng: Pages, Components 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

Quy tắcGiải thích
Page gọi ServicePage lấy dữ liệu từ Service, truyền vào Components
Component gọi ServiceComponent tương tác (form, nút xóa) gọi Service để ghi dữ liệu
Component không gọi Firebase trực tiếpMọi giao tiếp với Firebase đều đi qua Service
Service không biết giao diệnService chỉ lo dữ liệu, không biết dữ liệu hiển thị thế nào
UI Component không biết nghiệp vụButton không biết bấm để tạo task hay xóa project

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”:
📄 app/dashboard/tasks/page.tsx

│  Gọi Service: getTasks() → lấy danh sách tasks
│  Gọi Service: getUsers() → lấy danh sách users (cho bộ lọc)

│  Truyền dữ liệu vào Components:

├── 🏗️ PageHeader
│   ├── title: "Danh sách công việc"
│   └── 🧩 Button: "Thêm task"
│       └── onClick → Mở TaskForm (Dialog)

├── 🧩 TaskFilter
│   ├── Select: Trạng thái (Tất cả / Đang làm / Hoàn thành)
│   ├── Select: Người phụ trách (Tất cả / User A / User B)
│   └── Input: Tìm kiếm theo tiêu đề

├── 🧩 TaskTable
│   ├── Dòng 1: TaskCard (task "Làm báo cáo Q1")
│   │   ├── TaskStatusBadge: "In Progress"
│   │   ├── UserAvatar: "Nguyễn Văn A"
│   │   ├── Text: "30/04/2026"
│   │   └── Buttons: [Sửa] [Xóa]
│   ├── Dòng 2: TaskCard (task "Review thiết kế")
│   └── Dòng 3: ...

└── 🧩 TaskForm (Dialog — ẩn, hiện khi bấm "Thêm task")
    ├── Input: Tiêu đề
    ├── Textarea: Mô tả
    ├── Select: Người phụ trách
    ├── DatePicker: Deadline
    └── Buttons: [Hủy] [Lưu]
        └── onSave → Gọi Service: createTask(data)

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:
"Tạo trang /dashboard/reports tại app/dashboard/reports/page.tsx.
Trang này hiển thị thống kê: tổng số tasks, tỷ lệ hoàn thành, tasks quá hạn.
Lấy dữ liệu từ service tasks.ts."
Thêm component:
"Tạo component TaskKanban trong components/tasks/TaskKanban.tsx.
Hiển thị tasks dạng kanban board với 3 cột: Todo, In Progress, Completed.
Mỗi task hiển thị bằng TaskCard (component có sẵn).
Cho phép kéo-thả task giữa các cột.
Khi kéo-thả → gọi updateTask() từ services/tasks.ts để cập nhật status."
Thêm service:
"Tạo service mới services/notifications.ts.
Các hàm:
- sendNotification(userId, message) → ghi vào Firestore collection 'notifications'
- getNotifications(userId) → lấy danh sách thông báo
- markAsRead(notificationId) → đánh dấu đã đọc"

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.
Tiêu chíservices/hooks/
Loại hàmHàm thuần túy, gọi FirebaseHàm React, quản lý state giao diện
Ví dụgetTasks() trả về datauseTasks() trả về data + loading + error
Dùng ở đâuBất cứ đâuChỉ trong React components
Thực tế: hooks gọi services. Hook useTasks() bên trong gọi service getTasks(), rồi bổ sung quản lý loading state, error handling, caching.
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:
"Tạo cấu trúc thư mục chuẩn cho dự án:
- app/ cho pages
- components/ chia theo ui/, layout/, tasks/, shared/
- services/ cho Firebase
- hooks/ cho custom hooks
- types/ cho TypeScript types"
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

┌──────────────────────────────────────────────────────┐
│              CẤU TRÚC DỰ ÁN NEXT.JS                 │
├──────────────────────────────────────────────────────┤
│                                                      │
│  📄 PAGES (app/)                                     │
│  ├── Mỗi file page.tsx = 1 trang (1 URL)            │
│  ├── Lấy dữ liệu từ Services                       │
│  ├── Ghép Components thành giao diện hoàn chỉnh     │
│  └── layout.tsx = khung chung (header, sidebar)     │
│                                                      │
│  🧩 COMPONENTS (components/)                         │
│  ├── UI: Button, Input, Badge (viên gạch cơ bản)   │
│  ├── Feature: TaskCard, TaskForm (khối nghiệp vụ)   │
│  └── Shared: UserAvatar, LoadingSpinner (dùng chung)│
│                                                      │
│  🔌 SERVICES (services/)                             │
│  ├── firebase.ts: khởi tạo kết nối                  │
│  ├── auth.ts: đăng nhập / đăng xuất                 │
│  ├── tasks.ts: CRUD tasks                           │
│  └── storage.ts: upload / download file             │
│                                                      │
│  🪝 HOOKS (hooks/)                                   │
│  └── Logic tái sử dụng (useAuth, useTasks)          │
│                                                      │
│  📋 TYPES (types/)                                   │
│  └── Định nghĩa cấu trúc dữ liệu                  │
│                                                      │
└──────────────────────────────────────────────────────┘
Điểm mấu chốt: Dự án Next.js được tổ chức theo nguyên tắc “mỗi nhóm lo một việc”: Pages lo bố cục trang, Components lo giao diện tái sử dụng, Services lo kết nối dữ liệu. 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.