Привет, Хабр!
Сегодня пробежимся по теме, которую не назовёшь новенькой, но без неё — ни шагу в CSS-вёрстке в Next.js. Модульные CSS-архитектуры — это необходимый инструмент для тех, кто хочет создать прочную основу для масштабируемого проекта.
В статье разберём, как с помощью BEM, SMACSS и OOCSS можно держать CSS в порядке.
Почему CSS-архитектура нужна в Next.js?
Next.js — штука прекрасная, но фича его компонентного подхода может быстро стать вашим злейшим врагом, если не подойти к CSS грамотно. Один из ключей к надежному проекту — это модульность и предсказуемость CSS. Хочется не думать о том, «где оно там стилизуется», а спокойно пользоваться компонентами.
CSS-модули в Next.js
Одна из фич Next.js — встроенная поддержка CSS Modules. Создайте CSS-файл с суффиксом .module.css
, и стили автоматически локализуются для текущего компонента, предотвращая пересечение стилей между модулями. Но как это работает под капотом? CSS Modules создаёт уникальные имена классов при компиляции, так что классы, вроде .button
, превращаются во что-то вроде .Button_module__button__3VgY1
. Это избавляет от проблем коллизий, даже если в разных модулях есть одноимённые классы.
// components/Button.js
import styles from './Button.module.css';
export default function Button({ children }) {
return <button className={styles.button}>{children}</button>;
}
// components/Button.module.css
.button {
background-color: #0070f3;
color: white;
padding: 0.5em 1em;
border: none;
border-radius: 5px;
cursor: pointer;
}
Здесь Button.module.css
содержит стили, которые изолированы только для компонента Button
. Это сразу решает многие проблемы, но порядок в коде и структуре CSS всё равно важен. И вот тут на помощь приходят методологии, такие как BEM, SMACSS и OOCSS. Начнём с BEM.
BEM: Системный минимализм
BEM — это структурная спасалка для ваших стилей. Понятный принцип, который структурирует CSS по блокам, элементам и модификаторам. BEM-интерфейсы к Next.js модулям легко «ложатся» на CSS-модули.
Расшифровка BEM:
Block: Независимый, логически завершённый компонент, например,
card
,header
.Element: Составная часть блока, которая не может существовать отдельно от него, например,
card__title
,header__logo
.Modifier: Определяет варианты блока или элемента, например,
button--primary
,card__title--large
.
// components/Card.js
import styles from './Card.module.css';
export default function Card({ title, content, variant }) {
return (
<div>
<h1>{title}</h1>
<p>{content}</p>
</div>
);
}
// components/Card.module.css
.card {
padding: 1em;
border-radius: 5px;
background-color: #f3f3f3;
}
.card--primary {
background-color: #0070f3;
color: white;
}
.card__title {
font-size: 1.5em;
margin: 0;
}
.card__content {
font-size: 1em;
}
Классы, построенные по BEM-принципам, сами собой подсказывают, что из них есть что. Например, когда вы видите класс .card__title
, сразу становится понятно, что это заголовок конкретной карточки, и он не затронет другие компоненты.
Используя BEM вместе с CSS Modules, можно создавать уникальные классы, не опасаясь, что их имена будут конфликтовать где-то ещё в проекте. Динамическое добавление классов для модификаторов тоже легко реализуется:
// components/Card.js
import styles from './Card.module.css';
export default function Card({ title, content, variant }) {
return (
<div className={`${styles.card} ${variant === 'primary' ? styles['card--primary'] : ''}`}>
<h1 className={styles.card__title}>{title}</h1>
<p className={styles.card__content}>{content}</p>
</div>
);
}
Обратите внимание на строку variant === 'primary' ? styles['card--primary'] : ''
: она позволяет добавлять модификатор card--primary
, только если компоненту передан вариант primary
.
SMACSS: для тех, кто любит порядок
SMACSS — это гибкий подход, который делит CSS на категории:
Base — базовые стили. Здесь лежат правила для элементов
html
,body
, а также любые обнуления стилей и префиксы. Эти стили — основа дизайна, и они будут использоваться на всех страницах.Layout — основная структурная разметка. Это классы, которые определяют общие блоки и контейнеры: шапка сайта, подвал, главные обёртки. Эти стили формируют скелет страницы, и именно к ним Next.js привязывает рендеринг.
Module — это компоненты. Все кнопки, карточки, формы и любые другие самостоятельные элементы интерфейса оформляются здесь. Каждая деталь интерфейса получает свой собственный файл, особенно если компонент используется многократно.
State — изменения состояний. SMACSS выделяет эту категорию, чтобы было легче контролировать активные состояния. Пример: класс
.is-active
для выделенной вкладки или.is-hidden
для скрытых элементов. Кстати, не забывайте, что такие классы чаще всего обновляются динамически.Theme: Темы, если проект поддерживает несколько вариантов оформления.
Эти категории помогают поддерживать ясную организацию кода и снижают вероятность пересечения стилей. Для больших проектов — прямо находка, т.к SMACSS легко адаптируется под новые компоненты и страницы.
Когда есть чёткие правила для каждого стиля, поддерживать CSS становится гораздо легче. Например, не нужно вспоминать, где у прописаны стили для кнопок, — сразу ясно, что это будет в styles/modules/Button.module.css
.
// components/Button.js
import buttonStyles from '../styles/modules/Button.module.css';
export default function Button({ children, onClick, isActive }) {
return (
<button
className={`${buttonStyles.button} ${isActive ? buttonStyles['button--active'] : ''}`}
onClick={onClick}
>
{children}
</button>
);
}
// styles/modules/Button.module.css
.button {
padding: 0.5em 1em;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #0070f3;
color: white;
}
.button--active {
background-color: #005bb5;
}
Здесь Button
получает доп. классы в зависимости от состояния, причём всё логично и предсказуемо. Если isActive
равен true
, то кнопка получает стиль .button--active
. Эта логика легко масштабируется на новые состояния или варианты компонента.
Для небольших проектов SMACSS может показаться избыточным — в конце концов, чем меньше CSS, тем меньше проблем. Но по мере роста проекта ценность SMACSS проявляется всё больше.
OOCSS
OOCSS про то, чтобы создавать стили, которые можно тасовать между компонентами, как карточную колоду. Основная идея здесь — разделяй и властвуй, а именно: разделяй стили на структуру и оформление, чтобы переиспользовать один и тот же шаблон, изменяя только визуальные детали.
Допустим, есть базовая карточка (без изображения) и карточка с изображением, где стиль .card--with-image
просто меняет расположение элементов внутри карточки. Это делает стили гибкими, так что можно добавить новое оформление без изменения самой структуры.
// components/Card.js
import styles from './Card.module.css';
export default function Card({ title, content, imgSrc }) {
return (
<div className={`${styles.card} ${imgSrc ? styles['card--with-image'] : ''}`}>
{imgSrc && <img src={imgSrc} alt="" className={styles.card__image} />}
<h1 className={styles.card__title}>{title}</h1>
<p className={styles.card__content}>{content}</p>
</div>
);
}
Добавили класс card--with-image
только тогда, когда imgSrc
не пуст.. С таким подходом каждый компонент может иметь «базу» (в данном случае .card
) и уникальные стили, подстраивающиеся под нужный контент.
OOCSS подходит тем, кто думает на перспективу, ведь этот подход делает стили готовыми к переиспользованию. Когда есть несколько компонентов с разной начинкой, но похожей структурой, OOCSS позволяет легко добавить вариативность.
Например:
Единообразие в стилях: Любой новый компонент может использовать
card--with-image
илиcard__image
, не переписывая стили.Гибкость и расширяемость: Можно изменить стили карточек (например, поменять
flex-direction
) для всех компонентов сразу, без модификации их структуры.Простота обновления: Стили разделены на отдельные компоненты, поэтому изменение одного элемента (например, цвета фона) не приведёт к непредсказуемым последствиям в других местах.
OOCSS хорош, но иногда может показаться, что он создаёт слишком много классов. Да, будет больше классов для каждого типа компонента, но зато они работают изолированно и не влияют друг на друга. Это плата за гибкость и предсказуемость.
Какой подход выбрать?
Каждая методология имеет свои сильные и слабые стороны:
BEM хорош для простых и крупных проектов, где нужно чётко видеть, какой класс за что отвечает.
SMACSS идеально подойдёт для больших, многокомпонентных приложений, особенно когда вы работаете в команде.
OOCSS может быть полезен для адаптивного дизайна и UI-библиотек, где компоненты активно переиспользуются и требуется модульный подход.
Всё зависит от потребностей проекта и личных предпочтений. Лично я чаще использовал смесь BEM и OOCSS — так проще сохранять код читаемым, не дублируя излишки.
25 ноября в Otus пройдёт открытый урок «Эффективная работа с Next.js и TypeScript». Участники поговорят об использовании статической и серверной генерации, работе с маршрутизацией и оптимизации производительности. Обсудят типизацию компонентов и интеграцию с внешними API для создания надежных приложений. Если интересно, записывайтесь на странице практического курса по TypeScript.
Комментарии (3)
finethanks
17.11.2024 20:48Касательно использования связки BEM+CSS Modules. Нет смысла тащить в css названия компонента и использовать дефолтный БЭМ. Я бы рекомендовал использовать вот такую нотацию.
// components/Card.module.css .root { padding: 1em; border-radius: 5px; background-color: #f3f3f3; &_primary { background-color: #0070f3; color: white; } } .title { font-size: 1.5em; margin: 0; } .content { font-size: 1em; }
Меньше букв, не нужно таскать названия компонента в стили, использовать нижнее подчеркивания для модификаторов и camelCase для элементов, тогда при импорте стиле будет удобное ображение к классу через точку, а не через квадратные скобки
import styles from 'index.module.scss'; // styles.root // styles.root_primary // в отличии от // styles['card--primary']
Yan4evsky
17.11.2024 20:48Было бы интересно узнать, есть смысл в комбинировании. К чему это может привести? Может ли в этом смысл или создать больше сложностей.
serginho
styled-components