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:
- Importing large libraries in
gatsby-browser.js. A singleimport _ from "lodash"at the top of a plugin's browser entry point pulls the entire library into the framework bundle for every page. - UI libraries bundling their own React. Some component libraries ship with React as a dependency, causing duplicate React instances in the final output.
- Heavy plugins in production.
gatsby-plugin-offlinewith default settings can aggressively cache all assets, increasing the initial payload. - 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-v2to 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-preactfor ~30KB savings. - Server-side heavy lifting: Move data-processing imports into
gatsby-node.jsAPIs likesourceNodes. - Set hard limits: Configure
gatsby-plugin-perf-budgetsto fail builds on regression. - Split dynamically: Use
React.lazyfor components that aren't needed on every page. - Prune unused plugins: Wrap non-essential plugins in
process.env.NODE_ENVchecks. - 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.jsbloat every page, while SSR-only plugins ingatsby-node.jsadd zero client weight. - Replacing React with Preact via
gatsby-plugin-preactreduces the framework bundle by approximately 30KB without code changes. - Tools like
gatsby-plugin-webpack-bundle-analyser-v2andgatsby-plugin-perf-budgetsprovide 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →