Почему мы отключили 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, прежде чем потратить несколько часов на отладку собственного кода.