# Gatsby Webpack Configuration: A Complete Guide to Customizing Your Build Pipeline

> Master Gatsby Webpack configuration for custom builds. Learn how to modify loaders, plugins, and optimization settings using the onCreateWebpackConfig API for your unique project needs.

- Repository: [Gatsby/gatsby](https://github.com/gatsbyjs/gatsby)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Gatsby generates distinct webpack configurations for four build stages—`develop`, `develop-html`, `build-javascript`, and `build-html`—and exposes the `onCreateWebpackConfig` API in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) to customize loaders, plugins, and optimization settings without ejecting.**

Gatsby's build system relies heavily on webpack to bundle JavaScript, CSS, and assets across multiple build stages. Understanding how Gatsby webpack configuration works is essential for optimizing bundle sizes, adding custom loaders, or integrating specialized build tools. This guide examines the core architecture in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) and demonstrates how to safely customize your build pipeline using the public Node API.

## The Four Build Stages

Gatsby constructs separate webpack configurations for four distinct stages. Each stage serves a specific purpose in the build lifecycle:

- **`develop`** – Client-side development server with hot reloading and React Fast Refresh.
- **`develop-html`** – Server-side rendering for the development environment.
- **`build-javascript`** – Production client bundle generation with code splitting and optimization.
- **`build-html`** – Static HTML generation for server-side rendering during production builds.

The stage is detected at runtime in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) and determines entry points, output paths, and plugin selection.

## Core Configuration Architecture

Gatsby's webpack configuration is assembled from reusable "atoms" rather than static objects. This modular approach allows the framework to generate stage-specific configs while maintaining consistency.

### webpack.config.js

The main entry point resides in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js). This module exports an async function that orchestrates the entire configuration:

```javascript
module.exports = async (program, directory, suppliedStage, port, { parentSpan } = {}) => {
  const stage = suppliedStage
  const { rules, loaders, plugins } = createWebpackUtils(stage, program)
  
  // Configuration assembly logic
  const publicPath = getPublicPath({ assetPrefix, pathPrefix, ...program })
  
  // Entry, output, module, resolve, and plugin configuration
  // ...
  
  // User customization hook
  await apiRunnerNode(`onCreateWebpackConfig`, {
    getConfig,
    stage,
    rules,
    loaders,
    plugins,
    parentSpan,
  })
  
  return getConfig()
}

```

Key responsibilities include:

- **Stage detection** – Determines build context (development vs. production, client vs. server).
- **Entry point generation** – Creates stage-specific entries (`app` for client, `render-page` for SSR).
- **Output configuration** – Sets paths, filenames, and public paths for assets.
- **Module rules** – Configures loaders for JavaScript, CSS, images, and fonts.
- **Plugin injection** – Adds React Refresh, MiniCssExtract, and optimization plugins.
- **Externals** – Prevents bundling of React and Node.js built-ins during SSR stages.
- **Caching** – Configures filesystem caching keyed by stage.

### webpack-utils.ts

The `createWebpackUtils` function in [`packages/gatsby/src/utils/webpack-utils.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack-utils.ts) returns three namespaces of factory functions:

- **`loaders`** – Factory helpers for individual loaders (e.g., `loaders.js()`, `loaders.css()`).
- **`rules`** – Complete rule definitions (e.g., `rules.js()`, `rules.cssModules()`).
- **`plugins`** – Plugin constructors (e.g., `plugins.extractText()`, `plugins.fastRefresh()`).

These utilities ensure consistency across stages while allowing granular customization. For example, the production flag is derived as `const PRODUCTION = !stage.includes('develop')`, and SSR detection uses `const isSSR = stage.includes('html')`.

### webpack-plugins.ts

Specialized plugin implementations reside in [`packages/gatsby/src/utils/webpack-plugins.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack-plugins.ts). These include custom Gatsby-specific plugins for virtual modules, partial hydration shims, and framework-specific optimizations.

## Customizing Your Webpack Configuration

Gatsby exposes the `onCreateWebpackConfig` API in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) (or plugin files) for safe customization without ejecting. This hook receives the current stage and utility functions.

### Available Actions

Two primary methods modify the configuration:

- **`setWebpackConfig`** – Deep merges a partial configuration into Gatsby's generated config using `webpack-merge`. Preferred for most use cases.
- **`replaceWebpackConfig`** – Replaces the entire configuration object. Use sparingly, as it bypasses Gatsby's defaults.

### Stage-Specific Customization

Always guard customizations by `stage` to avoid breaking server-side rendering:

```javascript
exports.onCreateWebpackConfig = ({ actions, stage }) => {
  // Only apply to client-side builds
  if (stage === "develop" || stage === "build-javascript") {
    // Client-specific configuration
  }
}

```

## Practical Customization Examples

### Adding a Custom Loader

To handle SVG files as React components, add `svg-react-loader` to the client bundle:

```javascript
// gatsby-node.js
exports.onCreateWebpackConfig = ({ actions, stage }) => {
  if (stage === "develop" || stage === "build-javascript") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /\.svg$/,
            use: [
              {
                loader: require.resolve("svg-react-loader"),
              },
            ],
          },
        ],
      },
    })
  }
}

```

*The stage guard ensures this rule only affects the client bundle. Applying it to SSR stages (`build-html`, `develop-html`) would fail because `svg-react-loader` expects a browser environment.*

### Integrating a Bundle Analyzer

Add `webpack-bundle-analyzer` to inspect production chunks:

```javascript
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

exports.onCreateWebpackConfig = ({ actions, stage }) => {
  if (stage === "build-javascript") {
    actions.setWebpackConfig({
      plugins: [new BundleAnalyzerPlugin({ analyzerMode: "static" })],
    })
  }
}

```

### Modifying Existing Rules for SSR

Enable CSS Modules during the `build-html` stage by mutating the existing rule:

```javascript
exports.onCreateWebpackConfig = ({ actions, stage, getConfig }) => {
  if (stage === "build-html") {
    const config = getConfig()
    
    // Locate the CSS Modules rule
    const cssModulesRule = config.module.rules.find(
      r => r.oneOf && r.oneOf.some(inner => inner.test && inner.test.test('.module.css'))
    )
    
    if (cssModulesRule) {
      cssModulesRule.oneOf.forEach(inner => {
        if (inner.test && inner.test.test('.module.css')) {
          inner.use = inner.use || []
          inner.use.push({
            loader: require.resolve("css-loader"),
            options: { modules: true },
          })
        }
      })
    }
    
    actions.replaceWebpackConfig(config)
  }
}

```

> **Tip:** Because Gatsby constructs a complex `module.rules` structure, prefer adding new rules with `setWebpackConfig` rather than mutating Gatsby's internal rule structure. Use `replaceWebpackConfig` only when you need full control over the configuration object.

### Reusing Gatsby's Internal Atoms

Import `createWebpackUtils` to leverage Gatsby's loader factories in your customizations:

```javascript
const { createWebpackUtils } = require("gatsby/src/utils/webpack-utils")

exports.onCreateWebpackConfig = ({ actions, stage, ...args }) => {
  const { loaders, rules } = createWebpackUtils(stage, args.program)

  actions.setWebpackConfig({
    module: {
      rules: [
        {
          ...rules.js(),
          use: [
            loaders.babel({ /* custom Babel options */ }),
            { loader: "my-special-loader" },
          ],
        },
      ],
    },
  })
}

```

*Note: Importing from `gatsby/src/...` works in development but may break in production builds. For maximum compatibility, rely on the public `setWebpackConfig` API.*

## Key Source Files

Understanding the internal architecture helps when debugging complex customizations:

| File | Role |
|------|------|
| [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) | Core configuration generator that orchestrates stage-specific entries, output, and plugins. |
| [`packages/gatsby/src/utils/webpack-utils.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack-utils.ts) | Factory functions for loaders, rules, and plugins used across all stages. |
| [`packages/gatsby/src/utils/webpack-plugins.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack-plugins.ts) | Built-in plugin implementations including React Refresh and MiniCssExtract. |
| [`packages/gatsby/src/utils/get-public-path.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/get-public-path.js) | Computes `publicPath` from `assetPrefix` and `pathPrefix` settings. |
| [`packages/gatsby/src/utils/browserslist.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/browserslist.js) | Provides target browser lists for PostCSS and Babel. |

## Summary

- Gatsby generates **four distinct webpack configurations** for different build stages: `develop`, `develop-html`, `build-javascript`, and `build-html`.
- The core logic resides in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js), which assembles configurations using reusable atoms from [`webpack-utils.ts`](https://github.com/gatsbyjs/gatsby/blob/main/webpack-utils.ts).
- Customize builds via **`onCreateWebpackConfig`** in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) using `setWebpackConfig` for merges or `replaceWebpackConfig` for full control.
- Always **guard customizations by stage** to prevent breaking server-side rendering with browser-specific loaders.
- Reuse Gatsby's internal utilities via `createWebpackUtils` when you need to extend existing loader configurations while maintaining framework consistency.

## Frequently Asked Questions

### How do I add a custom webpack loader to Gatsby?

Use the `onCreateWebpackConfig` API in your [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) file with the `setWebpackConfig` action. Always wrap loader additions in a stage check (`stage === "develop" || stage === "build-javascript"`) to ensure they only run in browser bundles, preventing errors during server-side rendering.

### What is the difference between `setWebpackConfig` and `replaceWebpackConfig`?

`setWebpackConfig` performs a deep merge of your partial configuration with Gatsby's generated config using `webpack-merge`, making it the safer choice for most customizations. `replaceWebpackConfig` completely overwrites the entire configuration object and should only be used when you need full control over every aspect of the webpack setup.

### Which build stages does Gatsby use for webpack?

Gatsby uses four distinct stages: `develop` (client-side development with hot reloading), `develop-html` (server-side rendering for development), `build-javascript` (production client bundle with code splitting), and `build-html` (static HTML generation for production). Each stage receives a tailored webpack configuration optimized for its specific environment.

### How can I modify existing webpack rules without breaking Gatsby's defaults?

Retrieve the current config using `getConfig()`, locate the specific rule by testing its `test` property or structure, modify the rule in place, then use `replaceWebpackConfig` to apply changes. However, for maintainability, prefer adding new rules with `setWebpackConfig` rather than mutating Gatsby's internal rule structure.