# How to Optimize Gatsby Plugin Configurations for Better Bundle Size and Performance

> Optimize Gatsby plugin configurations to reduce bundle size and boost performance. Understand how browser APIs and client-side code impact your site and learn to streamline your Gatsby build for faster loading.

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

---

**Every Gatsby plugin that exposes browser APIs or ships client-side code increases your JavaScript bundle size, while SSR-only plugins run exclusively during the build process and have zero impact on the client payload.**

Optimizing Gatsby plugin configurations is essential for maintaining fast page loads and Core Web Vitals scores. In the `gatsbyjs/gatsby` repository, the build system distinguishes sharply between browser-bound code and server-side logic, determining exactly what ends up in your final webpack bundles. Understanding this architecture allows you to audit plugin impact, eliminate bloat, and implement targeted optimizations like Preact substitution and SSR-only imports.

## How Plugin Code Ends Up in Your Bundle

Gatsby categorizes plugin code based on which API file it exports. This classification determines whether webpack includes the code in the client-side framework bundle or excludes it entirely.

### Browser APIs vs. SSR APIs

Plugins that export a [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) file or use browser-specific APIs like `onClientEntry` and `onRouteUpdate` are bundled into the **framework bundle** that ships to every visitor. Conversely, code in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) that uses `onPreBootstrap`, `sourceNodes`, or `createPages` executes only in the Node.js process during the build.

According to the source in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) (lines 915–926), Gatsby collects all plugins exposing browser APIs into a flattened list that webpack processes into the framework bundle. Meanwhile, [`packages/gatsby/src/utils/api-runner-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/api-runner-node.js) (line 317) demonstrates how `gatsby-node` APIs are loaded exclusively in the server environment, ensuring they never reach the client.

### The Framework Bundle

The framework bundle is created in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) and contains React, the shared runtime, and any plugin code required by every page. A critical comment at line 662 warns that changes to this configuration must be kept in sync with `gatsby-plugin-preact`, highlighting how tightly coupled the framework bundle is to the React runtime.

Plugins that import from `react-dom/server` or other large libraries inside browser-facing code can inadvertently bloat this shared bundle unless explicitly excluded.

## Common Bundle Bloat Pitfalls

Several configuration mistakes consistently inflate Gatsby bundle sizes:

1. **Importing large libraries in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js)**. A single `import _ from "lodash"` at the top of a plugin's browser entry point pulls the entire library into the framework bundle for every page.
2. **UI libraries bundling their own React**. Some component libraries ship with React as a dependency, causing duplicate React instances in the final output.
3. **Heavy plugins in production**. `gatsby-plugin-offline` with default settings can aggressively cache all assets, increasing the initial payload.
4. **Unused plugins remaining active**. Disabled features often leave their plugins in [`gatsby-config.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-config.js), where they still execute browser APIs and add weight.

## Proven Strategies to Optimize Gatsby Plugin Configurations

### Swap React for Preact

`gatsby-plugin-preact` replaces the React runtime with Preact in the framework bundle, typically saving approximately 30KB gzipped. The plugin achieves this through webpack aliasing in its [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js):

```javascript
// packages/gatsby-plugin-preact/src/gatsby-node.js
exports.onCreateWebpackConfig = ({ actions }) => {
  actions.setWebpackConfig({
    resolve: {
      alias: {
        react: `preact/compat`,
        "react-dom": `preact/compat`,
        "react-dom/test-utils": `preact/test-utils`,
        "react/jsx-runtime": `preact/jsx-runtime`,
      },
    },
  })
}

```

Most Gatsby sites function identically with this substitution, though plugins relying on specific React internals may require testing.

### Audit with Webpack Bundle Analyser

`gatsby-plugin-webpack-bundle-analyser-v2` generates a visual report ([`_bundle.html`](https://github.com/gatsbyjs/gatsby/blob/main/_bundle.html)) showing exactly which plugins and dependencies contribute the most bytes. Add it conditionally to avoid slowing production builds:

```javascript
// gatsby-config.js
module.exports = {
  plugins: [
    !process.env.CI && `gatsby-plugin-webpack-bundle-analyser-v2`,
  ].filter(Boolean),
}

```

This pattern appears in the Gatsby benchmarks at [`benchmarks/gabe-fs-mdx/gatsby-config.js`](https://github.com/gatsbyjs/gatsby/blob/main/benchmarks/gabe-fs-mdx/gatsby-config.js) (line 18), demonstrating its use in real-world performance testing.

### Enforce Performance Budgets

`gatsby-plugin-perf-budgets` (experimental) aborts builds when bundles exceed defined thresholds, preventing regression:

```javascript
// gatsby-config.js
{
  resolve: `gatsby-plugin-perf-budgets`,
  options: {
    maxBundleSize: 500 * 1024, // 500 KB
  },
}

```

### Move Heavy Code to SSR-Only

Libraries needed only for data fetching should be imported inside Node.js APIs rather than at the top level of components. Because [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) runs only during the build, webpack excludes these imports from the client bundle:

```javascript
// gatsby-node.js
exports.sourceNodes = async ({ actions }) => {
  // Lazy-load inside the function, not at top level
  const largeLib = await import("large-data-processing-library")
  const data = await largeLib.fetchData()
  // ...create nodes
}

```

### Lazy-Load Page-Specific Code

Use dynamic imports to split heavy components into separate chunks that load only when needed:

```javascript
import React from "react"

const HeavyChart = React.lazy(() => import("./HeavyChart"))

export default function Dashboard() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <HeavyChart />
    </React.Suspense>
  )
}

```

This keeps the shared framework bundle lean while isolating heavy dependencies to specific routes.

### Disable Unused Plugins in Production

Wrap non-essential plugins in environment checks to prevent them from executing browser code in production:

```javascript
// gatsby-config.js
process.env.NODE_ENV !== `production` && `gatsby-plugin-sitemap`,

```

## How Gatsby Internally Handles Plugin Bundling

Gatsby's build pipeline explicitly separates browser-bound code from server-side logic. The system creates a **flattened plugin list** (`flattenedPlugins`) in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) (lines 915–926), iterating through plugins to identify which ones expose browser APIs.

Only plugins with [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) exports are added to the framework bundle. Meanwhile, [`packages/gatsby/src/utils/api-runner-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/api-runner-node.js) (line 317) demonstrates how `gatsby-node` APIs are loaded exclusively in the Node.js process, ensuring these modules never reach the client.

This architectural split guarantees that build-time data fetching, page creation, and node manipulation remain server-side concerns, while only the minimal necessary runtime code ships to the browser.

## Quick Checklist for Lean Gatsby Plugin Configurations

- **Analyze first**: Run `gatsby-plugin-webpack-bundle-analyser-v2` to establish a baseline.
- **Identify browser bloat**: Check which plugins import large libraries in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js).
- **Swap the runtime**: Replace React with Preact using `gatsby-plugin-preact` for ~30KB savings.
- **Server-side heavy lifting**: Move data-processing imports into [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) APIs like `sourceNodes`.
- **Set hard limits**: Configure `gatsby-plugin-perf-budgets` to fail builds on regression.
- **Split dynamically**: Use `React.lazy` for components that aren't needed on every page.
- **Prune unused plugins**: Wrap non-essential plugins in `process.env.NODE_ENV` checks.
- **Verify changes**: Re-run the bundle analyzer to confirm size reductions.

## Sample Optimized gatsby-config.js

```javascript
// gatsby-config.js
module.exports = {
  plugins: [
    // 1. Swap React for Preact – biggest win for framework bundle
    `gatsby-plugin-preact`,

    // 2. Analyze bundle (only locally / CI)
    !process.env.CI && `gatsby-plugin-webpack-bundle-analyser-v2`,

    // 3. Enforce size budgets (fails build if exceeded)
    {
      resolve: `gatsby-plugin-perf-budgets`,
      options: { maxBundleSize: 500 * 1024 }, // 500 KB
    },

    // 4. Only needed in dev – keep it out of prod bundles
    process.env.NODE_ENV !== `production` && `gatsby-plugin-sitemap`,

    // 5. Example of an SSR‑only plugin (no browser code)
    {
      resolve: `gatsby-source-wordpress`,
      options: { url: `https://example.com/wp-json` },
    },
  ].filter(Boolean),
}

```

## Summary

- **Gatsby plugin configurations** directly control bundle size through the distinction between browser APIs (added to the framework bundle) and Node.js APIs (build-time only).
- Plugins importing heavy dependencies in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) bloat every page, while SSR-only plugins in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) add zero client weight.
- Replacing React with Preact via `gatsby-plugin-preact` reduces the framework bundle by approximately 30KB without code changes.
- Tools like `gatsby-plugin-webpack-bundle-analyser-v2` and `gatsby-plugin-perf-budgets` provide visibility and enforcement to prevent regression.
- Lazy-loading components and conditionally loading plugins based on environment variables keep production bundles minimal.

## Frequently Asked Questions

### How do I know if a Gatsby plugin is increasing my bundle size?

Check whether the plugin exports a [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) file or implements browser APIs like `onClientEntry` or `onRouteUpdate`. These indicate client-side code that webpack bundles into the framework payload. Run `gatsby-plugin-webpack-bundle-analyser-v2` to visualize exactly which plugins contribute the most bytes to your output.

### Can I use Preact with all Gatsby plugins?

Most plugins work seamlessly with `gatsby-plugin-preact` because the plugin aliases `react` and `react-dom` to `preact/compat` in the webpack configuration. However, plugins that depend on specific React internals or experimental features may require testing. The swap reduces the framework bundle by roughly 30KB gzipped according to the source implementation in [`packages/gatsby-plugin-preact/src/gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby-plugin-preact/src/gatsby-node.js).

### What is the difference between gatsby-node and gatsby-browser in terms of performance?

[`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) APIs execute only during the build process within the Node.js environment, meaning any imports or heavy logic defined here never reach the client bundle. Conversely, [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) APIs and any top-level imports in that file are bundled by webpack and shipped to every visitor, directly impacting download and parse times. This separation is enforced internally in [`packages/gatsby/src/utils/webpack.config.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/webpack.config.js) (lines 915–926) and [`packages/gatsby/src/utils/api-runner-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/api-runner-node.js) (line 317).

### How do I analyze my Gatsby bundle size locally?

Install `gatsby-plugin-webpack-bundle-analyser-v2` and add it to your [`gatsby-config.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-config.js) with an environment guard to prevent it from running in production builds. After running `gatsby build`, the plugin generates a [`_bundle.html`](https://github.com/gatsbyjs/gatsby/blob/main/_bundle.html) file in your public directory with an interactive visualization of every chunk, dependency, and plugin contribution. This approach is used in the official Gatsby benchmarks at [`benchmarks/gabe-fs-mdx/gatsby-config.js`](https://github.com/gatsbyjs/gatsby/blob/main/benchmarks/gabe-fs-mdx/gatsby-config.js) (line 18).