Почему мы отключили Turbopack в проекте на Next.js
Недавно мы столкнулись с очень странной проблемой при работе над крупным приложением на Next.js с кастомным сервером и рендерингом Markdown через react-markdown, remark и rehype.
Сначала ошибка выглядела так, будто вообще не имеет отношения к нашему коду:
ReferenceError: boolean is not defined
Stack trace указывал куда-то глубоко внутрь сгенерированных чанков Next.js и property-information — одной из зависимостей, используемых внутри markdown-экосистемы.
Самое интересное заключалось в том, что код приложения был полностью валидным и прекрасно работал в production-сборках и при использовании Webpack.
Симптом
Ошибка появлялась только в режиме разработки, когда Next.js запускался с включённым Turbopack.
Stack trace выглядел примерно так:
allowFullScreen: boolean,
async: boolean,
Сгенерированный чанк ссылался на переменную boolean, но этот символ нигде не импортировался и не определялся.
В результате возникало:
ReferenceError: boolean is not defined
Причём проблема происходила ещё на этапе вычисления модуля — до того, как React вообще начинал рендеринг.
Что делало проблему особенно запутанной
Сначала всё выглядело как:
- проблема TypeScript;
- несовместимость с браузерами;
- или даже проблема транспиляции для Safari.
Мы также экспериментировали с конфигурацией browserslist, потому что проекту требовалась поддержка Safari 13.
Но всё это не имело никакого отношения к настоящей причине.
Одна и та же кодовая база:
- работала с Webpack;
- работала в production;
- падала только в dev-режиме Turbopack.
Это заметно сузило область поиска.
Корень проблемы
После изучения сгенерированных чанков стало понятно, что Turbopack некорректно обрабатывал часть ESM-графа зависимостей внутри markdown-экосистемы.
Проблема проявлялась где-то в цепочке:
react-markdown
-> rehype
-> property-information
Необходимый импорт символа фактически терялся во время бандлинга, из-за чего на выходе генерировался некорректный runtime-код.
Иными словами:
код приложения был исправен, а сломанным оказался сгенерированный клиентский чанк.
Почему это важно
Такие проблемы особенно сложно отлаживать, потому что:
- runtime-ошибка возникает далеко от реального источника;
- сгенерированный чанк сильно трансформирован;
- stack trace указывает во внутренние артефакты сборки.
Если вы используете:
react-markdown;remark;rehype;hast-util-*;- или другие современные библиотеки с тяжёлым использованием ESM,
вы можете столкнуться с похожими проблемами в Turbopack, особенно в сложных проектах или при использовании кастомных серверов.
Исправление
Мы решили явно переключить проект обратно на Webpack.
Для сборки:
- next build
+ next build --webpack
Для кастомного сервера Next.js:
const app = next({
dev,
/**
* Disable Turbopack for Next.js custom server due to runtime
* module resolution issues with the react-markdown/rehype ecosystem.
* Turbopack produced invalid client chunks causing
* ReferenceError: boolean is not defined during module evaluation,
* while the same code worked correctly with Webpack.
*/
webpack: process.env.NEXT_USE_TURBOPACK !== 'true',
})
После этого проблема исчезла сразу.
Итог
Turbopack очень быстрый и перспективный, но он всё ещё активно развивается.
В нашем случае преимущества в скорости оказались менее важны, чем нестабильная обработка модулей в реальном сложном графе зависимостей.
Webpack может быть медленнее, но на данный момент он всё ещё гораздо лучше проверен временем для крупных production-приложений с разнообразной экосистемой зависимостей и требованиями к legacy-совместимости.
Если вы сталкиваетесь со странными runtime-ошибками внутри сгенерированных чанков — особенно при работе с Markdown или ESM-heavy библиотеками — попробуйте переключиться обратно на Webpack, прежде чем потратить несколько часов на отладку собственного кода.