Next.js использует Webpack как основной сборщик (в классических
версиях до Turbopack по умолчанию), при этом предоставляет
контролируемый способ расширения и изменения конфигурации через файл
next.config.js. В отличие от «чистого» Webpack-проекта, где
вся конфигурация пишется вручную, здесь базовая сборка уже
преднастроена, а разработчику доступен слой расширения, который
позволяет точечно вмешиваться в процесс сборки без потери интеграции с
SSR, маршрутизацией и оптимизациями фреймворка.
Кастомизация Webpack в Next.js строится вокруг функции
webpack, которая возвращает изменённый конфигурационный
объект. Эта функция вызывается на этапе сборки и получает доступ к
исходной конфигурации, а также к контекстной информации о режиме
выполнения.
Основная точка расширения находится в
next.config.js:
module.exports = {
webpack: (config, options) => {
return config;
}
};
Первый аргумент config — это готовая конфигурация
Webpack, сформированная Next.js. Второй аргумент options
содержит служебную информацию:
isServer — выполняется ли сборка на сервереbuildId — уникальный идентификатор сборкиdev — режим разработкиdefaultLoaders — встроенные загрузчики Next.js
(например, для Babel/SWC)Именно через эти параметры принимаются решения о том, какие изменения применять к конфигурации.
Конфигурационный файл Next.js может содержать не только Webpack-логику, но и другие настройки:
module.exports = {
reactStrictMode: true,
swcMinify: true,
webpack: (config, options) => {
return config;
}
};
Webpack-функция является частью общей конфигурации и не существует отдельно от фреймворка. Это важно, потому что любые изменения должны учитывать SSR, разделение клиентского и серверного бандлов и внутренние оптимизации Next.js.
Наиболее распространённый сценарий — изменение уже существующего
config. Типичный подход заключается в аккуратном
расширении, а не в полной замене.
module.exports = {
webpack: (config) => {
config.mode = 'development';
return config;
}
};
Однако прямое переопределение свойств используется редко. Чаще применяются точечные изменения:
resolve.aliasrulesОдной из самых частых задач является настройка алиасов для упрощения импортов.
const path = require('path');
module.exports = {
webpack: (config) => {
config.resolve.alias['@'] = path.resolve(__dirname);
config.resolve.alias['components'] = path.resolve(__dirname, 'components');
return config;
}
};
После этого импорт:
import Button from 'components/Button';
становится альтернативой относительным путям с множеством
../.
Важно учитывать, что Next.js уже имеет встроенные резолверы для
js, ts, tsx, а также поддержку
абсолютных импортов через baseUrl в
jsconfig.json или tsconfig.json.
Webpack-алиасы используются, когда требуется более сложная или
динамическая логика.
Webpack в Next.js уже включает стандартные лоадеры для JavaScript и TypeScript, но иногда требуется обработка нестандартных файлов: YAML, Markdown, SVG как компонентов или кастомных форматов.
Пример добавления загрузчика для Markdown:
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.md$/,
use: 'raw-loader'
});
return config;
}
};
Другой пример — обработка SVG как React-компонентов:
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack']
});
return config;
}
};
При добавлении loaders важно учитывать порядок правил. Next.js уже включает обработку изображений, CSS и TypeScript, поэтому конфликтующие правила могут нарушить сборку.
Webpack-плагины позволяют расширять процесс сборки: оптимизация, генерация файлов, анализ бандла.
Пример подключения Bundle Analyzer:
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
webpack: (config, { dev }) => {
if (!dev) {
config.plugins.push(new BundleAnalyzerPlugin());
}
return config;
}
};
Плагины подключаются через массив config.plugins.
Next.js уже использует множество внутренних плагинов для SSR, code
splitting и оптимизации изображений, поэтому добавление новых должно
быть аккуратным.
Одно из ключевых различий Next.js по сравнению с обычным Webpack-проектом — разделение сборки на server и client bundles.
module.exports = {
webpack: (config, { isServer }) => {
if (isServer) {
config.externals = [...config.externals];
} else {
config.resolve.fallback = {
fs: false
};
}
return config;
}
};
Серверная сборка может включать Node.js-модули, тогда как клиентская не имеет доступа к системным API. Поэтому часто требуется:
fs, path,
netWebpack-конфигурация в Next.js позволяет вмешиваться в оптимизацию:
module.exports = {
webpack: (config) => {
config.optimization.splitChunks = {
chunks: 'all'
};
return config;
}
};
Также можно контролировать:
Next.js уже применяет агрессивные оптимизации, включая автоматическое code splitting по страницам, поэтому ручная настройка требуется только в специфических случаях.
Хотя Next.js имеет встроенную поддержку CSS, Sass и CSS Modules, Webpack-расширения иногда используются для:
Пример добавления PostCSS loader:
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
});
return config;
}
};
Однако в Next.js прямое переопределение CSS pipeline может
конфликтовать с встроенной системой. Более корректный путь — расширение
через postcss.config.js.
Next.js использует встроенный Image Optimization, но Webpack всё равно участвует в обработке ассетов.
Добавление кастомных правил:
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.(png|jpg|gif)$/,
type: 'asset/resource'
});
return config;
}
};
При этом важно не конфликтовать с next/image, который
использует собственный pipeline.
Webpack в Next.js автоматически прокидывает process.env,
но кастомные переменные могут быть добавлены через DefinePlugin:
const webpack = require('webpack');
module.exports = {
webpack: (config) => {
config.plugins.push(
new webpack.DefinePlugin({
'process.env.CUSTOM_VAR': JSON.stringify('value')
})
);
return config;
}
};
Next.js также поддерживает .env.local,
.env.production и .env.development, что делает
прямое использование DefinePlugin менее необходимым, но он остаётся
полезным для сложных случаев.
При модификации конфигурации часто возникают проблемы, связанные с нарушением внутренних соглашений Next.js:
Особенно критично вмешательство в babel/swc
pipeline, так как Next.js постепенно уходит от Babel в сторону SWC.
Webpack-функция вызывается каждый раз при сборке, и её результат
должен быть детерминированным. Любые условия внутри функции должны
опираться на options, а не на внешнее состояние.
module.exports = {
webpack: (config, { dev, isServer }) => {
if (dev && !isServer) {
config.devtool = 'source-map';
}
return config;
}
};
Такой подход позволяет поддерживать разные конфигурации для development и production без разделения конфигов.
Конфигурация Webpack в Next.js часто строится как последовательность изменений:
module.exports = {
webpack: (config, options) => {
config.resolve.alias['@shared'] = path.resolve(__dirname, 'shared');
config.module.rules.push(customRule);
config.plugins.push(customPlugin);
config.optimization = {
...config.optimization,
minimize: !options.dev
};
return config;
}
};
Такой подход позволяет постепенно наращивать функциональность, не разрушая базовую структуру сборки Next.js.
В монорепозиториях Webpack-конфигурация Next.js часто требует:
resolve.modulesmodule.exports = {
webpack: (config) => {
config.resolve.symlinks = false;
return config;
}
};
Это особенно актуально при использовании Yarn Workspaces или pnpm,
где структура node_modules отличается от стандартной.
Хотя Next.js активно внедряет альтернативный сборщик (Turbopack), Webpack остаётся важным компонентом экосистемы. Большинство проектов по-прежнему используют именно его, особенно в production-сценариях, где важна предсказуемость и совместимость с существующими плагинами.
Кастомизация через next.config.js остаётся основным
способом адаптации сборки под специфические требования проекта, сохраняя
баланс между контролем и автоматизацией.