Làm Chủ Next.js & Node.js (Phần 2): Fetch Dữ Liệu và Xử Lý Tìm Kiếm Sản Phẩm
Ở phần trước, chúng ta đã kết nối thành công Database. Hôm nay, chúng ta sẽ đưa những dữ liệu đó hiển thị lên màn hình người dùng một cách chuyên nghiệp nhất bằng cách kết hợp sức mạnh của Server Components trong Next.js.
Bài 1: Fetch Dữ Liệu Phía Server (Server-side Fetching)
Một trong những ưu điểm lớn nhất của Next.js là khả năng fetch dữ liệu ngay trên Server. Điều này giúp tăng tốc độ tải trang và cực kỳ tốt cho SEO.
Tại file page.jsx của trang chủ, chúng ta chỉ cần dùng async/await trực tiếp:
export default async function Home() {
const res = await fetch("http://localhost:3000/products");
const data = await res.json();
return (
<div className="container mt-4">
<h5 className="text-success">DANH SÁCH SẢN PHẨM</h5>
{/* Dữ liệu đã sẵn sàng trong biến data */}
</div>
);
};
Bài 2: Xây Dựng Component Tái Sử Dụng (ProductCard)
Để code sạch sẽ và dễ bảo trì, chúng ta sẽ tách phần hiển thị từng sản phẩm thành một Component riêng biệt.
Tạo file component/ProductCard.jsx:
Chúng ta sử dụng props để nhận dữ liệu và dùng hàm .map() để render danh sách sản phẩm theo định dạng của Bootstrap Card.
import React from 'react';
import Link from 'next/link';
function ProductCard({ data }) {
return (
<>
{data.map((product) => (
<div className="col-sm-6 col-md-4 col-lg-3 my-3" key={product._id}>
<div className="card h-100 p-2 shadow-sm">
<img
src={`http://localhost:3000/img/${product.image}`}
className="card-img-top"
alt={product.name}
style={{ objectFit: 'contain', height: '200px' }}
/>
<div className="card-body text-center">
<h6 className="card-title text-dark fw-bold">{product.name}</h6>
<p className="text-danger mb-2">
{product.price.toLocaleString('vi-VN', { style: 'currency', currency: 'VND' })}
</p>
<Link href={`/chitietsanpham/${product._id}`} className="btn btn-outline-warning w-100">
Xem chi tiết
</Link>
</div>
</div>
</div>
))}
</>
);
}
export default ProductCard;
Bài 3: Hiện Thực Hóa Chức Năng Tìm Kiếm (Search)
Chức năng tìm kiếm yêu cầu sự phối hợp nhịp nhàng giữa Backend (lọc dữ liệu) và Frontend (nhận từ khóa).
1. Phía Backend (Node.js) Sử dụng Regular Expression với flag 'i' (không phân biệt hoa thường) để tìm kiếm sản phẩm theo tên:
router.get('/search/:keyword', async(req, res) => {
const db = await connectDb();
const products = await db.collection('products')
.find({ name: new RegExp(req.params.keyword, 'i') })
.toArray();
res.status(200).json(products);
});
2. Phía Frontend (Next.js)
Tạo thư mục /app/timkiem/page.jsx. Next.js sẽ tự động lấy các tham số trên URL thông qua searchParams.
import ProductCard from '../component/ProductCard';
export default async function SearchPage({ searchParams }) {
const keyword = searchParams.keyword;
const res = await fetch(`http://localhost:3000/search/${keyword}`);
const productSearch = await res.json();
return (
<div className="container mt-4">
<h3 className="mb-4">Kết quả tìm kiếm cho: "<span className="text-success">{keyword}</span>"</h3>
<div className="row">
{productSearch.length > 0 ? (
<ProductCard data={productSearch} />
) : (
<p className="text-muted">Không tìm thấy sản phẩm nào phù hợp.</p>
)}
</div>
</div>
);
}
Bài 4: Nâng Cấp Giao Diện (Chia Nhóm Sản Phẩm)
Để đạt điểm tối đa (8-10đ), bạn không nên đổ tất cả sản phẩm vào một chỗ. Hãy phân loại chúng:
Sản phẩm Hot: Những sản phẩm có thuộc tính hot: true.
Sản phẩm theo danh mục: Lọc theo categoryId.
Mẹo nhỏ: Hãy sử dụng hàm .filter() của Javascript sau khi fetch dữ liệu để chia nhóm nhanh chóng mà không cần gọi API nhiều lần.
Tổng kết Lab 2
Đến đây, dự án của bạn đã bắt đầu "thở" được với dữ liệu thực. Việc tách Component và hiểu cơ chế searchParams là bước ngoặt quan trọng để làm chủ các dự án lớn hơn.
Hẹn gặp lại các bạn ở Lab 3 với các tính năng giỏ hàng và thanh toán!
All rights reserved