Turbopack у Next.js 16.4 приніс набір змін, які об'єднує одна думка: не роби роботу раніше, ніж вона потрібна. Disk cache став на 20–25% меншим без жодної конфігурації, server HMR – лінивим (оновлюються лише роути, які реально запитуються), runtime тепер один спільний чанк на всі роути, production-бандли поменшали завдяки коротшим CSS-класам і export mangling, а Bundle Analyzer навчився снапшотів, diff-ів і виділення critical render path. Нічого революційного – і саме тому це хороший реліз: нудні оптимізації, які відчуваються щодня.

Менший кеш без твоєї участі

Disk cache Turbopack тепер використовує на 20–25% менше місця. Механіка чесна: Zstandard-компресія для даних, які займають найбільше простору, LZ4 для метаданих (щоб lookup лишався швидким), плюс розумніша компакція, яка ефективніше викидає застарілі дані.

Чому це важливо не лише для диска: на CI з кешованими білдами розмір кеша – це час завантаження/вивантаження артефактів. Мінус чверть обсягу – це хвилини на місяць, які ніхто не помічає окремо, але всі відчувають сумарно.

Окремо в 16.4 з'явився experimental-прибиральник turbopackGc – garbage collector, який чистить невикористану скомпільовану роботу з пам'яті й кеша, включно зі слідами видалених роутів із минулих сесій. Якщо у тебе довгі dev-сесії на великому проєкті – варто спробувати.

Lazy server HMR: не оновлюй те, на що ніхто не дивиться

Раніше зміна shared server-модуля могла тригерити оновлення роутів, які ти відкривав раніше в dev-сесії – навіть якщо зараз із ними не працюєш. У 16.4 Turbopack компілює і застосовує server-оновлення лише тоді, коли запит їх реально потребує. Сторінка, яку ти дивишся, оновлюється як завжди; решта чекає, поки їх знову запитають.

У pet-проєкті різницю можна не помітити. У великому застосунку з десятками роутів і monorepo зі спільними модулями – це відчутний developer experience: редагуєш lib/utils.ts і не платиш за перекомпіляцію половини застосунку.

Туди ж – experimental turbopackLazyDynamicImports: клієнтські import() компілюються лише коли браузер їх запитав, а не на старті dev-сервера. Бібліотека, що вантажиться по кліку, більше не сповільнює перший запуск.

Production: спільний runtime і коротші імена

Дві зміни для production-бандлів:

  • Спільний runtime-чанк. Runtime Turbopack тепер один на всі роути замість копій. Менший сумарний download і кращий cache hit rate між сторінками.
  • Коротші імена. CSS Modules у production отримують короткі class names (dev лишає довгі – для дебага), а export mangling скорочує внутрішні імена експортів, які зшивають модулі.

Це класичні «безкоштовні» відсотки: нічого не треба міняти в коді, бандл просто меншає після апгрейда.

Bundle Analyzer стає інструментом, а не картинкою

Найцікавіша для мене частина релізу. Analyzer тепер:

  • показує найбільші клієнтські роути на окремій головній сторінці;
  • має табличний вигляд із сортуванням за найбільшими contributors;
  • автоматично зберігає снапшоти аналізів і вміє diff-ити бандли між змінами;
  • відрізняє модулі на critical render path і дозволяє відфільтрувати async-залежності.

Останній пункт – головний. «Наш bundle 2 MB» – слабка метрика: туди входить і те, що вантажиться асинхронно після interaction, і те, що реально блокує перший рендер. Питання, яке варто ставити: скільки байтів лежить на критичному шляху до першої взаємодії? Це прямий місток до LCP/INP, які ми розбирали в статті про Core Web Vitals.

Практичний workflow тепер виглядає так:

  1. Зроби снапшот аналізу до зміни.
  2. Внеси зміну (нова бібліотека, рефакторинг, новий роут).
  3. Прожени аналіз ще раз і подивись diff.
  4. Окремо перевір critical path: чи виросло те, що блокує рендер, чи лише async-хвіст.
  5. Підтверди field-метриками у production – лабораторний аналіз без польових даних бреше рівно настільки, наскільки йому зручно.

Бонус для тих, хто працює з агентами: поруч із analyzer-ом з'явився experimental agent skill next-bundle-optimizer (npx skills add vercel/next.js --skill next-bundle-optimizer) – агент сам проходить цикл «знайти → діагностувати → запропонувати зменшення». Це той самий патерн «пакуємо процес, а не промпт», який ми розбирали на прикладі Claude Code.

Пастки bundle-оптимізації

  • Оптимізувати bundle без снапшота «до». Без базової лінії ти не знаєш, чи допомогла зміна. Тепер снапшоти вбудовані – немає виправдань.
  • Дивитись на сумарний розмір замість critical path. Перенесення коду в async-чанк не «зменшило bundle», воно перенесло вартість у інший момент. Іноді це правильно, але це інше рішення.
  • Вмикати всі experimental-прапорці одразу. turbopackGc, lazy dynamic imports, worker threads – вмикай по одному і дивись на поведінку. Experimental означає experimental.
  • Міряти лише в dev. Коротші class names і export mangling працюють у production; висновки про розмір роби з production-білда.

Спробуй diff на своєму бандлі

Онови тестову гілку до 16.4 і зроби три речі: (1) зафіксуй розмір .next/cache до і після – побачиш свої 20–25%; (2) відкрий Bundle Analyzer, знайди найбільший клієнтський роут і подивись, що з нього лежить на critical path; (3) зроби снапшот, додай будь-яку «невинну» бібліотеку, яку давно хотів, і подивись diff. Третій крок зазвичай найповчальніший.

Хороша інженерна культура навколо бандла – це не разовий «тиждень оптимізації», а дешевий постійний цикл: снапшот, diff, рішення. У 16.4 цей цикл нарешті вбудований в інструмент. Як це поєднується з рештою моделі 16.4 – у розборі Cache Components.