Bỏ qua

Sổ Tay Cấp Cứu: Giải Mã Các Lỗi Hay Gặp 🚑

Dưới đây là danh sách "bắt bệnh" những lỗi kinh điển mà người mới rất hay gặp phải khi lắp ráp hệ thống này. Ở mỗi lỗi, mình sẽ chỉ rõ: Biểu hiện (Triệu chứng)Vì sao lại thế (Nguyên nhân)Cách chữa trị (Fix). Cứ bình tĩnh dò theo nhé!


Lỗi Mạng / Chặn Cửa (CORS)

Trình duyệt báo lỗi thiếu Access-Control-Allow-Origin

Biểu hiện: Bật F12 xem tab Console trên trình duyệt, bạn thấy dòng chữ đỏ rực:

Access to fetch at '[https://api.workers.dev/groups](https://api.workers.dev/groups)' from origin '[https://app.pages.dev](https://app.pages.dev)'
has been blocked by CORS policy

Nguyên nhân: Web (Frontend) và Trạm xử lý (Backend) đang nằm ở 2 địa chỉ khác nhau. Trình duyệt nghi ngờ bị hack nên chặn lại vì Backend chưa thông báo là "Tôi cho phép anh Web này lấy dữ liệu".

Cách chữa:

  1. Mở file wrangler.toml kiểm tra xem đã điền đúng địa chỉ Web chưa:
[vars]
FRONTEND_URL = "[https://app.pages.dev](https://app.pages.dev)"  # ĐÚNG đường link web của bạn nhé, cẩn thận dư dấu gạch chéo ở cuối.
  1. Trong file src/index.ts, hãy chắc chắn hàm app.use('*', cors({...})) được đặt ở trên cùng, trước tất cả các đường dẫn (routes) khác.
  2. Sửa xong nhớ gõ wrangler deploy để cập nhật lên mạng.

Bị lỗi 404 ở request "thăm dò" (Preflight OPTIONS)

Nguyên nhân: Khi gọi API khác domain, trình duyệt hay ném thử một cục đá dò đường (request dạng OPTIONS) xem cổng có mở không. Nếu cấu hình Hono không đón đầu cái request này thì nó sẽ báo 404 (Không tìm thấy).

Cách chữa: Hãy gắn dấu sao * để nó bắt mọi loại request.

app.use('*', cors({ /* ... */ }));  // ✅ Chuẩn
// app.use('/groups', cors(...))    // ❌ Sai nhé, thế này nó không bắt được request thăm dò đâu

Lỗi Thẻ Thông Hành (JWT)

Vừa đăng nhập xong đã báo Token không hợp lệ hoặc đã hết hạn

Nguyên nhân: Bạn đổi ổ khóa (JWT_SECRET) nhưng người dùng vẫn cầm chìa khóa cũ (Token cũ). Hoặc ổ khóa ở máy bạn (local) đang khác với ổ khóa trên mạng (production).

Cách chữa: Cấp lại ổ khóa cho đồng nhất:

# Ở trên mạng (Production)
wrangler secret put JWT_SECRET

# Ở dưới máy tính (Local) — Mở file .dev.vars và thêm dòng này:
echo "JWT_SECRET=long-random-secret-32-chars-or-more" >> api/.dev.vars

Lưu ý: Cập nhật mật mã xong thì mấy cái thẻ cũ vô dụng hết, bạn phải đăng nhập lại từ đầu nhé.

Token chạy dưới máy thì ngon, lên mạng lại ngỏm

Nguyên nhân hay gặp nhất: Bạn đang tính thời gian hết hạn bằng Mili-giây (milliseconds), trong khi chuẩn quốc tế của JWT yêu cầu tính bằng Giây (seconds).

Cách chữa: Chia cho 1000 để đổi ra giây nhé:

// ✅ ĐÚNG (Chuẩn quốc tế)
const exp = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30; // 30 ngày
if (payload.exp < Math.floor(Date.now() / 1000)) return null;

// ❌ SAI (Để mili-giây là token bị coi là hết hạn từ đời tám hoánh nào rồi)
const exp = Date.now() + 7 * 24 * 60 * 60 * 1000;
if (payload.exp < Date.now()) return null;

Lỗi Kho Dữ Liệu (D1 / Database)

Báo lỗi D1_ERROR: no such table: users (Không tìm thấy bảng)

Nguyên nhân: Database chưa được khởi tạo bảng, giống như xây nhà mà chưa chia phòng vậy. Bạn quên chạy kịch bản (migration) rồi.

Cách chữa:

# Nếu đang code ở máy
wrangler d1 migrations apply <tên-db-của-bạn> --local

# Nếu muốn áp dụng cho bản trên mạng
wrangler d1 migrations apply <tên-db-của-bạn>

Báo lỗi D1_ERROR: UNIQUE constraint failed: users.email

Nguyên nhân: Bạn đang cố đăng ký một cái email đã tồn tại trong hệ thống. (Cột email đã được đánh dấu là UNIQUE - Độc nhất vô nhị).

Cách chữa: Cần kiểm tra xem email có chưa trước khi thêm:

const existing = await c.env.DB.prepare('SELECT id FROM users WHERE email = ?')
  .bind(email).first();
if (existing) return c.json({ error: 'Email này có người xài rồi bạn ơi' }, 409);

Báo lỗi D1_ERROR: FOREIGN KEY constraint failed

Nguyên nhân: Lỗi "cọc đi tìm trâu". Bạn đang cố thêm một dữ liệu mà nó lại trỏ tới một dữ liệu gốc không tồn tại (Ví dụ: Thêm giao dịch cho một thành viên không có trong nhóm).

Cách chữa: Hãy luôn kiểm tra xem "thằng cha" có tồn tại không trước khi tạo "thằng con".

Code trên máy chạy lệnh Migration cứ đứng im không tác dụng

Cách chữa: Đôi khi máy nó bị "ngáo". Hãy xóa bộ nhớ đệm đi rồi chạy lại:

rm -rf api/.wrangler/state
wrangler d1 migrations apply <tên-db-của-bạn> --local

Lỗi Môi Trường Code (Wrangler / Workers)

Chạy wrangler dev báo binding 'DB' is not defined

Nguyên nhân: Worker không biết cái DB bạn nhắc tới là cái gì. Bạn quên đấu dây trong wrangler.toml rồi.

Cách chữa:

[[d1_databases]]
binding = "DB"
database_name = "my-fund-db"
database_id = "PASTE_YOUR_DATABASE_ID_VÀO_ĐÂY"

Báo lỗi Error: Worker exceeded CPU time limit

Nguyên nhân: Cloudflare có quy định: Code chạy miễn phí không được suy nghĩ (chạy ngầm) quá 10 mili-giây. Bạn bắt nó tính toán quá nặng (như băm mật khẩu mà quên lưu đệm, hoặc viết Regex bị lặp vô tận) thì nó sẽ sập cầu dao.

Cách chữa:

  • Đẩy các tác vụ nặng ra chỗ khác (như Durable Objects hay Queues).
  • Nếu ứng dụng thực sự cần xử lý nặng, hãy nâng cấp lên gói Paid của Workers (cho tận 30 giây suy nghĩ).

Chạy lệnh soi log wrangler tail mà màn hình tối thui

Nguyên nhân: Nó không bị lỗi đâu! Nó chỉ hiện log khi thực sự có ai đó đang gọi vào API.

Cách chữa: Để nó chạy đó, mở một Terminal khác hoặc dùng trình duyệt gọi thử một cái là chữ sẽ nhảy ra:

curl [https://api.workers.dev/health](https://api.workers.dev/health)

Lỗi Khi Up Web Lên Mạng (Cloudflare Pages)

Nguyên nhân: Ứng dụng React của bạn là Single Page App (SPA). Cloudflare Pages khi thấy đường dẫn lạ, nó đi tìm file abc123.html và không thấy nên báo 404. Bạn phải dặn nó: "Gặp đường dẫn nào cũng ném về file index.html cho tôi, React sẽ tự lo phần còn lại".

Cách chữa: Tạo file _redirects trong thư mục web/public:

/* /index.html   200

Xong nhớ build và đẩy code (redeploy) lại nhé.

Biến import.meta.env.VITE_API_URL bị undefined khi lên mạng

Nguyên nhân:

  • Bạn đặt tên biến không bắt đầu bằng chữ VITE_ (Vite chỉ cho phép lộ những biến có chữ này ra mặt tiền).
  • Bạn chưa nhập biến này trên giao diện Dashboard của Cloudflare.
  • Bạn nhập rồi nhưng chưa cho deploy lại (Biến môi trường chỉ ngấm vào code lúc build thôi).

Cách chữa:

  1. Vào Cloudflare Dashboard → Pages → Chọn project → Settings → Environment variables → thêm biến VITE_API_URL.
  2. Deploy lại web: wrangler pages deploy dist --project-name <tên-project>

Lỗi Giao Diện (React / Vite)

Báo lỗi Cannot find module '@vitejs/plugin-react-swc'

Cách chữa: Bạn thiếu gói dịch code siêu tốc của Vite. Cài vào là xong:

npm install -D @vitejs/plugin-react-swc

Sửa code, lưu lại mà Web không tự động cập nhật (HMR hỏng)

Nguyên nhân: Tính năng theo dõi file bị ngáo (thường hay gặp nếu bạn xài Windows chạy WSL2 hoặc Docker).

Cách chữa: Mở vite.config.ts lên và bật chế độ ép buộc theo dõi:

server: { watch: { usePolling: true } }

Báo lỗi "Hooks can only be called inside the body of a function component"

Nguyên nhân:

  1. Bạn đang xài Hook (mấy cái bắt đầu bằng use...) ở trong một hàm tính toán bình thường. Hook chỉ được sống trong Functional Component thôi.
  2. Ứng dụng đang bị cài lẫn lộn 2 phiên bản React khác nhau.

Cách chữa: Kiểm tra xem có bị trùng React không:

npm ls react
# Nếu thấy hiện ra 2-3 bản khác nhau → gõ npm dedupe để nó dọn dẹp.

Lỗi Lấy Dữ Liệu (TanStack Query)

Thêm/Sửa/Xóa thành công mà màn hình không thèm cập nhật dữ liệu mới

Nguyên nhân: Bạn dọn rác xong mà quên báo cho quản gia đi quét lại nhà. Quên dùng invalidateQueries rồi.

Cách chữa:

const mutation = useMutation({
  mutationFn: createTransaction,
  onSuccess: () => {
    // Gọi lệnh này để Query tự động đi lấy dữ liệu mới
    queryClient.invalidateQueries({ queryKey: ['transactions', groupId] });
    queryClient.invalidateQueries({ queryKey: ['group', groupId] });
  },
});

Vừa vào web nó đã gọi API báo lỗi búa xua (do chưa có token)

Cách chữa: Hãy đặt cờ enabled để kìm nó lại, bảo nó "Khi nào có token mới được chạy nhé":

const { data } = useQuery({
  queryKey: ['groups'],
  queryFn: () => api.groups.list(token!),
  enabled: !!token,  // Có thẻ mới được chạy!
});

Lỗi Lưu Trạng Thái (Zustand)

F5 (Reload) lại trang web là bị văng ra bắt đăng nhập lại

Nguyên nhân: Bộ nhớ của Zustand mắc bệnh cá vàng, tắt web là quên sạch. Bạn phải bảo nó ghi chép lại vào bộ nhớ trình duyệt (localStorage).

Cách chữa: Bọc cái store lại bằng persist:

import { persist } from 'zustand/middleware';

export const useAuthStore = create()(
  persist(
    (set) => ({ /* ... */ }),
    { name: 'my-auth' },  // Đặt cái tên chìa khóa này cho độc lạ chút để không bị trùng web khác
  ),
);

F5 xong thì dữ liệu vẫn còn nhưng các hàm (function) trong store thì báo lỗi

Chuyện bình thường: Tính năng persist chỉ lưu được dạng chữ và số thôi, nó không lưu được code (hàm). Hàm sẽ tự động được phục hồi khi code React của bạn khởi chạy lại. Nếu báo lỗi thì thường là do cách bạn gọi hàm bị sai nhịp.


Lỗi Chống Spam (Turnstile)

Cái ô tick "Tôi không phải người máy" biến mất tiêu

Nguyên nhân: Thiếu file script của Cloudflare rồi.

Cách chữa: Mở web/index.html dán dòng này vào trước thẻ đóng </head>:

<script src="[https://challenges.cloudflare.com/turnstile/v0/api.js](https://challenges.cloudflare.com/turnstile/v0/api.js)" async defer></script>

Tick xanh rồi mà server vẫn chửi 'invalid-input-response'

Nguyên nhân: Mã Captcha bạn tick chỉ có hạn xài trong khoảng 5 phút. Bạn ngâm form lâu quá hoặc đã ấn gửi 1 lần rồi bị lỗi, mã đó đã bị dùng nên vô hiệu.

Cách chữa: Cứ bấm gửi là phải làm mới lại mã:

<TurnstileWidget
  onVerify={setTurnstileToken}
  onExpire={() => setTurnstileToken('')} // Bắt sự kiện hết hạn để xóa  
/>

Lỗi Đẩy Code Lên Mạng (Deploy)

Báo lỗi Authentication error khi gõ lệnh deploy

Cách chữa: Có thể phiên đăng nhập của bạn trên Terminal hết hạn. Đăng xuất ra vào lại:

wrangler logout
wrangler login

Lên web thật test thử thì văng lỗi 1101 Worker threw exception

Cách chữa: Lỗi này chung chung lắm. Phải soi log:

wrangler tail
# Để cửa sổ đó, qua trình duyệt load lại trang cho nó bắn lỗi ra rồi đọc xem cụ thể là gì.

Chạy Database trên máy OK, mà đẩy lên mạng báo fail

Nguyên nhân: Cái database_id trong file wrangler.toml của bạn đang trỏ bậy bạ.

Cách chữa:

wrangler d1 list
# Gõ lệnh này rồi nhìn kỹ xem ID trên màn hình có khớp 100% với trong file TOML không.

Khi Nào Thì Cần Giơ Cờ Trắng Báo Cáo Cứu Viện? 🏳️

Nếu bạn đã thử hết các cách trên mà vẫn bế tắc, hãy làm theo các bước sau trước khi đi hỏi:

  1. Đọc kỹ log báo lỗi của Backend bằng lệnh: wrangler tail.
  2. Bật F12 (DevTools) trên trình duyệt → Chọn tab Network (Mạng) → Bấm vào cái API bị đỏ để xem chính xác server đang chửi câu gì ở phần Response.
  3. Copy y nguyên câu báo lỗi lên Google hoặc tìm trong nhóm Cloudflare Workers Discord.
  4. Tìm trên Stack Overflow: Thường 99% các lỗi bạn gặp thì những người đi trước đã đạp phải và có giải pháp rồi.

Khi đi hỏi bài, hãy nhớ quy tắc vàng: Luôn cung cấp chính xác câu báo lỗi + Bạn đã làm gì để ra lỗi đó + Bạn đang chạy ở máy (local) hay trên mạng (production). Đừng hỏi "anh ơi code em không chạy", các cao thủ sợ lắm!