Bcorrections

@import в CSS: почему это медленно и чем заменить

Директива @import в CSS — частая причина задержки рендера. Разбираем как она работает, почему вредна для производительности и какие есть альтернативы.

CSS-директива @import url('...'); позволяет одному CSS-файлу подключить другой. Удобно для модульной организации стилей: основной файл подтягивает темы, типографику, медиа-стили. Удобно для разработчика, но почти всегда вредно для производительности.

В этой статье — как @import работает, почему он замедляет рендер, и чем его заменить.

Как браузер обрабатывает @import

Стандартный flow:

  1. Браузер получает HTML, парсит, находит <link rel="stylesheet" href="main.css">
  2. Скачивает main.css
  3. Парсит main.css, находит внутри @import url('theme.css')
  4. Только теперь запрашивает theme.css
  5. Скачивает theme.css
  6. Парсит, если там тоже @import — повторяет
  7. Финально строит CSSOM, начинает рендер

Главная проблема — шаги 2-5 последовательны. Браузер не знает что нужен theme.css, пока не скачает и не распарсит main.css. Это блокирующая цепочка.

С <link> в HTML история другая — браузер видит все теги сразу при парсинге HTML, начинает скачивать все CSS параллельно. Существенная разница в финальном time-to-render.

Сколько это стоит

Конкретный пример. Допустим, у вас main.css весит 50 КБ, theme.css — 30 КБ. На быстром соединении каждый скачивается за 200 мс.

Через <link>:

  • Браузер начинает оба запроса одновременно
  • Через 200 мс оба готовы
  • Общее время до начала рендера: ~250 мс

Через @import в main.css:

  • Браузер скачивает main.css — 200 мс
  • Парсит, обнаруживает @import
  • Скачивает theme.css — ещё 200 мс
  • Общее время до рендера: ~450 мс

Разница — почти в два раза. На медленном соединении (3G mobile) — разница 1-2 секунды. Это удар по LCP и Core Web Vitals.

Когда это особенно болезненно

Глубокие цепочки

Если у вас main.css импортирует theme.css, theme.css импортирует typography.css, typography.css импортирует variables.css — браузер обрабатывает их последовательно. На 4 уровнях глубины задержка может составить 1-2 секунды на быстром интернете.

Условные импорты

@import url('mobile.css') (max-width: 768px);

Кажется логичным — на мобильных не качать desktop-стили. Но браузер всё равно скачивает все CSS (он не знает заранее условие), парсит, и только потом решает применять или нет. Сэкономили на исполнении, но не на загрузке.

Импорты из CDN

@import url('https://cdn.jsdelivr.net/npm/normalize.css');

К дополнительному запросу добавляется DNS lookup и TCP-handshake к новому домену. На холодном соединении +300-500 мс.

Альтернативы

1. Множественные <link> теги в HTML

Старая школа, но работает:

<link rel="stylesheet" href="/css/normalize.css">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/theme.css">

Браузер начинает параллельную загрузку всех трёх сразу. Парсит по мере готовности. Объединённый CSSOM собирается раньше.

2. Сборщик объединяет CSS

Webpack, Vite, esbuild, Parcel — все они умеют склеивать CSS-файлы в один при сборке. Один HTTP-запрос вместо нескольких.

// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
  build: {
    cssCodeSplit: false, // один CSS вместо разбивки
  },
});

Минус: при изменении одной строчки в CSS меняется весь файл — пользовательский кеш протухает. Но обычно стили меняются редко, плюс не каждое изменение деплоится.

3. CSS Modules или CSS-in-JS

Современные React-стеки часто используют CSS Modules (Next.js по умолчанию) или CSS-in-JS (styled-components, emotion). Стили подключаются автоматически на уровне компонентов, без @import. Каждый компонент получает свой scope, сборщик объединяет в финальный bundle.

4. HTTP/2 push (deprecated, но был релевантен)

В эпоху HTTP/2 был механизм push — сервер мог отправить CSS-файл клиенту до того, как он его запросил. Это решало проблему @import-цепочек. С 2022 года Chrome убрал push (он создавал больше проблем, чем решал). Не используйте.

5. Inline критического CSS

Самое радикальное и эффективное решение. Стили above-the-fold блока инлайнятся прямо в HTML через <style>, остальные подгружаются асинхронно. Подробнее — в посте про render-blocking ресурсы.

Когда @import всё-таки можно

Несколько случаев когда @import оправдан:

  1. Шрифты с Google Fonts — в Google Fonts API сам файл подключается через <link>, а внутри он использует @import для подгрузки конкретных weight'ов. Это часть документированного API, не вы решаете.
  2. Условные импорты по media-query — для печатных стилей, например @import 'print.css' print;. Печатные стили не нужны при загрузке экрана, и браузер это понимает (загружает с низким приоритетом).
  3. Динамические темы, переключаемые JavaScript'ом. Тогда «лишний» @import не критичен — он сработает только при переключении темы, не на первой загрузке.

Во всех остальных случаях — избегайте.

Как проверить ваш сайт

Откройте Chrome DevTools → Network → фильтр CSS. Загрузите страницу.

В колонке Initiator смотрите кто запустил запрос:

  • Если CSS-файл инициирован «(index)» или HTML — это <link> (хорошо)
  • Если инициирован другим CSS-файлом — это @import (плохо)

Если видите цепочку CSS-файлов, инициированных друг другом — у вас @import. Стоит переделать.

Резюме

@import в CSS — атавизм из эпохи модульного CSS до сборщиков. В 2026 году с modern build pipeline (Webpack/Vite/etc) альтернатива всегда есть. Замена @import на параллельные <link> теги или склеенный bundle обычно ускоряет first paint на 200-1000 мс. Простая правка с прогнозируемым результатом.

Хотите проверить ваш CSS на цепочки и render-blocking? Напишите нам. Аудит производительности за 2 рабочих дня.

Веб-разработка

Это часть нашей услуги Разработка сайтов

Сайты под ключ, сразу готовые к SEO и AI-поиску

Перейти к услуге →
Идём дальше?

Нужна пара экспертных глаз на ваш проект?

Делаем экспресс-аудит за 2 рабочих дня: показываем где сайт теряет трафик и что исправить в первую очередь.

Обсудить проект