Как Linear добился ускорения навигации на ~30%, заменив рантаймовый CSS-in-JS на компилируемый StyleX — и какие уроки может извлечь каждый React-разработчик.
«Я потратил больше токенов на эту миграцию, чем хотелось бы признавать.» Так Кеннет Сковхус, инженер Linear, начинает рассказ о переносе React-приложений Linear с styled-components на StyleX. За последние несколько месяцев Linear систематически мигрирует кодовую базу — и результаты впечатляют: примерно на 30% быстрее рендеринг при навигации между страницами.
Это не просто замена одной библиотеки на другую. Это фундаментальный сдвиг в том, как современные React-приложения работают со стилями — от рантаймового CSS-in-JS, где стили генерируются в момент рендеринга, к компилируемому CSS-in-JS, где стили преобразуются на этапе сборки. Для любого, кто строит React-приложения в масштабе, путь Linear даёт конкретные уроки по производительности, архитектуре и направлению развития экосистемы React.
Миграция Linear была вызвана двумя сходящимися факторами: производительностью и поддержкой.
Рантаймовые CSS-in-JS-библиотеки, такие как styled-components, генерируют CSS-правила и внедряют их в DOM во время рендеринга приложения. Это означает, что пользователи платят за генерацию стилей при каждой навигации. В крупном приложении, таком как Linear — инструмент управления проектами со сложными UI-поверхностями — эти накладные расходы накапливаются на десятках компонентов на страницу.
Особенно заметна стоимость производительности при переходах между страницами. Когда пользователь перемещается между представлениями в Linear, новые компоненты монтируются, styled-components генерирует их CSS, внедряет в документ, и только после этого браузер может отрисовать новый контент. Со StyleX вся эта работа происходит на этапе сборки — сгенерированный CSS — это статический ресурс, который браузер загружает вместе с JavaScript-бандлом.
Решающим фактором стал перевод styled-components в режим обслуживания. После обновления до React 18 Linear почувствовал это напрямую: React представил useInsertionEffect для оптимизации инъекции стилей, но styled-components так и не внедрил его.
Кеннет Сковхус отследил зависший PR с реализацией useInsertionEffect и связался с его автором, Коди Олсеном из Sanity. Они протестировали оптимизированный форк Sanity — который в статье Sanity назван «последним средством». Форк — это спасательная шлюпка, а не долгосрочное решение.
Миграция руководствовалась пятью обязательными требованиями:
Linear оценил большинство совместимых с React библиотек стилизации. Ближайшим конкурентом был vanilla-extract — он предлагает хорошую статическую экстракцию и типовую безопасность. Однако его API показался фрагментированным, а необходимость в отдельных файлах стилей не соответствовала предпочтениям Linear.
StyleX — компилируемое решение CSS-in-JS от Meta — соответствовал всем требованиям. Он сохраняет стили локальными для компонентов, предоставляет небольшое API, детерминированное разрешение стилей через атомарные CSS-классы, типобезопасные интерфейсы и строгие ограничения, затрудняющие переопределение стилей компонентов извне. StyleX активно поддерживается Meta, используется на большинстве веб-поверхностей Meta и внедрён такими компаниями, как Figma и Cursor.
| Характеристика | styled-components | StyleX |
|---|---|---|
| Генерация стилей | Рантайм — во время рендеринга | Компиляция — атомарный CSS на этапе сборки |
| Накладные расходы | Инъекция стилей при каждом монтировании | Нулевые — статические CSS-классы |
| Инкапсуляция | Открыта — любой компонент можно обернуть в styled() |
Строгая — намеренные барьеры для внешней стилизации |
| Разрешение стилей | На основе специфичности CSS — возможны конфликты | Детерминированное — последний определённый стиль побеждает |
| Размер рантайма | ~12 КБ в бандле | Нулевой — только скомпилированный CSS |
| React Server Components | Несовместим — требует доступа к DOM | Совместим — только статические CSS-классы |
| Статус поддержки | Режим обслуживания | Активно поддерживается Meta |
| Сложность миграции | Н/Д (исходная система) | Высокая — требуется кодмод для больших кодовых баз |
Разница между двумя подходами наиболее наглядна в коде. Вот как изменился типичный компонент Linear:
import styled from 'styled-components';
import { Issue } from './types';
const Container = styled.div`
padding: 12px 16px;
border-radius: 8px;
border: 1px solid ${props => props.$active ? '#0f766e' : '#e7e5e4'};
background: ${props => props.$active ? '#f0fdfa' : '#ffffff'};
cursor: pointer;
transition: all 0.2s ease;
&:hover {
border-color: #0f766e;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
`;
const Title = styled.h3`
font-size: 0.95rem;
font-weight: 600;
margin: 0 0 4px 0;
color: #1c1917;
`;
const Meta = styled.span`
font-size: 0.8rem;
color: #78716c;
`;
function IssueCard({ issue, active }: Props) {
return (
<Container $active={active}>
<Title>{issue.title}</Title>
<Meta>#{issue.number} · {issue.status}</Meta>
</Container>
);
}
import stylex from '@stylexjs/stylex';
const styles = stylex.create({
container: {
padding: '12px 16px',
borderRadius: 8,
border: '1px solid #e7e5e4',
cursor: 'pointer',
transition: 'all 0.2s ease',
},
containerActive: {
borderColor: '#0f766e',
backgroundColor: '#f0fdfa',
},
title: {
fontSize: 14,
fontWeight: 600,
marginBottom: 4,
color: '#1c1917',
},
meta: {
fontSize: 13,
color: '#78716c',
},
});
const hoverStyles = stylex.create({
container: {
borderColor: '#0f766e',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
},
});
function IssueCard({ issue, active }: Props) {
return (
<div
{...stylex.props(
styles.container,
active && styles.containerActive,
)}
onMouseEnter={(e) => {
stylex.merge(hoverStyles.container);
}}
>
<h3 {...stylex.props(styles.title)}>
{issue.title}
</h3>
<span {...stylex.props(styles.meta)}>
#{issue.number} · {issue.status}
</span>
</div>
);
}
Версия на StyleX более явная — нет магических шаблонных литералов, нет динамической генерации CSS во время выполнения. Каждый стиль объявлен как обычный объект и детерминированно объединяется через stylex.props(). Активное состояние обрабатывается условной композицией стилей вместо интерполяции строки шаблона.
Linear не пытался выполнить миграцию вручную — кодовая база слишком велика и сложна. Вместо этого Кеннет Сковхус создал детерминированный кодмод: styled-components-to-stylex-codemod.
Сложность задачи огромна. styled-components использует тегированные шаблонные литералы — по сути, полный по Тьюрингу язык, встроенный в CSS-строки. Динамические значения, выражения, условные стили и вычисления на основе пропсов создают огромное количество возможных паттернов. Как отмечает Сковхус, «количество способов выразить одно и то же намерение огромно, и слишком легко получить результат, который выглядит правильно, но на деле — нет.»
Что усложняет задачу ещё больше: в Linear нет дизайн-системы. Миграция вынудила их расплачиваться по долгам, накопленным за годы стилизации — убирать гибкость из общих компонентов, ужесточать API и затруднять переопределение стилей извне.
Сейчас кодмод насчитывает 500+ пул-реквестов, примерно 100 000 строк инструментов миграции, онлайн-площадку, кросс-файловую обработку селекторов и обширное регрессионное покрытие. Это свидетельствует как о масштабе современных веб-приложений, так и о сложности паттернов CSS-in-JS на практике.
Миграция Linear сигнализирует о более широком сдвиге в экосистеме React. Компилируемый CSS становится подходом по умолчанию для производительных приложений. Вот что стоит учесть для своих проектов:
Если вы используете styled-components на крупной кодовой базе и замечаете задержки при рендеринге, StyleX (или vanilla-extract) может дать измеримые улучшения. Ускорение навигации на 30%, которого добился Linear, трудно игнорировать. Однако затраты на миграцию реальны — готовьтесь инвестировать в инструментарий и погашать долги по стилизации.
Начинаете с нуля? Пропустите рантаймовый CSS-in-JS. StyleX, vanilla-extract или даже CSS Modules с современным препроцессором обеспечат лучшую производительность и меньше проблем с поддержкой в долгосрочной перспективе.
Если ваше приложение состоит из нескольких десятков компонентов и навигация работает быстро, затраты на миграцию, вероятно, превысят выгоду. Но сейчас хорошее время для планирования — если вы ожидаете значительного роста, выбор компилируемого решения сейчас избавит от крупной миграции в будущем.
Миграция styled-components → StyleX — часть более широкого отраслевого тренда. Рантаймовый CSS-in-JS появился около 2016 года как реакция на глобальный CSS — он решал реальные проблемы (изоляция, колокация, динамические стили). Но по мере роста приложений стоимость рантайма стала заметной. Компилируемые решения представляют следующий этап эволюции: сохранить опыт разработки CSS-in-JS (колоцированные стили, привязка к компонентам, динамика), но устранить налог на рантайм.
В своих проектах я выбираю подход к стилизации исходя из потребностей приложения. Для производительных React-приложений компилируемый CSS даёт лучшее из двух миров. Если вы оцениваете варианты для нового проекта — обсудим ваши требования и подберём правильный подход.
styled-components-to-stylex-codemod — 500+ PR, ~100 000 строк инструментов миграции, онлайн-площадка, кросс-файловая обработка селекторов. Поскольку в Linear нет дизайн-системы, значительная часть работы ушла на ужесточение API компонентов.Выбор системы стилизации — только часть архитектуры фронтенда. Если вы оцениваете варианты для своего React-приложения или планируете миграцию с рантаймового CSS-in-JS — я могу помочь. Я full-stack веб-разработчик с опытом работы с React, Next.js и различными CSS-методологиями. Живу в Минске, работаю удалённо по всему миру. Свяжитесь для бесплатной консультации — обсудим ваш проект и подберём правильный подход.
Расскажите о вашем React-приложении — я порекомендую оптимальную архитектуру стилизации и предоставлю предварительную оценку. Бесплатно.