Олег Марков
CSS-in-JS — плюсы и минусы
Введение
CSS-in-JS — парадигма, при которой стили пишутся на JavaScript и генерируются в runtime или во время сборки. Этот подход произвёл революцию в стилизации React-компонентов, но по-прежнему вызывает споры в сообществе разработчиков.
В этой статье мы объективно рассмотрим преимущества и недостатки CSS-in-JS, сравним с альтернативами и поможем вам принять обоснованное решение для вашего проекта.
Что такое CSS-in-JS
CSS-in-JS объединяет несколько подходов к стилизации:
Runtime CSS-in-JS — стили вычисляются и внедряются в DOM во время работы приложения:
- Styled Components
- Emotion
- JSS
Zero-runtime / compile-time CSS-in-JS — стили генерируются во время сборки, в продакшн попадает чистый CSS:
- vanilla-extract
- Linaria
- Panda CSS
Большинство споров касаются именно runtime-решений, поскольку они имеют наибольший overhead.
Плюсы CSS-in-JS
1. Изоляция стилей по умолчанию
Главная проблема обычного CSS — глобальная область видимости. Класс .button из одного файла может конфликтовать с .button из другого. CSS-in-JS решает это автоматически:
// Каждый компонент получает уникальный класс (например, sc-abc123)
// Конфликты невозможны по определению
const Button = styled.button`
background: #6c63ff; /* Применится только к этой кнопке */
`;
2. Динамические стили через JavaScript
CSS-in-JS позволяет использовать всю мощь JavaScript для генерации стилей:
const Button = styled.button`
background: ${props => props.disabled
? '#ccc'
: props.variant === 'danger'
? '#e53e3e'
: '#6c63ff'
};
/* Вычисления на основе данных */
width: ${props => `${props.progress}%`};
/* Медиа-запросы с переменными */
@media (max-width: ${props => props.theme.breakpoints.md}) {
padding: ${props => props.compact ? '4px 8px' : '8px 16px'};
}
`;
3. Совместное размещение стилей и компонента
Стили живут рядом с логикой компонента — не нужно переключаться между файлами:
// Всё в одном файле
const UserCard = () => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<Container expanded={isExpanded}>
<Avatar src={user.avatar} />
<Content>
<Name>{user.name}</Name>
{isExpanded && <Bio>{user.bio}</Bio>}
</Content>
</Container>
);
};
// Стили рядом с компонентом
const Container = styled.div`
display: flex;
padding: ${props => props.expanded ? '24px' : '16px'};
transition: padding 0.2s;
`;
4. Автоматическое удаление неиспользуемых стилей
Если компонент удалён, его стили удаляются автоматически — нет риска "утечки" CSS:
// Удалили компонент OldButton из кода
// → его стили автоматически исчезли из бандла
5. Полноценный TypeScript
Стили могут быть типизированы через TypeScript, что исключает ошибки в передаче значений:
interface ThemeColors {
primary: string;
secondary: string;
danger: string;
}
// Ошибка компиляции если передать несуществующий вариант
const Button = styled.button<{ variant: keyof ThemeColors }>`
background: ${props => props.theme.colors[props.variant]};
`;
<Button variant="primary" /> // ✅
<Button variant="purple" /> // ❌ Ошибка TypeScript
6. Простая реализация тем
Темизация через ThemeProvider позволяет централизованно управлять дизайном:
<ThemeProvider theme={darkTheme}>
<App /> {/* Все компоненты автоматически получат тёмную тему */}
</ThemeProvider>
7. Критический CSS на сервере
Modern CSS-in-JS библиотеки умеют генерировать критический CSS только для текущей страницы:
// В SSR-режиме генерируется только CSS, используемый на этой странице
// Не весь CSS приложения — это улучшает FCP (First Contentful Paint)
Минусы CSS-in-JS
1. Overhead в runtime
Runtime CSS-in-JS парсит шаблонные строки, вычисляет стили и вставляет их в DOM при каждом рендере. Это создаёт дополнительную нагрузку:
Обычный CSS: 0ms overhead
CSS Modules: 0ms overhead (статический CSS)
Styled Components v6: ~2-5ms на 1000 компонентов
Emotion: ~1-3ms на 1000 компонентов
Для большинства приложений это незначительно, но для сложных дашбордов с тысячами компонентов может быть заметно.
2. Размер бандла
Библиотеки CSS-in-JS добавляют вес:
| Библиотека | Gzipped |
|---|---|
| styled-components | ~15KB |
| @emotion/styled + @emotion/react | ~11KB |
| vanilla-extract (zero-runtime) | ~1KB |
| Tailwind CSS (purged) | ~5-15KB |
| CSS Modules | 0KB |
3. Сложности с Server Components (React 18+)
Это актуальная проблема для Next.js App Router. Runtime CSS-in-JS несовместим с React Server Components (RSC), поскольку требует выполнения JavaScript на клиенте:
// ❌ Не работает в Server Component (Next.js App Router)
import styled from 'styled-components';
const Title = styled.h1`color: purple;`; // Ошибка!
export default function Page() {
return <Title>Привет</Title>;
}
// ✅ Альтернативы:
// 1. Добавить 'use client' директиву (превращает в Client Component)
// 2. Использовать CSS Modules или Tailwind
// 3. Использовать zero-runtime решение (vanilla-extract, Panda CSS)
Это серьёзное ограничение, если вы строите приложение на Next.js с активным использованием RSC.
4. Трудности с отладкой
В DevTools вы увидите сгенерированные классы вместо понятных имён:
/* В DevTools без плагина */
.sc-abc123 { background: #6c63ff; }
/* С babel-plugin-styled-components */
.Button-sc-abc123 { background: #6c63ff; }
Плагины Babel для разработки решают эту проблему, но требуют дополнительной настройки.
5. Кривая обучения
Разработчики, пришедшие из классического CSS-мира, могут испытывать трудности с непривычным синтаксисом и паттернами CSS-in-JS.
6. Vendor lock-in
Переход с одной CSS-in-JS библиотеки на другую или обратно на обычный CSS — трудоёмкий рефакторинг, особенно в крупных проектах.
Сравнение с альтернативами
CSS-in-JS vs CSS Modules
| Критерий | CSS-in-JS | CSS Modules |
|---|---|---|
| Изоляция | Автоматическая | Автоматическая |
| Динамические стили | ✅ Нативно | ❌ Только через style prop |
| SSR/RSC совместимость | ⚠️ Требует настройки | ✅ Полная |
| Bundle size | +10-15KB | 0KB |
| Runtime overhead | Есть | Нет |
| TypeScript | Хорошая | Базовая |
| Кривая обучения | Средняя | Низкая |
CSS-in-JS vs Tailwind CSS
| Критерий | CSS-in-JS | Tailwind |
|---|---|---|
| Динамические стили | ✅ Любые | ⚠️ Ограниченно через классы |
| Размер бандла | +10-15KB | ~5-15KB |
| Runtime overhead | Есть | Нет |
| Читаемость JSX | Чистый | Длинные className |
| Дизайн-система | Тема + TypeScript | tailwind.config.js |
| RSC совместимость | ⚠️ Проблематично | ✅ Полная |
| Итеративное прототипирование | Медленнее | Быстрее |
Когда использовать CSS-in-JS
CSS-in-JS подходит, если:
- Строите полноценную дизайн-систему с централизованной темой
- Компоненты имеют сложную логику стилей, зависящую от множества пропсов
- Команда предпочитает держать стили и логику компонента вместе
- Не используете React Server Components в Next.js
- Работаете над проектом, где важна изоляция стилей между командами
CSS-in-JS не подходит, если:
- Используете Next.js App Router с Server Components
- Критична максимальная производительность (игры, анимации 60fps+)
- Небольшой проект без сложных требований к стилизации
- Команда хорошо знает Tailwind или CSS Modules
Zero-runtime как компромисс
Современные zero-runtime решения устраняют главные недостатки CSS-in-JS, сохраняя преимущества:
// vanilla-extract — TypeScript стили без runtime overhead
import { style, styleVariants } from '@vanilla-extract/css';
export const base = style({
display: 'flex',
alignItems: 'center',
padding: '12px 24px',
borderRadius: '8px',
});
export const variants = styleVariants({
primary: { backgroundColor: '#6c63ff', color: 'white' },
secondary: { backgroundColor: '#f0f0f0', color: '#333' },
});
import { base, variants } from './Button.css.ts';
import clsx from 'clsx';
function Button({ variant = 'primary', children }) {
return (
<button className={clsx(base, variants[variant])}>
{children}
</button>
);
}
Итоги
CSS-in-JS — мощный инструмент с реальными преимуществами для определённого класса задач. Его стоит выбирать осознанно, взвесив требования проекта:
Выбирайте runtime CSS-in-JS (Styled Components, Emotion), если:
- SPA без SSR или с Pages Router в Next.js
- Нужна гибкая система тем
- Важна TypeScript-типизация стилей
Выбирайте zero-runtime CSS-in-JS (vanilla-extract, Panda CSS), если:
- Хотите преимущества CSS-in-JS без runtime overhead
- Используете Next.js App Router с RSC
Выбирайте Tailwind или CSS Modules, если:
- Нужна максимальная простота и производительность
- Команда предпочитает традиционный CSS-подход
Постройте личный план изучения React до уровня Middle — бесплатно!
React — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по React
Лучшие курсы по теме

React и Redux Toolkit
Антон Ларичев
TypeScript с нуля
Антон Ларичев