Bài 3. Server Component và Client Component

Điểm khác biệt lớn nhất của Next.js hiện đại: component mặc định chạy trên máy chủ (Server Component), chỉ những phần cần tương tác mới chạy trên trình duyệt (Client Component). Hiểu đúng ranh giới này giúp ứng dụng nhanh, an toàn và gọn nhẹ.

50 phútTrung cấp
Bạn sẽ học được
  • Phân biệt Server và Client Component
  • Biết khi nào dùng "use client"
  • Truyền dữ liệu từ server xuống client
  • Tránh các lỗi thường gặp

Một số khái niệm

Server Component (mặc định)Client Component ("use client")
Chạy ở đâuMáy chủ (khi render)Trình duyệt (và render sẵn lần đầu)
Truy cập CSDL, tệp, khóa bí mậtĐượcKhông
useState, useEffect, onClickKhôngĐược
API trình duyệt (window, localStorage)KhôngĐược
Kích thước JS gửi xuốngKhông tăngTăng

Server Component lấy dữ liệu

// src/app/sinhvien/page.tsx  (Server Component)
import NutThich from './NutThich';

async function laySinhVien() {
  const res = await fetch('http://localhost:4000/api/sinhvien');  // API Node.js loạt trước
  return res.json();
}

export default async function TrangSinhVien() {
  const { du_lieu } = await laySinhVien();
  return (
    <ul className="space-y-2">
      {du_lieu.map((sv: { id: number; ho_ten: string }) => (
        <li key={sv.id} className="flex justify-between rounded bg-white p-3 shadow-sm">
          {sv.ho_ten} <NutThich />
        </li>
      ))}
    </ul>
  );
}

Client Component cho tương tác

// src/app/sinhvien/NutThich.tsx
'use client';
import { useState } from 'react';

export default function NutThich() {
  const [thich, setThich] = useState(false);
  return (
    <button onClick={() => setThich(!thich)} className="text-xl">
      {thich ? '❤️' : '🤍'}
    </button>
  );
}

Quy tắc: đặt 'use client' ở lá của cây component (nút, form, bộ lọc), giữ trang và layout là Server Component.

Lỗi thường gặp

  • Dùng useState trong component không có "use client" → lỗi biên dịch.
  • Truyền hàm (function) từ Server sang Client qua props → không được (chỉ truyền dữ liệu tuần tự hóa được, hoặc Server Action).
  • Import mã có khóa bí mật vào Client Component → lộ khóa. Dùng gói server-only để chặn.
  • Biến môi trường dùng ở client phải có tiền tố NEXT_PUBLIC_.
Mẹo hay

Client Component vẫn có thể chứa Server Component nếu truyền qua children: <KhungClient><DanhSachServer /></KhungClient>.

Lưu ý

Đừng thêm "use client" vào layout gốc chỉ vì một nút bấm: toàn bộ cây bên dưới sẽ thành client, mất lợi ích của Server Component.

Thực hành
  1. Hiển thị danh sách sinh viên bằng Server Component.
  2. Tạo ô tìm kiếm (Client) lọc danh sách.
  3. Tạo nút chuyển chế độ tối lưu vào localStorage.
  4. Thử import fs vào Client Component và đọc thông báo lỗi.
Tóm tắt bài học
  • Mặc định là Server Component.
  • "use client" cho state, sự kiện, API trình duyệt.
  • Đặt "use client" ở lá, truyền dữ liệu qua props.
  • Bảo vệ mã bí mật bằng server-only.