# Bài 23 — Lỗi thường gặp & cách debug
🎯 Mục đích
Đây là trang tra cứu nhanh. Khi bị kẹt, dùng Ctrl+F tìm thông báo lỗi
bạn đang gặp.
Cuối bài có mục Phương pháp debug — quan trọng hơn cả danh sách lỗi, vì nó dạy bạn cách tự tìm ra nguyên nhân cho những lỗi không có trong danh sách này.
📑 Mục lục nhanh
- A. Lỗi khởi động & môi trường
- B. Lỗi MongoDB
- C. Lỗi Express / Backend
- D. Lỗi React / Next.js
- E. Lỗi Redux
- F. Lỗi kết nối Frontend ↔ Backend
- G. Lỗi upload file
- H. Lỗi khi deploy
- I. Phương pháp debug
- J. Bảng tra lỗi riêng của dự án này
A. Lỗi khởi động & môi trường
'node' is not recognized as an internal or external command
Node chưa vào PATH.
- Đóng hết terminal, mở lại
- Vẫn lỗi → cài lại Node, nhớ tick "Add to PATH"
- Kiểm tra:
node -v
Error: listen EADDRINUSE: address already in use :::3000
Port đang bị chiếm. Thường là do backend và frontend cùng dùng port 3000.
# Tìm tiến trình đang chiếm port
netstat -ano | findstr :3000
# Tắt nó (thay <PID> bằng số ở cột cuối)
taskkill /PID <PID> /F
Cách sửa đúng: đổi backend sang port 5000 trong backend/bin/www (bài 04).
macOS: port 5000 bị AirPlay chiếm → tắt trong System Settings hoặc dùng 5001.
Cannot find module 'xxx'
Chưa cài thư viện:
npm install xxx
Nếu đã cài rồi mà vẫn lỗi:
rm -rf node_modules package-lock.json # Windows: Remove-Item -Recurse -Force
npm install
nodemon: command not found
npm install --save-dev nodemon
# hoặc chạy trực tiếp
npx nodemon ./bin/www
B. Lỗi MongoDB
MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
MongoDB chưa chạy. Đây là lỗi phổ biến nhất khi mới bắt đầu.
Get-Service MongoDB # phải là Running
Start-Service MongoDB # nếu Stopped
Nếu không có service nào tên MongoDB → bạn cài mà không tick "Install as a Service". Chạy thủ công:
& "C:\Program Files\MongoDB\Server\7.0\bin\mongod.exe" --dbpath "C:\data\db"
connect ECONNREFUSED ::1:27017 (chú ý ::1 chứ không phải 127.0.0.1)
Node phân giải localhost thành IPv6 nhưng MongoDB chỉ nghe IPv4.
Sửa trong backend/model/db.js:
const url = "mongodb://127.0.0.1:27017"; // dùng IP thay vì localhost
API trả về mảng rỗng [] dù database có dữ liệu
Kiểm tra theo thứ tự:
- Sai tên database —
dbNametrongdb.jsphải khớp chính xác - Sai tên collection —
db.collection("products")phân biệt hoa thường - Chưa import dữ liệu — chạy
db.products.countDocuments()trong Compass - Kiểu dữ liệu không khớp — xem lỗi tiếp theo
/products/byCategory/:id luôn trả về []
Lỗi kinh điển của dự án này. categoryId trong DB là chuỗi,
nhưng query tìm bằng ObjectId.
Kiểm tra:
db.products.countDocuments({ categoryId: { $type: "string" } })
// Ra > 0 → đây chính là vấn đề
Sửa dữ liệu:
db.products.find({ categoryId: { $type: "string" } }).forEach(function (doc) {
db.products.updateOne(
{ _id: doc._id },
{ $set: { categoryId: new ObjectId(doc.categoryId) } }
);
});
BSONError: input must be a 24 character hex string
Bạn truyền chuỗi không hợp lệ vào new ObjectId().
// Luôn kiểm tra trước
if (!ObjectId.isValid(req.params.id)) {
return res.status(400).json({ message: "ID không hợp lệ" });
}
E11000 duplicate key error collection: ... index: _id_ dup key: { _id: null }
Bạn đặt _id: null khi insert. Chỉ thêm được 1 document, cái thứ 2 sẽ lỗi.
Sửa: xóa dòng _id: null — MongoDB tự sinh _id.
Đây là lỗi trong POST /products của source gốc (bài 07).
updateOne không thay đổi gì
Quên toán tử $set:
updateOne(filter, { price: 100 }) // ❌ lỗi
updateOne(filter, { $set: { price: 100 } }) // ✅
C. Lỗi Express / Backend
Cannot GET /products
Router chưa được gắn vào app.js:
var productsRouter = require('./routes/products');
app.use('/products', productsRouter);
Và file router phải có module.exports = router; ở cuối.
req.body is undefined
Thiếu middleware parse body:
app.use(express.json()); // cho JSON
app.use(express.urlencoded({ extended: false })); // cho form
Phải đặt TRƯỚC các app.use(...router).
Nếu đang upload file → dùng Multer, không phải express.json().
Request bị treo, không có phản hồi
Ba nguyên nhân:
- Middleware quên gọi
next()— request dừng giữa chừng - Route
asyncném lỗi mà không cótry/catch— Express 4 không bắt được - MongoDB không kết nối được —
await connectDb()treo mãi
Cách phòng: bọc mọi route async trong try/catch + next(error).
Cannot set headers after they are sent to the client
Bạn gọi res.json() hai lần. Thường do quên return:
// ❌ Sai
if (!product) {
res.status(404).json({ message: "..." }); // gửi lần 1
}
res.status(200).json(product); // gửi lần 2 → LỖI
// ✅ Đúng
if (!product) {
return res.status(404).json({ message: "..." });
}
res.status(200).json(product);
Route không bao giờ chạy
Thứ tự route sai — route tổng quát "nuốt" route cụ thể:
// ❌
router.get("/:id", ...); // khớp cả "/topRating"
router.get("/topRating", ...); // không bao giờ chạy
// ✅ cụ thể trước, tổng quát sau
router.get("/topRating", ...);
router.get("/:id", ...);
D. Lỗi React / Next.js
Warning: Invalid DOM property 'class'. Did you mean 'className'?
JSX dùng className, không dùng class.
Find & Replace với regex:
- Find:
\sclass="→ Replace:className=" - Find:
for="→ Replace:htmlFor=" tabindex→tabIndex,colspan→colSpan,crossorigin→crossOrigin,enctype→encType
Nothing was returned from render
Component không return gì, hoặc return giá trị không phải JSX:
// ❌
export default function Product() { }
if (error) return (console.log('lỗi')); // console.log trả về undefined
// ✅
export default function Product() { return <div>...</div>; }
if (error) return <div>Đã có lỗi</div>;
Hai lỗi này đều có trong source gốc (bài 10, bài 12).
You're importing a component that needs useState. It only works in a Client Component
Thiếu "use client" ở dòng đầu tiên của file.
⚠️ Kiểm tra chính tả: "use clinet" (lỗi trong source gốc) không có tác dụng gì.
You are attempting to export "metadata" from a component marked with "use client"
export const metadata chỉ dùng được trong Server Component.
Cách sửa: tách phần cần client ra component riêng, giữ layout là server component (bài 17).
data.map is not a function
data không phải mảng. Debug:
console.log("data =", data);
Nguyên nhân thường gặp:
- API trả
{ message: "..." }thay vì mảng (lỗi 404 nhưng fetcher không bắt) NEXT_PUBLIC_API_URLlàundefined→ fetch tới URL sai- API đã đổi sang trả
{ data, pagination }(bài 21)
Phòng thủ:
const list = Array.isArray(data) ? data : [];
Warning: Each child in a list should have a unique "key" prop
Thiếu key khi .map():
{data.map((item) => <Product key={item._id} data={item} />)}
Encountered two children with the same key
Key bị trùng. Trong dự án này xảy ra ở giỏ hàng — cùng sản phẩm khác size:
key={product._id} // ❌ trùng
key={`${product._id}-${product.size}`} // ✅
Hydration failed because the initial UI does not match what was rendered on the server
HTML ở server khác HTML ở client. Nguyên nhân:
| Nguyên nhân | Cách sửa |
|---|---|
Đọc localStorage khi render |
Dùng PersistGate (bài 14) |
Date.now(), Math.random() trong JSX |
Đưa vào useEffect |
HTML không hợp lệ (<div> trong <p>, <Link> trong <ul>) |
Sửa cấu trúc HTML |
<html>/<body> lồng nhau |
Tách route group (bài 17) |
React has detected a change in the order of Hooks
Bạn gọi hook sau câu return hoặc bên trong if:
// ❌
if (isLoading) return <p>Loading</p>;
const [x, setX] = useState(0); // hook sau return
// ✅ mọi hook phải ở TRÊN CÙNG
const [x, setX] = useState(0);
if (isLoading) return <p>Loading</p>;
NextRouter was not mounted
Import sai. App Router dùng:
import { useRouter } from "next/navigation"; // ✅
import { useRouter } from "next/router"; // ❌ (Pages Router cũ)
useSearchParams() should be wrapped in a suspense boundary
import { Suspense } from "react";
export default function Page() {
return <Suspense fallback={<div>Loading...</div>}><Content /></Suspense>;
}
Lỗi này chỉ xuất hiện khi npm run build, không thấy khi npm run dev.
Modal / dropdown / carousel không hoạt động
Bootstrap JS chưa được nạp, hoặc đường dẫn sai:
<script src="js/bootstrap.bundle.min.js"></script> {/* ❌ đường dẫn tương đối */}
<script src="/js/bootstrap.bundle.min.js"></script> {/* ✅ tuyệt đối */}
Với đường dẫn tương đối, ở trang /cart trình duyệt tìm /cart/js/bootstrap... → 404.
Đây là lỗi trong source gốc (bài 10).
Kiểm tra: mở Console gõ bootstrap — phải ra object, không phải undefined.
Icon Font Awesome không hiển thị (chỉ thấy ô vuông)
- Kit CDN đã hết hạn hoặc bị giới hạn domain → dùng kit riêng hoặc CDN công khai
- Dùng icon Pro (
fa-light,fal,fa-duotone) với tài khoản miễn phí → đổi sangfa-solid/fas - File font trong
public/font/bị thiếu → kiểm tra tab Network, lọc "Font"
E. Lỗi Redux
could not find react-redux context value; please ensure the component is wrapped in a <Provider>
Component dùng useSelector/useDispatch nhưng nằm ngoài <Provider>.
<Providers>
<Navbar /> {/* ← phải nằm TRONG */}
{children}
</Providers>
Cannot read properties of undefined (reading 'reduce')
state.cart là undefined — tên key không khớp:
combineReducers({ cart: cartSlicer }) // định nghĩa
useSelector((state) => state.carts) // ❌ dùng sai tên
A non-serializable value was detected in an action
Cảnh báo do redux-persist. Sửa trong configureStore:
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
Dispatch action nhưng state không đổi
- Mở Redux DevTools — action có xuất hiện không?
- Có action, state không đổi → reducer sai (quên
state.push, sai điều kiệnfindIndex) - Không có action →
onClickchưa gắn, hoặc thiếu"use client"
Giỏ hàng mất khi F5
Chưa cấu hình redux-persist, hoặc quên <PersistGate> (bài 14).
F. Lỗi kết nối Frontend ↔ Backend
Access to fetch at '...' has been blocked by CORS policy
Backend chưa bật CORS, hoặc cors() đặt sau router:
var cors = require("cors");
app.use(cors()); // ← phải đặt TRƯỚC mọi app.use(...router)
Kiểm tra: DevTools → Network → request → Response Headers phải có
access-control-allow-origin.
Failed to fetch / ERR_CONNECTION_REFUSED
Backend chưa chạy. Mở terminal thứ hai:
cd backend && npm run dev
Kiểm tra bằng cách mở thẳng http://localhost:5000/products trong trình duyệt.
URL fetch là undefined/products
Biến môi trường chưa được nạp:
- File phải tên đúng
.env.local, đặt ởfrontend/(không phải thư mục gốc) - Biến phải có tiền tố
NEXT_PUBLIC_ - Phải khởi động lại
npm run devsau khi sửa.env.local
Ảnh không hiển thị
Chuột phải vào ảnh vỡ → "Open image in new tab", xem URL thực tế:
| URL thấy được | Nguyên nhân |
|---|---|
undefinedsp1.jpg |
NEXT_PUBLIC_IMA_URL chưa nạp → restart dev server |
localhost:5000/imgsp1.jpg |
Thiếu / cuối trong IMA_URL |
localhost:5000/img/sp1.jpg nhưng 404 |
File không có trong backend/public/img/ |
localhost:3000/img/sp1.jpg |
Ảnh sản phẩm nằm ở backend, không phải frontend |
G. Lỗi upload file
req.file is undefined
- Thiếu middleware Multer trên route:
router.post("/", upload.single("image"), handler); - Tên trường không khớp:
formData.append("image", file); // frontend upload.single("image") // backend — phải cùng tên - Đặt header
Content-Typethủ công — xem lỗi tiếp theo
req.body rỗng khi upload file
Bạn tự đặt Content-Type:
// ❌ SAI — thiếu boundary, Multer không parse được
fetch(url, {
method: "POST",
headers: { "Content-Type": "multipart/form-data" },
body: formData,
});
// ✅ ĐÚNG — để trình duyệt tự đặt
fetch(url, { method: "POST", body: formData });
MulterError: LIMIT_UNEXPECTED_FILE
Tên trường trong formData.append() khác với upload.single().
MulterError: LIMIT_FILE_SIZE
File vượt quá limits.fileSize. Tăng giới hạn hoặc nén ảnh trước khi upload.
Ảnh upload xong nhưng không hiển thị
- Kiểm tra file có thật trong
backend/public/img/ - Kiểm tra
imagelưu trong DB làreq.file.filename(tên thật trên đĩa), không phảireq.file.originalname - Kiểm tra
express.staticđã cấu hình trongapp.js
File lưu sai thư mục
destination: "./public/img" là đường dẫn tương đối với thư mục đang chạy lệnh.
Sửa thành:
const path = require("path");
destination: (req, file, cb) => cb(null, path.join(__dirname, "../public/img"))
H. Lỗi khi deploy
Build lỗi Module not found trên Vercel nhưng chạy được ở local
Phân biệt hoa thường. Windows không phân biệt, Linux có.
import Product from "./component/Product"; // file thật: product.js → lỗi trên Linux
Chạy npm run build ở local trước khi push để phát hiện sớm.
bad auth : Authentication failed (MongoDB Atlas)
- Sai mật khẩu trong connection string
- Ký tự đặc biệt chưa mã hóa URL:
@→%40,#→%23,/→%2F - User chưa có quyền đọc/ghi
Kết nối Atlas timeout
Network Access chưa cho phép 0.0.0.0/0.
Request đầu tiên mất 60 giây
Render gói Free đang "thức dậy" sau 15 phút ngủ. Dùng UptimeRobot ping /health.
Ảnh upload lên production rồi biến mất
Filesystem của Render/Heroku là ephemeral. Bắt buộc phải chuyển sang Cloudinary hoặc S3 (bài 22).
NEXT_PUBLIC_* là undefined trên production
Biến NEXT_PUBLIC_* được nhúng vào bundle lúc build. Thêm biến trên Vercel xong
phải Redeploy thì mới có tác dụng.
🔍 I. Phương pháp debug
Danh sách lỗi ở trên không bao giờ đủ. Đây mới là thứ giúp bạn tự giải quyết mọi lỗi.
Quy tắc 1 — Đọc thông báo lỗi. Thật sự đọc.
Đa số người mới nhìn thấy màu đỏ là hoảng và bỏ qua. Nhưng 80% thông báo lỗi đã nói thẳng vấn đề là gì.
TypeError: Cannot read properties of null (reading 'categoryId')
at D:\backend\routes\products.js:67:45
Đọc được: dòng 67, có gì đó là null, và bạn đang đọc .categoryId của nó.
Nhìn dòng 67 là ra ngay.
Quy tắc 2 — Xác định lỗi ở tầng nào
Trình duyệt (React) → HTTP → Express → MongoDB
Cách xác định nhanh:
| Kiểm tra | Nếu OK thì | Nếu lỗi thì |
|---|---|---|
Mở localhost:5000/products trong trình duyệt |
Backend + DB ổn → lỗi ở frontend | Lỗi ở backend hoặc DB |
| Chạy query trong Compass | DB ổn → lỗi ở backend | Lỗi ở dữ liệu/DB |
| DevTools → Network | Xem request có gửi đi không, trả về gì |
Quy tắc 3 — console.log là bạn thân
console.log("=== VÀO ROUTE ===");
console.log("req.params:", req.params);
console.log("req.body:", req.body);
console.log("req.file:", req.file);
const products = await productCollection.find(filter).toArray();
console.log("Tìm được:", products.length, "sản phẩm");
Đặt log trước và sau chỗ bạn nghi ngờ. Log nào không in ra → chương trình chưa chạy tới đó, hoặc nó ném lỗi ở giữa.
Ở frontend, thêm nhãn để dễ tìm:
console.log("🔵 Productlist props.data:", props.data?.length);
console.log("🟢 sortType:", sortType, "| min:", min, "| max:", max);
Quy tắc 4 — Thu hẹp phạm vi (nhị phân)
Không biết lỗi ở đâu trong 200 dòng? Cắt đôi:
- Comment nửa dưới → còn lỗi không?
- Còn → lỗi ở nửa trên. Không còn → lỗi ở nửa dưới
- Lặp lại
5–6 lần là khoanh vùng được dòng gây lỗi.
Quy tắc 5 — Bốn công cụ phải thành thạo
| Công cụ | Mở bằng | Dùng để |
|---|---|---|
| Console | F12 → Console | Đọc lỗi JS, chạy lệnh thử |
| Network | F12 → Network | Xem request gửi gì, nhận gì, status code |
| Redux DevTools | F12 → Redux | Xem action, state trước/sau |
| MongoDB Compass | App riêng | Xem dữ liệu thật trong DB |
Cách dùng tab Network hiệu quả:
- Lọc
Fetch/XHRđể chỉ thấy request API - Bấm vào một request:
- Headers — URL, method, status code
- Payload — dữ liệu bạn gửi lên
- Response — dữ liệu server trả về
- Chuột phải → Copy as cURL để chạy lại trong terminal hoặc Postman
Quy tắc 6 — Cô lập vấn đề
Tạo file test nhỏ nhất tái hiện được lỗi:
// test.js — chạy: node test.js
const connectDb = require("./model/db");
(async () => {
const db = await connectDb();
const p = await db.collection("products").findOne();
console.log("categoryId:", p.categoryId, "| type:", typeof p.categoryId);
process.exit(0);
})();
Bỏ hết Express, React, chỉ còn phần bạn nghi ngờ.
Quy tắc 7 — Kiểm tra giả định
Bug thường nằm ở chỗ bạn chắc chắn là đúng.
- "Chắc chắn
categoryIdlà ObjectId" → in ratypeofxem - "Chắc chắn API trả về mảng" →
console.log(Array.isArray(data)) - "Chắc chắn
.env.localđã được đọc" →console.log(process.env.NEXT_PUBLIC_API_URL)
Quy tắc 8 — Tìm kiếm đúng cách
❌ "web bán hàng lỗi"
✅ "MulterError LIMIT_UNEXPECTED_FILE nodejs"
✅ "next.js hydration failed redux persist"
Copy nguyên văn thông báo lỗi (bỏ phần đường dẫn riêng của máy bạn) và dán vào Google. Ưu tiên kết quả từ Stack Overflow và GitHub Issues.
Quy tắc 9 — Ghi lại lỗi đã gặp
Tạo file NOTES.md riêng:
## 16/08 — byCategory trả về rỗng
Nguyên nhân: categoryId trong DB là string, query dùng ObjectId
Cách sửa: script updateMany chuyển sang ObjectId
Bài học: luôn kiểm tra KIỂU dữ liệu, không chỉ giá trị
Sau 3 tháng bạn sẽ có cuốn sổ tay vô giá — và sẽ nhận ra mình gặp lại cùng một loại lỗi nhiều lần.
📋 J. Bảng tra lỗi riêng của dự án này
Tổng hợp toàn bộ vấn đề đã phát hiện trong source gốc:
| # | File | Vấn đề | Bài |
|---|---|---|---|
| 1 | backend/bin/www |
Port mặc định 3000, trùng Next.js | 04 |
| 2 | backend/app.js |
routes/index.js không được mount → code chết |
04 |
| 3 | backend/model/db.js |
Mỗi request tạo kết nối mới, không đóng | 05 |
| 4 | mọi route | Không có try/catch → request treo khi lỗi |
05 |
| 5 | routes/products.js |
products.categoryId đọc trước khi kiểm tra null |
06 |
| 6 | routes/products.js |
byCategory dùng ObjectId, DB lưu chuỗi → luôn rỗng |
06 |
| 7 | routes/products.js |
.map() dùng sai mục đích (nên là .forEach()) |
06 |
| 8 | routes/products.js |
_id: null → chỉ thêm được 1 sản phẩm |
07 |
| 9 | routes/products.js |
price lưu dạng chuỗi |
07 |
| 10 | routes/products.js |
req.file.originalname — trùng tên thì ghi đè |
07 |
| 11 | routes/products.js |
Không validate dữ liệu đầu vào | 07 |
| 12 | routes/oder.js |
3 route copy từ products, vô nghĩa và crash | 08 |
| 13 | routes/oder.js |
Nhận gì lưu nấy — khách sửa được giá | 08 |
| 14 | component/navbar.js |
Tên component viết thường | 09 |
| 15 | toàn bộ frontend | Dùng class thay className |
09 |
| 16 | component/addcardbuton.js |
Bỏ quên children, hardcode chữ nút |
09 |
| 17 | app/(user)/layout.js |
src="js/..." thiếu / → modal không chạy ở trang con |
10 |
| 18 | app/(user)/product/page.js |
Component rỗng, không return → crash | 10 |
| 19 | app/admin/layout.js |
"use clinet" sai chính tả |
10 |
| 20 | app/admin/layout.js |
<html>/<body> lồng nhau |
17 |
| 21 | app/(user)/page.js |
Lặp 9 khối Link, mọi link đều href="/" |
11 |
| 22 | repo | Thiếu .env.local mà code phụ thuộc |
12 |
| 23 | product/[id]/page.js |
return console.log(...) → trang trắng |
12 |
| 24 | product/[id]/page.js |
2 nút "thêm vào giỏ" trùng nhau | 12 |
| 25 | product/[id]/page.js |
/categpry/ + categoryID — sai chính tả và sai tên biến |
12 |
| 26 | slices/sortSlide.js |
name: 'cart' trùng với cartSlicer |
13 |
| 27 | slices/cartSlicer.js |
Vừa sửa state vừa return (sai quy tắc Immer) | 13 |
| 28 | slices/cartSlicer.js |
splice(-1, 1) xóa nhầm phần tử cuối |
13 |
| 29 | slices/cartSlicer.js |
decreaseItem là hàm rỗng |
13 |
| 30 | redux/store.js |
Thiếu serializableCheck, persist cả sort/filter |
14 |
| 31 | component/productlist.js |
useRef giữ dữ liệu cũ → sort "Mặc định" sai |
15 |
| 32 | component/productlist.js |
.sort() biến đổi mảng gốc |
15 |
| 33 | component/productlist.js |
Nhánh mặc định vứt bỏ kết quả filter |
15 |
| 34 | component/productlist.js |
row-cols-ms-2 — class không tồn tại |
15 |
| 35 | menu/layout.js |
max={100000} nhỏ hơn giá sản phẩm rẻ nhất |
15 |
| 36 | cart/page.js |
Công thức size không nhất quán (m cố định, l theo SL) | 16 |
| 37 | cart/page.js |
Tổng tiền không tính phụ phí size | 16 |
| 38 | cart/page.js |
Báo "thành công" kể cả khi API lỗi → mất giỏ hàng | 16 |
| 39 | cart/page.js |
key={product._id} trùng khi cùng SP khác size |
16 |
| 40 | admin/page.js |
Toàn số giả, nội dung mẫu về iPhone/Samsung | 17 |
| 41 | admin/component/recentOrder.js |
toLOcaString() sai chính tả |
17 |
| 42 | admin/product/page.js |
product.category.name crash nếu category null |
17 |
| 43 | admin/product/page.js |
<> bọc <tr> trong map → key sai chỗ |
17 |
| 44 | admin/product/add/page.js |
.then() rỗng, không xử lý kết quả |
18 |
| 45 | admin/product/add/page.js |
try/catch không bắt được lỗi Promise |
18 |
| 46 | admin/product/add/page.js |
Input thiếu value → không reset được |
18 |
| 47 | admin/product/add/page.js |
<option selected> không có value |
18 |
| 48 | login, register |
Form tĩnh, chưa nối API | 19 |
| 49 | component/navbar.js |
Ô tìm kiếm không hoạt động | 21 |
| 50 | routes/index.js |
Regex tìm kiếm không escape → nguy cơ ReDoS | 21 |
🎓 Lời kết
Bạn đã đi hết 24 bài học. Nhìn lại, bạn đã:
- Xây một REST API hoàn chỉnh với Express + MongoDB
- Dựng giao diện thương mại điện tử với Next.js App Router
- Quản lý state phức tạp với Redux Toolkit
- Xử lý upload file, xác thực JWT, phân quyền
- Tìm ra và sửa 50 lỗi thật trong một dự án thật
- Đưa sản phẩm lên internet
Điều quan trọng nhất bạn học được không phải cú pháp — cú pháp tra được. Đó là:
- Tư duy tách tầng — biết lỗi nằm ở frontend, backend hay database
- Không tin dữ liệu từ client — backend luôn phải kiểm tra lại
- Dữ liệu dẫn xuất thì tính lại, đừng lưu — nguồn gốc của rất nhiều bug
- Đọc thông báo lỗi — nó thường đã nói thẳng vấn đề
- Nhìn ra code chưa chuẩn — kể cả code đang "chạy được"
Nên học gì tiếp?
| Chủ đề | Vì sao |
|---|---|
| TypeScript | Bắt lỗi kiểu dữ liệu ngay khi gõ. 50 lỗi ở trên, TypeScript chặn được ~15 |
| Kiểm thử (Jest, Playwright) | Sửa code mà không sợ hỏng chỗ khác |
| Mongoose | Schema + validation cho MongoDB |
| Docker | "Chạy được trên máy tôi" không còn là vấn đề |
| React Query | Mạnh hơn SWR cho ứng dụng lớn |
| Tailwind CSS | Thay thế Bootstrap, linh hoạt hơn nhiều |
Ý tưởng nâng cấp dự án
- Thanh toán online (VNPay, MoMo)
- Đánh giá & bình luận sản phẩm
- Gửi email xác nhận đơn hàng (Nodemailer)
- Chat hỗ trợ realtime (Socket.io)
- Gợi ý sản phẩm theo lịch sử xem
- Ứng dụng di động (React Native dùng chung API)
Chúc bạn code vui và ít bug! 🌸
All rights reserved