Дайджесты новостей
Концептуальная иллюстрация миграции GitHub: переход от динамического CSS-in-JS к чистым CSS Modules ускоряет серверный рендеринг.

Почему GitHub переходит на чистый CSS: ускорение SSR на 55% без CSS-in-JS

На протяжении почти десятилетия фронтенд-индустрия жила под влиянием парадигмы CSS-in-JS. Библиотеки вроде styled-components и Emotion обещали идеальную жизнь разработчика: полную изоляцию стилей на уровне компонентов, удобную передачу пропсов в правила CSS и автоматическое удаление неиспользуемых стилей из бандла. Однако по мере роста масштабов веб-сервисов этот комфорт обернулся скрытым налогом на производительность. Инженеры GitHub завершили масштабную трехлетнюю миграцию интерфейсов сайта со styled-components на классические CSS Modules и поделились впечатляющими результатами: серверный рендеринг страниц ускорился более чем в два раза.

Опыт команды дизайн-системы Primer показал, что попытка заставить JavaScript управлять каждой строчкой таблицы стилей во время выполнения запроса наносит сокрушительный удар по быстродействию.

Куда исчезают миллисекунды: цена рантайм-стилизации

В архитектуре styled-components браузер и сервер Node.js вынуждены совершать огромный объем невидимой работы на каждый отрендеренный компонент. Сервер должен распарсить шаблонную строку, вычислить хеш-сумму классов на основе переданных пропсов, сгенерировать уникальные CSS-правила и вставить их в тело ответа.

// Исторический подход: styled-components с вычислением стилей в рантайме
import styled from 'styled-components';

interface ButtonProps {
  $variant?: 'primary' | 'danger';
}

export const ActionButton = styled.button<ButtonProps>`
  padding: 8px 16px;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
  background-color: ${props => props.$variant === 'danger' ? 'var(--color-danger-fg)' : 'var(--color-btn-bg)'};
  border: 1px solid var(--color-btn-border);

  &:hover {
    filter: brightness(0.95);
  }
`;

При рендеринге страницы с тысячами элементов (коммиты, диффы кода, списки задач) этот механизм превращается в тяжелую нагрузку на процессор. На клиенте ситуация повторяется: при гидратации JavaScript тратит драгоценное процессорное время на регистрацию виртуальных таблиц стилей и инъекцию тегов <style> в DOM-дерево.

Переход на CSS Modules: статика против динамики

Решением стал переход на CSS Modules — технологию, изолирующую имена классов на этапе предварительной сборки проекта без какого-либо рантайма в Node.js или браузере. Компоненты Primer React были переписаны на статические файлы .module.css.

Вместо интерполяции JavaScript-пропсов прямо в строки стилей команда задействовала нативные механизмы современного CSS: селекторы атрибутов данных и CSS-переменные:

/* ActionButton.module.css: чистая изоляция на этапе компиляции */
.button {
  padding: 8px 16px;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid var(--color-btn-border);
  background-color: var(--button-bg, var(--color-btn-bg));
}

.button[data-variant="danger"] {
  --button-bg: var(--color-danger-fg);
}

.button:hover {
  filter: brightness(0.95);
}
// ActionButton.tsx: легковесный компонент без зависимости от CSS-in-JS
import styles from './ActionButton.module.css';

interface ButtonProps {
  variant?: 'primary' | 'danger';
  children: React.ReactNode;
}

export function ActionButton({ variant = 'primary', children }: ButtonProps) {
  return (
    <button className={styles.button} data-variant={variant}>
      {children}
    </button>
  );
}

Парадокс объема и победа кэширования

После завершения миграции метрики производительности github.com показали поразительный сдвиг:

  • Время серверного рендеринга (SSR) сократилось на 55%, освободив значительные вычислительные ресурсы инфраструктуры.
  • Время инициализации компонентов в браузере при гидратации уменьшилось на 25%, обеспечив моментальный отклик интерфейса на клики.

При этом инженеры столкнулись с любопытным парадоксом: суммарный объем доставляемых CSS-файлов немного вырос. Поскольку статический CSS теперь включает селекторы для всех возможных состояний компонентов, а не только для тех, что присутствуют на текущем экране, байтов стилей в бандле стало больше. Однако этот прирост оказался абсолютно безвредным. Статический CSS отдается в сжатом виде (Brotli), кэшируется глобальной сетью CDN один раз на недели вперед и парсится нативным движком браузера на порядки быстрее, чем любой JavaScript.

Опыт GitHub подвел черту под эпохой тяжелого CSS-in-JS: возврат к корням веб-платформы и статическому CSS остается самым эффективным рецептом высокой скорости в веб-разработке.