Ba giải pháp khác nhau ở thời điểm sinh ra CSS — đó là tiêu chí quyết định, phần còn lại là khẩu vị.
CSS Modules — build time. Bundler băm tên class thành duy nhất, kết quả là file .css tĩnh.
- Không runtime, tương thích tốt với Server Component và streaming SSR.
- Vẫn là CSS thuần: DevTools dễ đọc, người mới không phải học API mới.
- Style động phải qua CSS variable hoặc đổi class, không nhận thẳng prop.
CSS-in-JS runtime (styled-components, emotion) — sinh CSS lúc chạy.
- Style phụ thuộc prop viết rất tự nhiên, colocation tốt, xoá component là xoá style.
- Đổi lại: thêm JS vào bundle, chi phí serialize/inject mỗi lần render, và ở React Server Component thì không dùng được trong server component. Nhiều team đã chuyển sang zero-runtime (vanilla-extract, Panda, Linaria) để giữ DX mà trả về CSS tĩnh.
Tailwind — build time, quét source rồi sinh đúng các utility đã dùng.
- CSS đầu ra gần như không tăng theo số lượng component; không phải đặt tên.
- Ràng buộc design token ngay trong cấu hình nên nhóm khó lệch khỏi hệ thống.
- Đổi lại: markup dài, style rất động vẫn cần CSS variable, và có chi phí học cú pháp.
Cách chốt: Next.js App Router hoặc quan tâm hiệu năng runtime → ưu tiên phương án build time (Tailwind hoặc CSS Modules, tuỳ team thích utility hay class có tên). Đội đã quen styled-components trên app client-only và tốc độ phát triển quan trọng hơn vài KB JS → giữ CSS-in-JS là lựa chọn hợp lý. Điều tệ nhất là dùng cả ba cùng lúc trong một codebase.