Giới thiệu

Hướng dẫn này giúp bạn viết prompt hiệu quả để Claude Code tạo các tính năng Firestore trong dự án Next.js, bao gồm CRUD, lọc dữ liệu và tìm kiếm.
Tất cả ví dụ trong bài dựa trên hệ thống Task Management với các collection tasks, projects, users và subcollection comments. Thay tên field và collection cho phù hợp với dự án của bạn.

Nguyên tắc cơ bản

Để Claude Code tạo tính năng chất lượng cao, prompt cần có:
  1. Phạm vi rõ ràng: Chỉ rõ cái gì, không phải làm thế nào
  2. Cung cấp ngữ cảnh: Cấu trúc dữ liệu, yêu cầu nghiệp vụ
  3. Liệt kê chi tiết: Xử lý lỗi, validation, kỳ vọng về kiểu dữ liệu
  4. Tránh yêu cầu ngoài phạm vi: Không yêu cầu refactor hay tối ưu hoá ngoài scope

Cấu trúc dữ liệu — Task Management

Trước khi viết prompt, cần nắm rõ cấu trúc dữ liệu của hệ thống:
tasks                          ← công việc cần làm
  ├── {taskId}
  │   ├── title: string
  │   ├── description: string
  │   ├── status: "todo" | "in_progress" | "done" | "blocked"
  │   ├── priority: "low" | "medium" | "high" | "critical"
  │   ├── projectId: Reference → projects/{projectId}
  │   ├── assignee: Reference → users/{userId}
  │   ├── reporter: Reference → users/{userId}
  │   ├── tags: string[]
  │   ├── watchers: string[]
  │   ├── dependencies: string[]
  │   ├── isCompleted: boolean
  │   ├── isBlocked: boolean
  │   ├── isPinned: boolean
  │   ├── hasAttachments: boolean
  │   ├── estimatedHours: number
  │   ├── actualHours: number
  │   ├── percentComplete: number
  │   ├── storyPoints: number
  │   ├── dueDate: Timestamp
  │   ├── completedAt: Timestamp | null
  │   ├── createdAt: Timestamp
  │   └── updatedAt: Timestamp

  └── {taskId}/comments        ← subcollection bình luận của task
      └── {commentId}
          ├── content: string
          ├── author: Reference → users/{userId}
          └── createdAt: Timestamp

projects                       ← dự án
  └── {projectId}
      ├── name: string
      ├── description: string
      ├── status: "active" | "archived" | "completed"
      ├── teamSize: number
      ├── createdAt: Timestamp
      └── updatedAt: Timestamp

users                          ← thành viên team
  └── {userId}
      ├── name: string
      ├── email: string
      ├── role: "admin" | "manager" | "developer" | "designer"
      ├── isActive: boolean
      ├── skills: string[]
      ├── createdAt: Timestamp
      └── updatedAt: Timestamp

Ngữ cảnh dự án Next.js

Khi viết prompt cho dự án Next.js, luôn chỉ rõ:
  • Vị trí file: Đặt code ở đâu (ví dụ: services/task-services.ts, components/add-task-modal.tsx)
  • Loại component: Server component, Client component, hoặc utility module
  • Điểm tích hợp: Kết nối với service, component nào đã có
  • Kiểu dữ liệu: Tham chiếu services/types/task-types.ts cho các type đã định nghĩa
  • Xử lý lỗi: Lỗi được xử lý như thế nào (state cho component, throw cho service)
  • TypeScript: Luôn chỉ định dùng TypeScript
Cấu trúc thư mục cho Task Management app:
Cấu trúc thư mục
modules
└── tasks
    ├── components
    │   ├── add-task-modal.tsx      # Modal thêm task mới
    │   ├── columns.tsx             # Định nghĩa cột bảng dữ liệu
    │   ├── data-table-column-header.tsx
    │   ├── data-table-faceted-filter.tsx  # Bộ lọc nhiều điều kiện
    │   ├── data-table-pagination.tsx      # Phân trang
    │   ├── data-table-row-actions.tsx     # Thành phần hành động từng dòng
    │   ├── data-table-toolbar.tsx         # Thanh công cụ bảng
    │   ├── data-table-view-options.tsx    # Tùy chọn hiển thị
    │   ├── data-table.tsx                 # Component bảng chính
    └── services        
        ├── types
        │   └── task-types.ts           # Định nghĩa kiểu dữ liệu Task
        ├── task_mock-data.ts           # Data mẫu Task
        └── task-services.ts            # Service kết nối Firestore thực tế
        └── task-charts-services.ts     # Service lấy dữ liệu để vẽ biểu đồ   
        └── task-statistics-services.ts # Service lấy dữ liệu thống kê

1. Thao tác CRUD

1.1 Tạo mới (Create)

Ví dụ prompt tốt:
Thêm function để tạo task mới trong Firestore:
- File: lib/firebase/tasks.ts
- Collection: "tasks"
- Fields bắt buộc: title, projectId, reporter
- Fields tùy chọn: description, assignee, dueDate, priority, tags
- Giá trị mặc định:
  - status: "todo"
  - priority: "medium"
  - isCompleted: false, isBlocked: false, isPinned: false
  - estimatedHours: 0, actualHours: 0, percentComplete: 0
  - createdAt: server timestamp, updatedAt: server timestamp
- Validation:
  - title: bắt buộc, 3–200 ký tự
  - projectId: phải tồn tại trong collection "projects"
  - dueDate: nếu có, phải là ngày trong tương lai
  - priority: chỉ nhận "low" | "medium" | "high" | "critical"
- Return: { id: string, task: Task }
- Error handling: ValidationError cho input sai, ProjectNotFoundError cho projectId sai
- Không tạo API route, chỉ utility function

1.2 Đọc dữ liệu (Read)

Tạo function để lấy task theo ID:
- Collection: "tasks", Document ID: taskId
- Return: Task object với tất cả fields hoặc null nếu không tìm thấy
- Xử lý Firestore errors một cách thuần tuý
- Bao gồm TypeScript Task interface

1.3 Cập nhật (Update)

Tạo function updateTask:
- Nhận taskId và partial task data
- Chỉ cập nhật các fields được cung cấp (partial update, không xóa fields khác)
- Fields cho phép cập nhật: title, description, status, priority, assignee,
  dueDate, tags, estimatedHours, actualHours, percentComplete, isBlocked, isPinned
- Ngăn chặn cập nhật: createdAt, reporter, projectId
- Tự động:
  - Set updatedAt = server timestamp
  - Nếu status chuyển sang "done", set isCompleted = true và completedAt = server timestamp
  - Nếu percentComplete = 100, tự động set status = "done"
- Validate status: chỉ nhận "todo" | "in_progress" | "done" | "blocked"
- Xử lý lỗi document not found

1.4 Xóa (Delete)

Thêm function deleteTask:
- Soft delete: set status thành "archived" (không xoá khỏi Firestore)
- Ghi lại: updatedAt = server timestamp
- Không xoá subcollection comments (giữ lại lịch sử)
- Return: true nếu thành công, false nếu taskId không tồn tại
- Xử lý not found một cách đuồ đặt (không throw lỗi)

2.1 Lọc đơn giản

Tạo function getTasksByStatus:
- Collection: "tasks"
- Filter: where("status", "==", status)
- status chỉ nhận: "todo" | "in_progress" | "done" | "blocked"
- Return: Task[]
- Order by: dueDate ascending
- Hỗ trợ phân trang (limit parameter, mặc định 20)

2.2 Lọc phức tạp (nhiều điều kiện)

Thêm function searchTasks với nhiều điều kiện lọc:
- Collection: "tasks"
- Bộ lọc (tất cả tùy chọn):
  - projectId: exact match
  - assignee: exact match (userId)
  - status: exact match ("todo" | "in_progress" | "done" | "blocked")
  - priority: exact match ("low" | "medium" | "high" | "critical")
  - isBlocked: boolean
  - dueBefore: Timestamp range (dueDate <= dueBefore)
  - dueAfter: Timestamp range (dueDate >= dueAfter)
  - tags: array-contains
- Order by: dueDate ascending, updatedAt descending
- Return: Task[]
- Nếu không có bộ lọc, return tất cả tasks không phải "archived"
Composite index: Firestore yêu cầu tạo composite index khi kết hợp nhiều where() khác field. Trong prompt, hãy liệt kê rõ các field được filter cùng nhau để Claude Code tạo đúng query và nhậc bạn tạo index.

2.3 Phân trang (Pagination)

Cập nhật function searchTasks để hỗ trợ cursor-based pagination:
- Parameters: filters (như trên), pageSize (mặc định 20), lastDoc (DocumentSnapshot | null)
- Lần gọi đầu tiên: lastDoc = null (lấy trang đầu)
- Các trang tiếp theo: truyền lastDoc từ kết quả trước (dùng startAfter)
- Return: { tasks: Task[], lastDoc: DocumentSnapshot | null, hasMore: boolean }
- Order by: dueDate ascending
Thêm function searchTasksByTitle:
- Tìm kiếm query string trên field "title"
- Thực hiện dùng where("title", ">=", query) và where("title", "<=", query + "\uf8ff")
- Kết hợp client-side filter để loại kết quả không khớp
- Xử lý search string rỗng (return empty array)
- Return: Task[] với limit 30 kết quả đầu
  • Nếu không có filter, return tất cả tasks không phải “archived”

<Info>
**Composite index**: Firestore yêu cầu tạo composite index khi kết hợp nhiều `where()` khác field. Trong prompt, hãy liệt kê rõ các field được filter cùng nhau để Claude Code tạo đúng query và nhắc bạn tạo index.
</Info>

### 2.3 Pagination

**Example prompt:**

```text Prompt
Update function searchTasks để hỗ trợ cursor-based pagination:
- Parameters: filters (như trên), pageSize (mặc định 20), lastDoc (DocumentSnapshot | null)
- Lần gọi đầu tiên: lastDoc = null (lấy trang đầu)
- Các trang tiếp theo: truyền lastDoc từ kết quả trước (dùng startAfter)
- Return: { tasks: Task[], lastDoc: DocumentSnapshot | null, hasMore: boolean }
- Order by: dueDate ascending
Example prompt:
Prompt
Thêm function searchTasksByTitle:
- Search query string trên field "title"
- Implement dùng where("title", ">=", query) và where("title", "<=", query + "\uf8ff")
- Kết hợp client-side filter để loại kết quả không khớp
- Handle empty search string (return empty array)
- Return: Task[] với limit 30 kết quả đầu
Firestore không có native full-text search. Các lựa chọn thay thế:
  • Client-side filtering (dataset nhỏ)
  • Algolia / Typesense (dataset lớn, search nâng cao)
  • Firebase Search Extension

2.5 React Hooks (Next.js Client Components)

Tạo hook hooks/useTasks.ts:
- Export hook: useTasks(filters: TaskFilters)
- State: { tasks: Task[], loading: boolean, error: Error | null }
- Lấy dữ liệu từ API endpoint /api/tasks/search với filters làm query params
- Gọi lại khi filters thay đổi (useEffect với filters làm dependency)
- Return: { tasks, loading, error, refetch }
- Xử lý cleanup khi component unmount

3. Các pattern tốt cho Firestore Prompt

3.1 Định nghĩa kiểu dữ liệu rõ ràng

# Kiểu enum (giá trị cố định)
task:
  status:   todo | in_progress | done | blocked
  priority: low | medium | high | critical

project:
  status: active | archived | completed

user:
  role: admin | manager | developer | designer

# Mô tả cấu trúc task đầy đủ
task:
  id: text
  title: text (bắt buộc)
  description: text (tùy chọn)
  status: todo | in_progress | done | blocked
  priority: low | medium | high | critical
  projectId: id của project
  assignee: id của user (tùy chọn)
  reporter: id của user (bắt buộc)
  tags: danh sách text
  isCompleted: true | false
  isBlocked: true | false
  estimatedHours: số
  actualHours: số
  percentComplete: số (0–100)
  dueDate: timestamp
  completedAt: timestamp hoặc null
  createdAt: timestamp
  updatedAt: timestamp

# Sau đó yêu cầu thực hiện:
# createTask, getTask, updateTask, deleteTask, getTaskComments, addComment

3.2 Xử lý lỗi (Error Handling)

Cho function getTaskWithDetails:
- Throw các lỗi cụ thể:
  - TaskNotFoundError khi taskId không tồn tại
  - ValidationError cho taskId format không hợp lệ
  - FirestoreError cho lỗi network/permission
- Bao gồm thông báo lỗi kèm ngữ cảnh (ví dụ: taskId nào gây lỗi)

3.3 Kiểm tra dữ liệu (Validation)

Thêm validation cho createTask:
- title: bắt buộc, 3–200 ký tự
- projectId: bắt buộc, phải tồn tại trong collection "projects"
- dueDate: nếu có, phải là ngày trong tương lai
- priority: phải là "low" | "medium" | "high" | "critical"
- tags: mỗi tag tối đa 30 ký tự, tối đa 10 tags
- Return: { success: true, id: string } hoặc { success: false, errors: string[] }

3.4 Giao dịch nguyên tử (Transactions)

Tạo function completeTask sử dụng Firestore transaction:
- Cập nhật task document:
  - status: "done"
  - isCompleted: true
  - completedAt: server timestamp
  - percentComplete: 100
- Cập nhật user stats trong collection "users":
  - Tăng completedTasksCount lên 1
  - Cập nhật lastActivityAt: server timestamp
- Dùng Firestore transaction để đảm bảo atomicity (cả 2 updates hoặc không cái nào)
- Return: task ID nếu thành công
- Tự động rollback nếu bất kỳ bước nào thất bại
- Throw TaskNotFoundError nếu taskId không hợp lệ

4. Những prompt nên tránh

Thêm các Firestore function
Tạo CRUD cho tasks

Tạo một hàm tìm kiếm

Xây dựng toàn bộ hệ thống quản lý công việc

5. Mẫu prompt (Templates)

Template CRUD

Template CRUD
Tạo function [tên function] cho collection [tên collection]:

Đầu vào:
- Tham số: [danh sách kèm kiểu dữ liệu]
- Validation: [quy tắc kiểm tra]

Lo-gic:
- Thao tác: [create/read/update/delete]
- Điều kiện query: [nếu có]
- Biến đổi dữ liệu: [nếu có]

Đầu ra:
- Kiểu trả về: [mô tả chính xác]
- Trường hợp thành công: [dữ liệu trả về]
- Trường hợp lỗi: [xử lý lỗi cụ thể]

Bổ sung:
- Xử lý timestamp: [createdAt/updatedAt/completedAt]
- Validation enum: [giá trị hợp lệ của status/priority]
- Thông báo lỗi: [mô tả lỗi có ngữ cảnh]

Template Lọc/Tìm kiếm

Template Search/Filter
Tạo function [tên function]:

Collection: [tên] (hoặc subcollection: [parent/{id}/tên])
Lọc theo:
- Field 1: [kiểu] ([exact match / range / array-contains])
- Field 2: [kiểu] ([exact match / range / array-contains])

Sắp xếp:
- Chính: [field] [tăng/giảm]
- Phụ: [field] [tăng/giảm]

Phân trang:
- Kiểu: cursor-based
- Số lượng mặc định: [số]

Trả về:
- Kiểu: { items: T[], lastDoc: DocumentSnapshot | null, hasMore: boolean }
- Bao gồm: [các field cần lấy]

Trường hợp biên:
- Không có kết quả: return mảng rỗng
- Giá trị enum không hợp lệ: throw ValidationError
- Dataset lớn: cursor-based pagination

6. Danh sách kiểm tra trước khi gửi prompt

Phần Firestore:
  • Tên collection rõ ràng (bao gồm subcollection nếu dùng tasks/{id}/comments)
  • Tên field và kiểu dữ liệu được liệt kê đầy đủ
  • Enum field được chỉ rõ giá trị hợp lệ (status, priority, role)
  • Quy tắc validation được mô tả
  • Cách xử lý lỗi được mô tả
  • Kiểu dữ liệu trả về được định nghĩa rõ
  • Các trường hợp biên được xem xét
Phần Next.js:
  • Vị trí file được chỉ định (lib/firebase/, app/api/, hooks/)
  • Loại component rõ ràng (utility, API route, hook, server component, server action)
  • Điểm tích hợp được ghi rõ (kết nối với code nào đã có)
  • Biến môi trường được tham chiếu đúng (process.env.NEXT_PUBLIC_*)
  • TypeScript types được định nghĩa
  • Định dạng lỗi trả về phù hợp với quy ước Next.js
Tổng quát:
  • Không có yêu cầu ngoài phạm vi (refactor, tối ưu hoá)
  • Prompt 100–300 từ — đủ chi tiết, không quá phức tạp
  • Tham chiếu các pattern hiện có trong codebase nếu có

7. Các pattern phổ biến trong Next.js + Firestore

Pattern 1: Utility Function

Dùng khi: Cần logic tái sử dụng, gọi từ nhiều nơi File: lib/firebase/tasks.ts Prompt cần có: Chữ ký hàm, tham số, kiểu trả về, xử lý lỗi

Pattern 2: API Route

Dùng khi: Cần HTTP endpoint cho client gọi qua mạng File: app/api/tasks/route.ts Prompt cần có: HTTP method, body/query params, định dạng phản hồi, status codes

Pattern 3: Server Component

Dùng khi: Lấy dữ liệu phía server trong quá trình render trang File: app/tasks/page.tsx Prompt cần có: Cách dùng server component, mẫu await, xử lý lỗi phía server

Pattern 4: Client Hook

Dùng khi: Cập nhật real-time, tương tác người dùng, gửi form File: hooks/useTasks.ts hoặc hooks/useTask.ts Prompt cần có: useState, useEffect, cleanup, trạng thái loading/error/success

Pattern 5: Server Action

Dùng khi: Gửi form, thay đổi dữ liệu từ client không cần API route File: app/actions/tasks.ts Prompt cần có: Chỉ thị 'use server', định nghĩa kiểu, validation, giá trị trả về Sơ đồ quyết định:
Sơ đồ chọn pattern
Chọn pattern cho thao tác Firestore:

Chỉ đọc dữ liệu?
  ├─ Server component (page/layout)? → Pattern 1 hoặc Pattern 3
  └─ Client component?               → Pattern 4 (hook)

Cần cập nhật real-time?
  └─ → Pattern 4 (hook với onSnapshot listener)

Thay đổi/ghi dữ liệu?
  ├─ Gửi form?             → Pattern 5 (server action) hoặc Pattern 2 (API route)
  └─ Tương tác client?      → Pattern 2 (API route) hoặc Pattern 5 (server action)

Logic dùng chung (xác thực, validation)?
  └─ → Pattern 1 (utility function)

8. Ví dụ thực tế

Ví dụ 1: Tạo task có validation

Prompt
Thêm function createTask trong lib/firebase/tasks.ts:
- Collection: "tasks"
- Đầu vào: { title, projectId, reporter, description?, assignee?, dueDate?, priority?, tags? }
- Validation:
  - title: bắt buộc, 3–200 ký tự
  - projectId: phải tồn tại trong collection "projects"
  - dueDate: nếu có, phải là ngày trong tương lai
  - priority: chỉ nhận "low" | "medium" | "high" | "critical"
  - tags: tối đa 10 tags, mỗi tag ≤ 30 ký tự
- Fields cần lưu:
  - Tất cả đầu vào
  - status: "todo" (mặc định)
  - priority: "medium" nếu không truyền
  - isCompleted: false, isBlocked: false, isPinned: false
  - estimatedHours: 0, actualHours: 0, percentComplete: 0
  - createdAt: server timestamp, updatedAt: server timestamp
- Return: { id: string, task: Task }
- Lỗi: ValidationError cho dữ liệu sai, ProjectNotFoundError cho projectId sai

Ví dụ 2: Tìm kiếm + Phân trang

Prompt
Tạo function searchTasks trong lib/firebase/queries.ts:
- Collection: "tasks"
- Bộ lọc (tất cả tùy chọn):
  - projectId: exact match
  - assignee: exact match (userId string)
  - status: "todo" | "in_progress" | "done" | "blocked"
  - priority: "low" | "medium" | "high" | "critical"
  - isBlocked: boolean
  - tags: array-contains (một tag)
- Sắp xếp: dueDate tăng dần, updatedAt giảm dần
- Phân trang cursor-based (pageSize mặc định 20)
- Return: { tasks: Task[], hasMore: boolean, nextCursor: DocumentSnapshot | null }
- Không có bộ lọc: return tất cả tasks có status != "archived"

Ví dụ 3: Hoàn thành task bằng Transaction

Prompt
Tạo function completeTask trong lib/firebase/tasks.ts:
- Đầu vào: taskId: string, notes?: string
- Dùng Firestore transaction để cập nhật đồng thời:
  1. Task document (tasks/{taskId}):
     - status: "done"
     - isCompleted: true
     - percentComplete: 100
     - completedAt: server timestamp
     - updatedAt: server timestamp
  2. User stats (users/{assigneeId}):
     - Tăng completedTasksCount += 1
     - lastActivityAt: server timestamp
- Return: { success: true }
- Lỗi: TaskNotFoundError nếu taskId không tồn tại
- Tự động rollback cả 2 updates nếu bất kỳ bước nào thất bại

Ví dụ 4: Subcollection comment

Prompt
Thêm function addComment trong lib/firebase/comments.ts:
- Subcollection: tasks/{taskId}/comments
- Đầu vào: { taskId, content, authorId }
- Validation: content bắt buộc, 1–2000 ký tự
- Lưu:
  - content: string
  - author: Reference users/{authorId}
  - createdAt: server timestamp
- Sau khi thêm comment: cập nhật task document:
  - hasAttachments: true nếu content chứa URL/file reference
  - updatedAt: server timestamp
- Return: { id: string, comment: Comment }
- Lỗi: TaskNotFoundError nếu taskId không tồn tại

Ví dụ 5: Quy trình đầy đủ Next.js

Prompt
Tạo tính năng quản lý task với 3 files:

1. lib/firebase/tasks.ts:
   - createTask(data: CreateTaskInput): Promise<{ id, task }>
   - getTaskById(taskId: string): Promise<Task | null>
   - updateTask(taskId: string, data: Partial<Task>): Promise<Task>
   - deleteTask(taskId: string): Promise<boolean>
   - Export interfaces: Task, CreateTaskInput, TaskStatus, TaskPriority

2. app/api/tasks/route.ts:
   - GET: query params ?projectId=&status=&assignee= → gọi searchTasks
   - POST: body { title, projectId, reporter, ... } → gọi createTask
   - Xử lý lỗi validation (return 400)
   - Xử lý project không tồn tại (return 404)
   - Return đúng status codes

3. hooks/useTaskMutation.ts:
   - Hook: useTaskMutation()
   - createTask(data): gọi POST /api/tasks
   - updateTask(id, data): gọi PATCH /api/tasks/[id]
   - deleteTask(id): gọi DELETE /api/tasks/[id]
   - Quản lý trạng thái loading, error
   - Cleanup khi component unmount

Yêu cầu:
   - TypeScript cho tất cả files
   - Xử lý lỗi mạng
   - Validation ở cả client (hook) và server (API route)
   - Tuân theo quy ước đặt tên Next.js
   - Không tạo thêm files ngoài 3 files trên

Tài liệu liên quan