Главная / Статьи

Настройка новых функций разбиения на части в Turbopack в Next.js 16.3

Практический обзор новой настройки turbopackChunking в Next.js 16.3 с объяснением того, как maxChunkCountPerGroup и generateComponentChunks влияют на размер пакета и кэширование.

6 мин чтения1482 слов

Было 4:40 пятницы — именно тот момент, когда вы закрываете ноутбук перед выходными, — когда обычная рутинная развертка приложения-дашборда внезапно перестала быть рутинной. Сборка, которая обычно занимала 40 секунд, теперь требовала чуть более трех минут, а размер пакета клиента для основного маршрута увеличился почти на 300 кБ. Ни один маршрут не менялся, и никаких новых зависимостей, которые могли бы это объяснить, не добавлялось. Единственным изменением той недели было незначительное обновление версии Next.js. В итоге пришлось провести вечер, уставившись на вкладку «Сеть» и наблюдая, как четыре огромных файла JavaScript загружаются один за другим, в то время как раньше для этой работы хватало дюжины маленьких файлов.

Причиной была не ошибка. Это просто логика разбиения на чанки в Turbopack работала ровно так, как было запланировано, только не в том виде, который ожидали.

Почему проблема с разбиением на чанки так и не была решена

Эта проблема существует уже около десяти лет, и разработчики инструментов для сборки кода постоянно пытаются её решить. В Webpack функция SplitChunksPlugin предоставляла такие настройки, как minSize, maxSize и cacheGroups, которые почти никто не понимал в глубине, — их в основном скопировали из какого-то поста на Stack Overflow, датируемого 2019 годом. Основной компромисс остаётся прежним: объединение кода в меньшее количество более крупных частей сокращает количество HTTP-запросов, но ухудшает работу кэша, поскольку одна изменение в CSS может сделать бесполезным целый пакет размером 500 кБ. Разделение кода на множество мелких частей улучшает детализацию кэширования, но увеличивает количество запросов, что быстро становится заметным при медленном соединении.

Turbopack унаследовал ту же необходимость балансировки, и до недавнего времени он склонялся к консолидации. Это разумный выбор для одностраничных сессий, где большинство ресурсов всё равно загружается один раз. Однако это мешает при частом навигировании по крупному приложению, поскольку ранее сохранённый в кэше код продолжает перезагружаться вместе с новым кодом, находящимся рядом с ним.

Что изменилось в 16.3

В Next.js 16.3 поведение слияния больше не скрыто за единой непрозрачной гипотезой — теперь оно регулируется непосредственно через настройки. Был добавлен новый блок turbopackChunking, а двумя наиболее важными параметрами являются maxChunkCountPerGroup и maxMergeChunkSize.

// next.config.js
module.exports = {
  experimental: {
    turbopackChunking: {
      maxChunkCountPerGroup: 40, // default
      maxMergeChunkSize: 200_000, // bytes, default
    },
  },
};

Понижение значения maxChunkCountPerGroup заставляет Turbopack использовать более агрессивный подход к объединению кода: количество запросов при загрузке страницы сокращается, но способность кэша сохранять данные между переходами ухудшается. Повышение этого значения приводит к формированию меньших по размеру, но более многочисленных фрагментов кода, которые остаются нетронутыми даже при изменении не связанных между собой частей приложения. maxMergeChunkSize определяет максимальный размер объединяемого фрагмента, после которого Turbopack прекращает включение дополнительного кода в него, что полезно, когда одна крупная зависимость от поставщика склонна поглощать всё вокруг себя.

Ранее ни один из этих параметров не существовал. Приходилось просто использовать значение по умолчанию и находить способы с этим справиться.

Опция разбиения на компоненты

Более значимым дополнением является параметр generateComponentChunks, который распространяет возможности разбиения кода Turbopack на отдельные компоненты, а не ограничивается границами маршрутов.

module.exports = {
  experimental: {
    turbopackChunking: {
      generateComponentChunks: true,
    },
  },
};

Включение этой опции заставляет Turbopack выводить как объединённые, так и необъединённые версии чанков, позволяя среде выполнения во время загрузки решить, какую версию получить, исходя из того, что уже сохранено в кэше браузером. Если общий компонент был сохранён в кэше при предыдущем посещении страницы, среда выполнения может запросить меньший необъединённый чанк вместо загрузки объединённого пакета, содержащего дублирующийся код. Это принципиально отличный подход от попытки заранее рассчитать идеальный размер чанка — это первый случай, когда инструмент для сборки кода откладывает принятие решения на момент запроса вместо того, чтобы фиксировать его на этапе сборки.

Что мы действительно увидели

Команда, тестирующая это в приложении типа дашборд, включила функцию generateComponentChunks и наблюдала за происходящим в течение следующих дней. При первой загрузке страницы практически не произошло изменений — размер сократился примерно на 2%. Однако при многократных переходах, которые составляют основу реального использования приложения вроде дашборда, средний объем передаваемого JS на каждый переход сократился примерно на 35%. В этом и заключается основная ценность данного решения: это не функция, предназначенная для ускорения первой загрузки, а инструмент, рассчитанный на ситуацию, когда пользователь постоянно навигирует в приложении в течение десяти минут подряд. Большинство тестов размера пакетов, распространяемых в Интернете, оценивают совершенно другую ситуацию, которая не отражает реального способа использования приложений с интенсивной внутренней навигацией.

Та же команда также пересмотрела параметр maxChunkCountPerGroup для маркетингового сайта, находящегося в том же monorepo, снизив его значение с 40 до 24. На этом сайте преобладают сессии с одной страницей, поэтому дополнительные сетевые запросы, возникающие из-за более дробного разделения данных, представляли собой чистую нагрузку без какой-либо пользы. Один и тот же инструмент, противоположная настройка — и оба варианта были правильными для своих контекстов. В этом, пожалуй, и заключается главный урок: не существует единственной универсально правильной стратегии разделения данных, Turbopack просто делал этот выбор за вас без вашего участия.

Как это сравнивается со старыми настройками Webpack

Для тех, кто годами боролся с параметром splitChunks.cacheGroups, наиболее близким аналогом в этой новой системе является maxMergeChunkSize; в отличие от многих других названий в Webpack, этот параметр действует именно так, как и должен. В конфигурации Webpack существовало десяток слабо задокументированных настроек, взаимодействовавших друг с другом таким образом, что никто не мог полностью это понять, и многие команды оказывались с конфигурационными файлами, оставленными ушедшими инженерами, которые все боялись изменять. Подход Turbopack преднамеренно более ограничен: вместо десятка настроек здесь есть всего два-три, что жертвует некоторой гибкостью ради решения действительно редких крайних случаев, но в то же время сокращает время от момента замечания, что «наш бандл выглядит не так», до момента понимания, какое именно число нужно изменить. Такой компромисс является разумным, особенно для тех, кто предпочитает не сталкиваться с сложностями.

Нам больше никогда не придётся иметь дело с такой многолетней конфигурацией cacheGroups.

Есть ещё одно отличие, которое стоит выделить отдельно. Webpack в значительной степени заставлял разработчиков думать о разбиении кода во время сборки, внимательно изучая файл stats.json и пытаясь предсказать, что на самом деле будет храниться в кэше браузера реального посетителя. Разбиение кода на уровне компонентов в Turbopack переносит часть этой работы на момент запроса, когда движок может проверить реальное состояние кэша вместо того, чтобы предполагать его заранее. Это настоящий архитектурный сдвиг, а не просто переименование флага конфигурации.

Недостатки

Эта функция по-прежнему отмечена как экспериментальная, и в документации прямо об этом говорится. Одна команда столкнулась с ситуацией, когда динамически импортируемая библиотека для графиков была разделена таким образом, что во время развертывания временно возникла ошибка «не удалось загрузить чанк» в продакшене — это та самая проблема устаревших ссылок на чанки, которая влияет практически на все инструменты для сборки с поддержкой разделения кода. Полная перезагрузка страницы решила проблему для тех пользователей, у кого она возникла, но это служит полезным напоминанием о том, что более детальное разделение кода увеличивает вероятность возникновения именно этого типа сбоев, а не уменьшает её.

Ещё есть проблема настройки этих параметров без реальных данных о трафике, что по сути является угадыванием. Стоит сдерживать желание менять стандартные настройки в новом проекте, пока у вас не появятся реальные аналитические данные о навигации, показывающие, склонны ли ваши посетители совершать только одну операцию или же переходить между десятком и более маршрутов за одну сессию. Если таких данных ещё нет, это отличная причина начать их сбор до изменения любых настроек группировки. Настройка «слепо» несёт реальный риск снижения скорости обращений к кэшу по сравнению со стандартными настройками, и такое ухудшение происходит незаметно. Никто не подаёт отчёт об ошибке с заголовком «Навигация казалась немного медленнее», они просто перестают возвращаться.

Так стоит это включать?

В сценарии панели управления ответ был положительным, и настройка осталась включенной. Что касается маркетингового сайта, было скорректировано лишь одно число, и на этом всё закончилось. Ничто из этого не может исправить фундаментально сломанное приложение, и если ваш пакет увеличился из-за зависимости, которая вам на самом деле не нужна, ни одна стратегия разбиения на части не поможет избавиться от этой ошибки. Эта функция предлагает целенаправленное решение узкой проблемы, что является свежим отступлением от многих функций инструментов сборки, выпущенных в этом году; такое мнение имеет вес, особенно у тех, кто потратил больше вечеров, чем хотелось бы, на устранение именно таких ошибок.