Bài 30.1: CSS Modules và Sass trong Next.js

Trong hành trình xây dựng các ứng dụng web hiện đại với Next.js, việc quản lý CSS hiệu quả là một thử thách lớn, đặc biệt khi dự án ngày càng phức tạp và có nhiều component. Việc sử dụng CSS truyền thống với phạm vi toàn cục (global scope) rất dễ dẫn đến các vấn đề xung đột tên class, khó bảo trì và tái sử dụng.

May mắn thay, Next.js cung cấp sẵn hoặc hỗ trợ tích hợp các giải pháp mạnh mẽ để giải quyết vấn đề này: CSS ModulesSass (hoặc SCSS). Kết hợp chúng lại, bạn sẽ có một quy trình phát triển CSS có tổ chức, có khả năng mở rộngít đau đầu hơn rất nhiều.

Hãy cùng đi sâu vào cách sử dụng và tận dụng tối đa hai công cụ này trong dự án Next.js của bạn!

1. CSS Modules: Giải Pháp Chống Xung Đột Tên Class

Vấn đề lớn nhất với CSS truyền thống là mọi style đều có phạm vi toàn cục. Nếu bạn định nghĩa một class .button trong file CSS này, nó sẽ ảnh hưởng đến tất cả các phần tử có class .buttonmọi nơi trong ứng dụng của bạn. Điều này rất dễ gây ra xung đột khi nhiều developer cùng làm việc hoặc khi bạn tích hợp code từ các nguồn khác.

CSS Modules ra đời để giải quyết triệt để vấn đề này. Ý tưởng cốt lõi là phạm vi hóa (scope) các tên class CSS theo từng component. Khi sử dụng CSS Modules, mỗi tên class bạn định nghĩa sẽ được biến đổi thành một tên duy nhất, chỉ áp dụng cho component mà nó được import vào.

Trong Next.js, việc sử dụng CSS Modules vô cùng đơn giản vì nó đã được hỗ trợ sẵn có (built-in). Bạn không cần cài đặt hay cấu hình gì thêm!

Cách sử dụng CSS Modules trong Next.js:

Bạn chỉ cần đặt tên file CSS của mình theo quy ước: [tên-file].module.css.

Ví dụ: Nếu bạn có một component Button.js, file CSS tương ứng sẽ là Button.module.css.

Ví dụ minh họa:

Giả sử bạn có một file Button.module.css như sau:

/* components/Button.module.css */
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.primary {
  background-color: #007bff;
  color: white;
}

.secondary {
  background-color: #6c757d;
  color: white;
}

Và bạn muốn sử dụng nó trong component Button.js:

// components/Button.js
import styles from './Button.module.css';

function Button({ children, type = 'primary' }) {
  const buttonClass = type === 'primary' ? styles.primary : styles.secondary;

  return (
    <button className={`${styles.button} ${buttonClass}`}>
      {children}
    </button>
  );
}

export default Button;

Giải thích code:

  • Khi bạn import styles from './Button.module.css', bạn không nhận được chuỗi CSS thô. Thay vào đó, bạn nhận được một object (styles).
  • Các thuộc tính của object styles chính là các tên class bạn đã định nghĩa trong file CSS (button, primary, secondary).
  • Giá trị của mỗi thuộc tính là tên class đã được tạo scoped bởi CSS Modules. Ví dụ, styles.button có thể trở thành chuỗi như Button_button__abc123.
  • Bằng cách sử dụng className={styles.button}className={${styles.button} ${buttonClass}}, bạn đảm bảo rằng các style này *chỉ áp dụng* cho componentButtonnày và sẽ không vô tình ảnh hưởng đến các phần tử.buttonhoặc.primary` ở nơi khác trong ứng dụng.

Lợi ích của CSS Modules:

  • Không xung đột tên class: Đây là lợi ích lớn nhất. Mỗi component có "không gian tên" CSS riêng.
  • Độ rõ ràng cao: Dễ dàng biết class CSS nào thuộc về component nào.
  • Dễ bảo trì: Khi xóa hoặc sửa đổi component, bạn biết chắc chắn rằng style trong file .module.css của nó sẽ không ảnh hưởng đến phần còn lại của ứng dụng.
  • Code Splitting: Next.js (và Webpack) sẽ tự động xử lý code splitting cho CSS Modules, chỉ tải CSS khi component tương ứng được tải.

Tuyệt vời, phải không? CSS Modules đã giải quyết vấn đề phạm vi. Nhưng CSS truyền thống vẫn còn một số hạn chế khác. Đó là lúc Sass xuất hiện.

2. Sass (SCSS): Viết CSS Mạnh Mẽ Hơn

Sass (Syntactically Awesome Stylesheets) là một bộ tiền xử lý CSS (CSS preprocessor). Nó mở rộng cú pháp của CSS bằng cách thêm vào các tính năng mà CSS thuần không có, giúp bạn viết CSS nhanh hơn, có cấu trúc hơn, và dễ bảo trì hơn.

Các tính năng chính của Sass bao gồm:

  • Biến (Variables): Lưu trữ các giá trị (màu sắc, font size, padding...) vào biến để tái sử dụng và dễ dàng cập nhật.
  • Lồng nhau (Nesting): Lồng các bộ chọn (selectors) vào nhau để tạo cấu trúc trực quan, phản ánh cấu trúc HTML.
  • Mixins: Tạo các khối CSS có thể tái sử dụng, hữu ích cho các nhóm thuộc tính thường xuyên lặp lại (ví dụ: tạo flexbox centered, responsive breakpoints).
  • Hàm (Functions): Thực hiện các phép tính trên các giá trị CSS (ví dụ: làm sáng/tối màu).
  • Import/Use: Chia nhỏ code CSS thành nhiều file nhỏ hơn và import chúng lại ở một nơi, giúp code gọn gàng và dễ quản lý.

Next.js cũng hỗ trợ Sass một cách tự nhiên, nhưng bạn cần cài đặt thêm gói sass.

Cách tích hợp Sass vào Next.js:

  1. Cài đặt gói sass:
    npm install sass
    # hoặc
    yarn add sass
    
  2. Sử dụng file với đuôi .scss hoặc .sass thay vì .css. Cú pháp .scss phổ biến hơn vì nó gần giống CSS thuần hơn.

Ví dụ sử dụng các tính năng của Sass trong Next.js:

Giả sử bạn có một file variables.scss và một file styles.scss:

/* styles/_variables.scss */
$primary-color: #007bff;
$secondary-color: #6c757d;
$font-stack: 'Arial', sans-serif;
$padding-base: 15px;
/* styles/styles.scss */
@import 'variables'; // Import biến từ file khác

body {
  font-family: $font-stack;
  margin: 0;
  padding: 0;
}

.container {
  width: 90%;
  margin: 0 auto;
  padding: $padding-base;

  h1 { // Lồng nhau
    color: $primary-color;
    margin-bottom: $padding-base / 2; // Sử dụng phép tính
  }

  button {
    background-color: $secondary-color;
    color: white;
    padding: $padding-base / 1.5 $padding-base;
    border: none;
    border-radius: 4px;
    cursor: pointer;

    &:hover { // Sử dụng & để tham chiếu đến bộ chọn cha (.container button)
      background-color: darken($secondary-color, 10%); // Sử dụng hàm
    }
  }
}

Để sử dụng các style này trong Next.js, bạn có thể import file .scss vào file Layout hoặc file component gốc (_app.js hoặc layout.js trong App Router) nếu muốn nó có phạm vi toàn cục:

// app/layout.js (App Router) hoặc pages/_app.js (Pages Router)
import '../styles/styles.scss'; // Import file Sass toàn cục

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
      </body>
    </html>
  );
}

Giải thích code Sass:

  • @import 'variables';: Lệnh này cho phép bạn nhập nội dung từ file _variables.scss vào đây. Dấu gạch dưới ở đầu tên file _variables.scss là quy ước trong Sass, báo hiệu đây là file partial (một phần) và sẽ không được biên dịch thành file CSS riêng.
  • $primary-color: #007bff;: Khai báo một biến. Bạn có thể sử dụng biến này ở nhiều nơi.
  • h1 { ... }: Bộ chọn h1 được lồng bên trong .container. Khi biên dịch ra CSS thuần, nó sẽ trở thành .container h1.
  • $padding-base / 2;: Sass cho phép thực hiện các phép toán cơ bản với các giá trị số.
  • &:hover: Ký hiệu & tham chiếu đến bộ chọn cha ngay lập tức (trong trường hợp này là button). :hover được thêm vào sau button. Khi biên dịch, nó trở thành .container button:hover.
  • darken($secondary-color, 10%);: Sử dụng hàm darken để làm tối giá trị màu của biến $secondary-color đi 10%.

Lợi ích của Sass:

  • Code DRY (Don't Repeat Yourself): Sử dụng biến và mixins giúp giảm thiểu việc lặp lại code.
  • Cấu trúc rõ ràng: Lồng nhau giúp code CSS có cấu trúc tương tự như HTML.
  • Dễ bảo trì và mở rộng: Thay đổi giá trị biến ở một nơi sẽ cập nhật ở khắp mọi nơi. Chia nhỏ file giúp quản lý dự án lớn dễ dàng hơn.
  • Các tính năng nâng cao: Hàm, phép toán, điều khiển luồng (if/else), vòng lặp... (mặc dù ít dùng trong CSS thông thường).

Sass giúp bạn viết CSS mạnh mẽ hơn, nhưng bản thân nó không giải quyết vấn đề phạm vi toàn cục.

3. Kết Hợp CSS Modules và Sass trong Next.js

Phần tuyệt vời là bạn không cần phải chọn một trong hai! Bạn hoàn toàn có thể kết hợp cả CSS ModulesSass lại với nhau trong Next.js. Điều này mang lại lợi ích kép: bạn có thể sử dụng các tính năng mạnh mẽ của Sass đảm bảo rằng các style của bạn được tạo phạm vi theo từng component.

Cách kết hợp rất đơn giản: bạn chỉ cần đặt tên file theo quy ước của CSS Modules nhưng với đuôi file của Sass: [tên-file].module.scss hoặc [tên-file].module.sass.

Ví dụ minh họa kết hợp:

Tạo một file Card.module.scss:

/* components/Card.module.scss */
$card-bg: #f8f9fa;
$card-border-color: #dee2e6;
$card-padding: 20px;
$border-radius: 8px;
$box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

.card { // Tên class này sẽ được tạo scope bởi CSS Modules
  background-color: $card-bg; // Sử dụng biến Sass
  border: 1px solid $card-border-color; // Sử dụng biến Sass
  border-radius: $border-radius; // Sử dụng biến Sass
  padding: $card-padding; // Sử dụng biến Sass
  box-shadow: $box-shadow; // Sử dụng biến Sass
  margin-bottom: 20px;

  h3 { // Lồng nhau
    color: #343a40;
    margin-top: 0;
    margin-bottom: 10px;
  }

  p { // Lồng nhau
    color: #6c757d;
    line-height: 1.5;
  }
}

.highlight { // Tên class này cũng sẽ được tạo scope
  border-color: #007bff;
  box-shadow: 0 2px 8px rgba(0, 123, 255, 0.2);
}

Và sử dụng nó trong component Card.js:

// components/Card.js
import styles from './Card.module.scss'; // Import file .module.scss

function Card({ title, content, highlight = false }) {
  const cardClasses = `${styles.card} ${highlight ? styles.highlight : ''}`;

  return (
    <div className={cardClasses}>
      <h3>{title}</h3>
      <p>{content}</p>
    </div>
  );
}

export default Card;

Giải thích code:

  • File Card.module.scss sử dụng cả biến Sass ($card-bg, $card-padding, v.v.) và kỹ thuật lồng nhau (h3, p bên trong .card).
  • Khi import styles from './Card.module.scss';, bạn nhận được một object tương tự như khi dùng .module.css.
  • Các tên class .card.highlight trong file Sass sẽ được CSS Modules xử lý để tạo ra các tên class duy nhất, chỉ áp dụng trong component Card.
  • Bạn sử dụng className={styles.card}className={cardClasses} để áp dụng các style đã được tạo scope.

Bằng cách này, bạn vừa tận dụng được:

  • CSS Modules: Đảm bảo .card.highlight chỉ có hiệu lực trong component Card, tránh xung đột.
  • Sass: Sử dụng biến, lồng nhau để viết code CSS hiệu quả, dễ đọc và dễ bảo trì hơn ngay trong file style của component đó.

Đây là phương pháp được khuyến khích cho hầu hết các style cấp component trong dự án Next.js, mang lại sự cân bằng tối ưu giữa khả năng tổ chức, tái sử dụng và tránh xung đột.

Tóm lại

Việc tích hợp và sử dụng CSS ModulesSass (hoặc SCSS) trong Next.js là một bước tiến lớn trong việc quản lý styling cho ứng dụng của bạn.

  • Sử dụng file .module.css hoặc .module.scss để có CSS Modules, giúp tạo phạm vi cho style theo từng component và loại bỏ xung đột tên class.
  • Sử dụng file .scss hoặc .sass để có Sass, giúp bạn viết CSS mạnh mẽ hơn với biến, lồng nhau, mixins, v.v. Bạn có thể sử dụng file .scss toàn cục (import trong _app.js/layout.js) cho các style chung như biến, typography, reset CSS, hoặc sử dụng nó kết hợp với CSS Modules trong file .module.scss cho style của từng component.

Kết hợp hai công cụ này, bạn sẽ có được quy trình phát triển CSS có tổ chức, dễ bảo trìcó khả năng mở rộng cho các dự án Next.js của mình. Hãy bắt tay vào áp dụng ngay nhé!

Comments

There are no comments at the moment.