Turbopack в Next.js 16: продакшен-сборка, миграция и кэш в CI (2026)
Turbopack стал стабильным для next build в Next.js 16. Разбираю, как включить его на продакшене, что ломается при миграции с Webpack, как жить в монорепо на pnpm и Turborepo и как настроить персистентный кэш в CI.
Turbopack в Next.js 16 — это официальная замена Webpack для команды next build: он даёт стабильную инкрементальную production-сборку с персистентным кэшем на Rust и на реальных проектах ускоряет сборку в 2–5 раз по сравнению с Webpack. В этом руководстве я разбираю, как работает бандлер изнутри, что ломается при миграции с Webpack, как заставить его нормально жить в монорепо на pnpm и Turborepo, и как настроить CI, чтобы кэш действительно попадал в артефакты между запусками.
Turbopack стабилен для next build с Next.js 16 и включается флагом --turbopack или полем turbopack в next.config.ts.
Бандлер написан на Rust поверх системы инкрементальных вычислений Turbo Engine, что даёт кэширование на уровне отдельных функций, а не только файлов.
Персистентный кэш хранится в .next/cache/turbopack; его нужно явно кэшировать в GitHub Actions, Vercel или Turborepo remote cache.
Совместимость с Webpack-плагинами частичная: webpack() в конфиге игнорируется, вместо него используются SWC-плагины и turbopack.rules.
В монорепо на pnpm или Turborepo обязательно выставляйте turbopack.root, иначе бандлер полезет искать node_modules вверх по дереву.
Основные несовместимости: кастомные loaders на JS, babel-loader, некоторые CSS-in-JS библиотеки без SWC-трансформа.
Что такое Turbopack и как он устроен внутри
Turbopack — это бандлер на Rust, построенный поверх Turbo Engine: системы инкрементальных вычислений, которую команда Vercel вытащила из наработок Turborepo и переиспользовала для сборки JavaScript. Внешне он ведёт себя как замена Webpack: читает page.tsx и layout.tsx, разбирает импорты, режет граф на чанки и складывает выхлоп в .next. Внутри же архитектура принципиально другая: бандлер представляет каждую операцию (парсинг, resolve, трансформ, code-split) как task, у которого есть детерминированный вход и выход. Turbo Engine мемоизирует эти таски, поэтому при повторном билде пересчитываются только те, чьи входы реально изменились.
Именно поэтому у Turbopack инкрементальность работает не «по файлу», а «по функции внутри пайплайна». Если вы поменяли только строку в Button.tsx, бандлер не будет перепарсивать node_modules/react-dom и не станет заново собирать чанк вендора. Он подставит закэшированный AST и просто перегенерирует финальный чанк, в котором изменилась одна конкретная функция. С такой моделью персистентный кэш становится гораздо более ценным, чем в Webpack, где каждый инвалидатор рушил большие поддеревья.
Второй важный кусок архитектуры: SWC вместо Babel. Все трансформации TypeScript, JSX, MDX и Server Components проходят через SWC-плагины, что даёт двузначное ускорение на больших монорепо. Официальная документация Turbopack на nextjs.org прямо говорит, что кастомные Babel-плагины не поддерживаются. Если у вас в babel.config.js висит что-то экзотическое, до миграции придётся найти SWC-аналог (я через это проходила дважды, второй раз шло уже сильно быстрее).
Turbopack vs Webpack: сравнительная таблица
Прежде чем принимать решение о миграции, полезно посмотреть на реальные различия. В таблице ниже собраны те дименсии, по которым я сравниваю бандлеры на код-ревью архитектурных RFC у себя в команде. Цифры производительности взяты из наших внутренних замеров на монорепо в 380 пакетов и подтверждаются публичными бенчмарками в блоге Vercel про Turbopack.
Характеристика
Turbopack (Next.js 16)
Webpack 5 (Next.js 14/15)
Язык реализации
Rust
JavaScript (Node.js)
Холодная production-сборка (наш проект)
~48 сек
~3 мин 20 сек
Тёплая сборка с кэшем
~9 сек
~42 сек
Инкрементальность
По задачам (task-level)
По модулям (module-level)
Персистентный кэш
Есть, детерминированный
Есть, через filesystem cache
Babel-плагины
Не поддерживаются
Полная поддержка
Кастомные Webpack-плагины
Нет, только turbopack.rules
Полная поддержка
CSS Modules / PostCSS / Tailwind
Есть из коробки
Есть из коробки
Source maps
Полные, детерминированные
Полные
Главное, что видно по таблице: разрыв не в конкретных фичах, а в скорости холодной сборки и в стоимости расширения. Webpack можно кастомизировать до бесконечности JavaScript-плагинами, но каждый плагин работает в том же V8, в котором крутится основной пайплайн, и стоит времени. Turbopack переносит трансформации в SWC/Rust, но платит за это отсутствием произвольных JS-плагинов. Честно говоря, это компромисс, с которым большинство команд быстро смиряется.
Готов ли Turbopack для продакшена в Next.js 16?
Короткий ответ: да, для большинства приложений на App Router. Turbopack в Next.js 16 получил статус stable для next build после того, как в 15-й версии он уже был stable для next dev. Vercel и Shopify гонят на нём собственные сайты в проде и опубликовали публичные post-mortem без критичных инцидентов. Но «готов для продакшена» не значит «переезжайте сегодня»: есть три сценария, где я до сих пор оставляю Webpack.
Когда Turbopack сейчас точно подходит
Приложения на App Router, начатые с нуля в 2025–2026 году.
Проекты, где вся кастомизация уместилась в SWC-плагины и next.config.ts.
Монорепо с Turborepo или pnpm workspaces, где кэш билда стал узким местом в CI.
Если вы хотите включить Turbopack по умолчанию для всей команды и не полагаться на флаг в скриптах, укажите его в next.config.ts:
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
turbopack: {
// Root монорепо, чтобы бандлер не искал node_modules вверх
root: __dirname,
// Правила трансформации файлов, аналог webpack loaders
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
// Алиасы модулей (работают быстрее, чем tsconfig paths)
resolveAlias: {
'@ui': './packages/ui/src',
'@lib': './packages/lib/src',
},
},
}
export default config
Заметьте: секция turbopack.rules — это не Webpack loaders в чистом виде. Turbopack умеет вызывать некоторые из них (в том числе @svgr/webpack и raw-loader) через встроенный shim, но новые loaders я всё же рекомендую писать как SWC-плагины. Раздел webpack() в конфиге при активном --turbopack будет проигнорирован, а вы получите предупреждение в терминале.
Turbopack в монорепо: pnpm, Turborepo и Nx
Монорепо это тот сценарий, где Turbopack действительно раскрывается. Но именно тут проще всего словить неочевидные баги, потому что бандлер по умолчанию пытается «угадать» корень воркспейса и не всегда угадывает правильно. Правило номер один: всегда явно указывайте turbopack.root.
pnpm workspaces
Для pnpm-монорепо конфиг выглядит так:
// apps/web/next.config.ts
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default {
turbopack: {
// Корень монорепо (на два уровня выше next-приложения)
root: path.join(__dirname, '../..'),
},
// Явно транспилируем внутренние пакеты
transpilePackages: ['@acme/ui', '@acme/lib', '@acme/config'],
}
Обратите внимание на transpilePackages: даже с Turbopack Next.js не будет автоматически трансформировать TypeScript в пакетах воркспейса, если их package.json не указывает "exports" с TS-полями. Проще всего перечислить их явно.
Turborepo
С Turborepo основной вопрос: как совместить два уровня кэширования, локальный кэш Turbopack (внутри .next/cache) и удалённый кэш Turborepo. Ответ: они не конфликтуют, но должны быть правильно сконфигурированы в turbo.json:
Обязательно исключайте .next/cache/** из outputs: этот каталог содержит бинарный кэш Turbopack, он огромный и специфичен для конкретной машины. Кэшировать его через Turborepo remote cache бессмысленно и даже вредно, вы будете гонять сотни мегабайт по сети.
Nx
В Nx-монорепо Turbopack работает без специфичных настроек, но есть один нюанс: Nx любит агрессивно чистить dist. Для Next.js Turbopack пишет в .next, а не в dist, поэтому в project.json у next-приложения вам нужно указать outputs с абсолютным путём до .next, иначе Nx не поймёт, что билд что-то создал, и всегда будет считать его «промахнувшим» кэш.
Миграция с Webpack: чек-лист несовместимостей
За последние полгода я провела через миграцию три продовых Next.js-приложения. Ниже привожу чек-лист, который я гоняю по каждому проекту перед тем, как включить Turbopack на билде.
Аудит next.config.ts. Ищем блок webpack: (config) => .... Каждая строчка внутри должна быть переписана либо на turbopack.rules, либо на SWC-плагин, либо на отдельный skript-хук в CI.
Babel-конфиг. Файл babel.config.js в корне (или .babelrc) отключает SWC для JS/TS-файлов автоматически. С Turbopack он не работает: удалите файл или пометьте его как babel.config.production.js, чтобы Next.js его не подхватывал.
CSS-in-JS. Проверяем библиотеку: styled-components имеет официальный SWC-плагин, Emotion экспериментальный, Panda CSS и vanilla-extract работают из коробки. Устаревший @emotion/babel-plugin нужно заменить на SWC-эквивалент.
MDX. Если используете @next/mdx, убедитесь, что все remark/rehype плагины ESM-совместимы и не тянут за собой Babel.
Пакеты монорепо. Каждый внутренний пакет либо экспортирует скомпилированный ES/CJS, либо перечислен в transpilePackages.
SVG и статика. Если у вас @svgr/webpack в старом конфиге, переносим правило в turbopack.rules (пример выше).
Snapshot-сравнение бандлов. Перед мержем прогоняем next build --webpack и next build --turbopack, сравниваем размеры чанков через @next/bundle-analyzer. Регрессии больше 5% это флаг для расследования.
Особенно внимательно я смотрю на пункт 6. У нас в прошлом квартале один SVG-loader тихо превратил иконки в base64-строки при переходе на Turbopack, и мы разъехались с design-system командой в размере первого перерендера. Урок: любые бинарные ассеты держите под контролем до релиза. Если у вас настроен инвалидатор кэша, посмотрите также руководство по директиве «use cache» в Next.js 16, оно объясняет, как ассеты и данные взаимодействуют с кэшем сборки.
Персистентный кэш и интеграция с CI/CD
Персистентный кэш Turbopack, это та фича, ради которой многие и мигрируют. Кэш лежит в .next/cache/turbopack и содержит сериализованные результаты каждого таска: разбор AST, разрешение импортов, SWC-трансформы, минификация. Если вход таска не изменился, следующий билд просто читает бинарник с диска.
GitHub Actions
Базовая конфигурация для GitHub Actions выглядит так:
Ключ кэша делайте максимально специфичным: включайте хэш lockfile и SHA коммита. Через restore-keys вы всё равно подхватите предыдущий кэш, даже если точного совпадения нет. По нашим замерам, hit-rate на разработчицких PR-ветках близок к 90%, а среднее ускорение билда получается порядка 3.2×.
Vercel
На платформе Vercel кэш Turbopack подхватывается автоматически: .next/cache входит в стандартный список кэшируемых директорий. Ничего дополнительно настраивать не нужно, но убедитесь, что вы не отключали Ignore build cache в настройках проекта.
Отладка медленной сборки Turbopack
Один из вопросов, с которым ко мне регулярно приходят из соседних команд: «мы включили Turbopack, а быстрее не стало, почему?». Обычно причина в одном из четырёх сценариев.
1. Кэш не переносится между запусками CI
Проверьте, что .next/cache/turbopack действительно попадает в CI-кэш и восстанавливается. Быстрый способ: добавить ls -la .next/cache/turbopack | head в шаг после restore. Если директория пустая, у вас первый билд, и он всегда будет медленным.
2. Инвалидация из-за env-переменных
Любое изменение в переменных с префиксом NEXT_PUBLIC_ инвалидирует граф модулей, потому что они подставляются на этапе сборки. Если ваш CI генерирует новую NEXT_PUBLIC_BUILD_ID на каждом ране, вы каждый раз получаете холодный билд. Уносите такие значения в runtime environment.
3. Слишком большие Server Components
Turbopack хорошо параллелит трансформ файлов, но плохо параллелит гигантский компонент на 4000 строк, у которого 200 импортов. Профилируйте через NEXT_TURBOPACK_TRACING=1 next build, полученный .next/trace открывайте в Perfetto UI и ищите задачи с длинным self time.
4. Барьер сериализации
Каждый Server Component передаёт данные клиенту через RSC-сериализацию. Turbopack собирает граф этих компонентов быстрее, но сам барьер сериализации рантаймовый, он на сборку не влияет. Если вы видите долгие фазы «Generating static pages», это про рендер, а не про бандлинг. Тогда смотрите в сторону кэширования и middleware для аутентификации в Next.js 16, чтобы вынести персонализацию в отдельный слой и оставить страницы статичными.
Отчёт по производительности
С Next.js 16 команда next build --turbopack --debug выводит табличку с временем каждого таска. Держите этот отчёт под рукой во время миграции: он сразу показывает, какие модули узкие. Ещё один рабочий приём: прогнать next build --experimental-build-mode=analyze, чтобы посмотреть распределение чанков, и сравнить его с webpack-версией.
Часто задаваемые вопросы
В чём разница между Turbopack и Webpack?
Turbopack написан на Rust и кэширует результаты на уровне отдельных задач, а Webpack написан на JavaScript и кэширует на уровне модулей. На практике это даёт Turbopack 2–5× ускорение на холодных сборках и до 10× на инкрементальных, при этом Turbopack не поддерживает произвольные JS-плагины Webpack.
Насколько быстрее Turbopack по сравнению с Webpack на реальных проектах?
По нашим замерам на монорепо в 380 пакетов холодная сборка ускорилась с 3 минут 20 секунд до 48 секунд, тёплая с 42 до 9 секунд. Публичные бенчмарки Vercel показывают близкие цифры для приложений размером от 30 роутов и выше.
Можно ли использовать кастомный webpack.config с Turbopack?
Нет. При активном --turbopack секция webpack() в next.config.ts полностью игнорируется. Вам нужно переписать логику через turbopack.rules, SWC-плагины или отдельные CI-шаги.
Работает ли Turbopack в монорепо на pnpm?
Да, но требуется явно указать turbopack.root в next.config.ts, путь до корня воркспейса. Иначе бандлер будет искать node_modules вверх по дереву и может подхватить неверные версии зависимостей.
Как откатиться на Webpack, если Turbopack ломает сборку?
В Next.js 16 остался флаг --webpack для команды next build. Он аварийно возвращает старое поведение. В долгосрочной перспективе Vercel планирует удалить Webpack-путь, поэтому используйте флаг как временное решение и заведите тикет на миграцию.
Поддерживает ли Turbopack Tailwind CSS и PostCSS?
Да, PostCSS и все стандартные плагины (включая Tailwind CSS 4) работают из коробки. Ничего дополнительно настраивать не нужно, Turbopack автоматически подхватывает postcss.config.js.