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

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 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 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 (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 (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 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. 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, 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:

// 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) showing exactly which plugins and dependencies contribute the most bytes. Add it conditionally to avoid slowing production builds:

// 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 (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:

// 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 runs only during the build, webpack excludes these imports from the client bundle:

// 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:

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:

// 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 (lines 915–926), iterating through plugins to identify which ones expose browser APIs.

Only plugins with gatsby-browser.js exports are added to the framework bundle. Meanwhile, 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.
  • 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 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

// 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 bloat every page, while SSR-only plugins in 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 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.

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

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 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 (lines 915–926) and 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 with an environment guard to prevent it from running in production builds. After running gatsby build, the plugin generates a _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 (line 18).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →