Откройте вкладку «Сеть» (Network) на этой странице, и вы увидите список JavaScript-файлов с, казалось бы, случайными именами:
Сеть
Нажмите на один из них, отмените минификацию, и вы увидите что-то вроде этого:
Это и есть чанк. Turbopack генерирует десятки таких файлов для вашего приложения на Next.js, содержащих ваш собственный код, пакеты, от которых вы зависите, и среду выполнения (runtime), которая связывает всё это воедино.
«Чанкинг» (chunking) — это процесс определения того, какой код в какой чанк попадет. Существует множество способов разбиения на чанки, каждый из которых имеет свои компромиссы.
Начнем с самого простого подхода: один чанк, содержащий весь JavaScript, который использует ваше приложение.
Все 355 модулей в одном чанке, загружаемые на каждой странице.
app.js
Теперь этот чанк загружается для каждой страницы. А поскольку все страницы используют один и тот же чанк, каждая загрузка после первой будет происходить из кэша. Отлично. Навигация будет быстрой.
Однако вы, вероятно, уже заметили подвох. Загрузка страницы, где почти нет JavaScript, всё равно означает загрузку JavaScript для всех остальных страниц. Увы. По мере роста вашего сайта каждая загрузка страницы становится всё тяжелее и тяжелее. В долгосрочной перспективе это нецелесообразно.
Мы могли бы перейти к модели «один чанк на страницу». Это позволило бы сохранить чанки компактными, и мы никогда не передавали бы лишний код. Идеально!
Один чанк на страницу. Те же 355 модулей разбиты на 8 чанков, при этом всё, что используется на разных страницах, дублируется в каждом чанке, которому это необходимо.
/docs
/learn
/blog
/showcase
/team
/governance
/telemetry
Но тогда мы потеряем кэширование. Если каждая страница использует <Footer />, его код окажется внутри чанка каждой страницы. Посетитель, открывающий четыре страницы, скачивает один и тот же код футера четыре раза.
Поэтому нам нужно что-то более детализированное. Что, если каждый модуль получит свой собственный чанк? Ничего лишнего никогда не будет передаваться, а модуль, используемый на разных страницах, будет скачиваться только один раз.
Один чанк на модуль: 355 отдельных чанков, каждый передается один раз, каждый — отдельный сетевой запрос.
Отлично, но есть одна небольшая проблема: это сотни сетевых запросов для крошечных JavaScript-файлов. Каждый из них несет накладные расходы, и сотни таких запросов замедлят работу сайта. HTTP/2 сделал запросы дешевле, но каждый запрос по-прежнему имеет накладные расходы. Алгоритмы сжатия (например, gzip) также работают хуже с большим количеством маленьких файлов, потому что они могут находить повторяющиеся паттерны только внутри одного файла. Словари сжатия помогают, но не решают эту проблему.
Меньше запросов или меньше кода
Итак, перед нами стоит задача: мы хотим минимально возможный размер загрузки и минимальное количество запросов. К сожалению, эти две цели противоречат друг другу. Меньшее количество более крупных чанков означает меньше запросов, но чем они больше, тем менее они пригодны для повторного использования на разных страницах.
Решение Turbopack заключается в объединении мелких чанков в более крупные. Самое сложное — решить, какие именно и когда объединение действительно выгодно.
Для этого нам нужно ввести новый термин — группа чанков (chunk group). Группа чанков — это набор чанков, которые загружаются вместе. Например, все чанки, используемые для /home, принадлежат к одной группе чанков, а для /blog — к другой:
Две группы чанков, по одной на маршрут. Группа для `/home` загружает три чанка; группа для `/blog` загружает два. Один чанк принадлежит обеим группам, поэтому его объединение с соседом безопасно только внутри группы.
Группы чанков решают проблему избыточной передачи кода. Turbopack объединяет только чанки из одной группы, а чанки в группе в любом случае всегда загружаются вместе, поэтому их объединение не может добавить ничего такого, что страница еще не скачивала.
Но как оптимизировать кэширование, не перегружая браузер запросами?
Чтобы разобраться в этом, представьте два типа посещений: когда вы заходите на страницу и уходите, и когда вы заходите на страницу, а затем переходите на другую. Тот же анализ естественным образом распространяется на сессии с тремя и более посещениями страниц.
Для этих случаев давайте разберем стоимость или выгоду от объединения чанков для <Footer /> и <VideoPlayer />. Назовем чанк <Footer /> — A, а чанк <VideoPlayer /> — B.
A используется на каждой странице, а B — только на главной. Оба они находятся в группе чанков главной страницы, поэтому являются кандидатами на объединение.
Если посетитель загружает главную страницу и уходит, объединение всегда выигрывает. Это один запрос вместо двух, и оба чанка в любом случае были нужны.
Если они загружают вторую страницу, всё зависит от того, нужны ли этой странице оба чанка или только один. Допустим, они переходят с главной страницы на страницу юридической информации. Странице юридической информации нужен A, но не B. Поскольку A и B были объединены в один файл, объединенный файл там бесполезен, поэтому браузер снова скачивает A отдельно. Посетитель теперь скачал A дважды.
Объединение окупается при навигации только тогда, когда обеим страницам нужны оба чанка. Тогда объединенный файл используется повторно, и вы сэкономили запрос.
Вот другие возможные сценарии навигации. Каждая строка ниже — это сессия из двух страниц, записанная как «что нужно первой странице», затем «что нужно второй». Цифры показывают, как объединение A и B меняет общее количество запросов и объем загруженного кода за эту сессию по сравнению с их разделением.
При рассмотрении объединения чанков A и B мы рассчитываем количество групп чанков, которые используют только A, только B и используют A + B. Мы взвешиваем стоимость или выгоду от объединения в зависимости от вероятности каждого сценария.
Наконец, мы взвешиваем случаи посещения одной страницы и посещения двух страниц по их вероятностям. Мы оцениваем, что 2/3 сессий — это одна страница, а 1/3 включает две или более.
Три стратегии чанкинга
Вот сравнение версий чанкинга, которые мы обсудили.
Чтобы получить цифры для каждой, я посетил nextjs.org с тремя различными конфигурациями чанкинга Turbopack: никогда не объединять, настройки по умолчанию и объединять всё внутри каждой группы чанков.
Для каждой версии я выполнил одну и ту же серию переходов, измеряя количество запросов и объем загруженного JavaScript на стороне клиента.
Как видите, чанкинг — это балансировка. На nextjs.org настройки по умолчанию сократили количество запросов более чем вдвое по сравнению с отсутствием объединения, при этом передавая немного меньше кода; максимальное объединение сократило количество запросов еще сильнее, но передало на 10% больше кода в целом. Однако, если бы мы совершали меньше переходов, максимальное объединение было бы более оптимальным. Объединение каждого чанка в группе в один большой чанк давало преимущества при начальной загрузке страницы, но было затратным в долгосрочной перспективе.
Новые функции чанкинга в Next.js 16.3
Две вещи ограничивают эффективность чанкинга. Решение об объединении принимается во время сборки, до того как кто-либо посетит сайт, поэтому оно не может реагировать на то, что уже кэшировано в браузере. А алгоритм должен угадывать, как люди перемещаются по вашему сайту, поэтому посещения одной страницы имеют вес 2/3. Этим летом, в рамках моей стажировки в команде Turbopack, я работал над обоими аспектами, а также над сокращением того, что вообще попадает в чанки.
Более умная загрузка чанков
В таблице выше показана стоимость объединения. Когда посетитель загружает страницу с A, а затем страницу с A + B, объединение заставляет их скачивать A дважды. Сборщик (bundler) не может этого избежать, потому что во время сборки он не знает, что уже есть в браузере. Среда выполнения (runtime) — может.
В Next.js 16.3 или более поздних версиях включение experimental.turbopackChunking.generateComponentChunks в вашем next.config.js заставляет Turbopack создавать неслитые версии чанков наряду со слитыми. Мы отслеживаем, какие элементы составляют каждый слитый чанк и какие из них уже были загружены, поэтому во время запроса мы можем выбрать то, что выгоднее: слитый чанк или только недостающие части.
Это работает и в обратную сторону. Если посетитель уже загрузил слитые A + B, мы можем пропустить повторную загрузку A отдельно.
Таким образом, при мягкой навигации загружается меньше ненужного кода, и мы получаем преимущества объединения без затрат на навигацию.
Взгляните на это демо-приложение, чтобы увидеть разницу:
generateComponentChunks: false
generateComponentChunks: true
Мы также экспериментируем с директивой only-if-cached, которая позволяет нам проверять, что кэшировано у посетителя при его прибытии. Это распространило бы те же улучшения на людей, которые покидают сайт и возвращаются позже.
Чанкинг на основе аналитики
Вы могли заметить, что мы делаем много предположений о веб-сайтах в нашем алгоритме чанкинга. Например, вес 2/3 — это догадка. Это разумное значение по умолчанию для многих сайтов, но не обязательно подходящее для конкретного сайта. Если вы знаете, как люди на самом деле перемещаются по вашему сайту, вы можете сообщить нам об этом.
В вашем next.config.js теперь можно настроить эти параметры в разделе experimental.turbopackChunking:
- firstPageLoadPriority: смещает вес между случаями одной страницы и двух страниц. Более высокое значение (от 0 до 1) отдает приоритет быстрой начальной загрузке, потенциально ценой навигации. Показатель отказов — разумное начальное значение. По умолчанию мы используем 0.67.
- priorityRoutes: список страниц, скорость загрузки которых наиболее важна. Мы будем объединять чанки на этих маршрутах более активно.
- clusters: группы маршрутов, которые часто посещаются вместе, каждый из которых определяется массивом регулярных выражений. Мы будем охотнее объединять перекрывающиеся чанки внутри кластера. Но если в кластере смешиваются страницы, использующие только A или только B, со страницами, использующими и то, и другое, мы будем объединять меньше.
Меньше чанков и меньшего размера
Все вышеперечисленное касается того, как группировать код. Этот последний набор функций касается того, как изначально отправлять его меньше. Я работал над следующими функциями для поддержки этого:
- Tree-shaking для CJS-модулей: Раньше мы поддерживали это только для ESM, поэтому неиспользуемые импорты и экспорты в CJS-модулях отправлялись клиенту. Включите это с помощью experimental.turbopackCjsTreeShaking. В будущей версии Next.js это будет включено по умолчанию. Я также расширил набор ESM-модулей, которые мы можем анализировать и подвергать tree-shaking. Это включает улучшение поддержки barrel-файлов в динамических импортах.
- Я также расширил набор ESM-модулей, которые мы можем анализировать и подвергать tree-shaking. Это включает улучшение поддержки barrel-файлов в динамических импортах.
- Общая среда выполнения Turbopack: один чанк среды выполнения теперь заменяет чанки для каждой страницы. Включите его с помощью experimental.turbopackSharedRuntime. Это экономит один блокирующий запрос и около 10 КБ JavaScript на стороне клиента при каждой навигации после первой. Позже это также будет включено по умолчанию.
- Более легкая среда выполнения по умолчанию: среда выполнения больше не поставляет код WebAssembly и Web Worker по умолчанию. Мы обнаруживаем, когда вы используете эти модули, и вставляем код загрузки в этот момент.
Попробуйте
Попробуйте эти новые функции в Next.js версии 16.3 или выше. А если вам интересно узнать больше о чанкинге (включая CSS-чанкинг), посмотрите недавнее выступление Тобиаса о компромиссах и ограничениях чанкинга.









