This episode covers Gatsby performance optimization: analyzing bundle size and page speed, preloading and code splitting, lazy loading with loadable-components, and improving Core Web Vitals and Lighthouse scores.

Gatsby is fast by default, but "already fast" is no reason to stop. Performance optimization is an ongoing process: measure, find the bottleneck, then fix them one at a time.
Episode 15 covers bundle and page speed analysis, preloading and code splitting, lazy loading, build optimization plugins, and improving Core Web Vitals and Lighthouse scores.
Lighthouse is Chrome's built-in audit tool that scores performance, accessibility, and SEO. Run it against a local production build:
npm run build
npx lighthouse http://localhost:9000 --only-categories=performanceThe npx lighthouse command needs a running server; use gatsby serve, which listens on port 9000 after the build finishes.
Large JavaScript bundles slow down interaction time. Analyze each package's contribution with a bundle analyzer. The output shows a size map per dependency, so you know which packages should be lazy-loaded.
Media-rich pages can benefit from preloading: fonts, hero images, and critical scripts are marked so the browser loads them earlier. The Gatsby Head API can render preload links for key assets.
Heavy components — charts, editors, large widgets — should only load when needed. Use @loadable/component:
npm install @loadable/component gatsby-plugin-loadable-componentsThen lazy-load the component in a file:
import loadable from "@loadable/component"
const ChartWidget = loadable(() => import("../components/ChartWidget"))
const Dashboard = () => (
<section>
<ChartWidget />
</section>
)loadable(() => import(...)) splits ChartWidget into a separate bundle that only loads when the component renders. This reduces the initial JavaScript that has to be downloaded.
Gatsby already splits code per page automatically. Your job is to make sure heavy components aren't statically imported on main pages, but loaded via lazy loading instead.
gatsby-plugin-perf-budgets fails the build if a bundle exceeds the defined limits:
module.exports = {
plugins: [
{
resolve: "gatsby-plugin-perf-budgets",
options: {
budgets: [
{
path: "/",
size: 250,
gzip: true,
},
],
},
},
],
}The size: 250 option means the main page's JavaScript must not exceed 250KB (depending on the plugin's configuration units). Budgets turn performance into a contract, not just good intentions.
Make sure every image uses gatsby-plugin-image (episode 6) so formats and sizes are automatically optimal. For fonts, avoid loading several weights at once; use subsets and font-display: swap so text doesn't stay invisible while fonts load.
LCP improves with optimized images and preloading. INP improves with smaller JavaScript and lazy loading. CLS improves by giving images and media explicit sizes, plus CSS aspect-ratio. Re-run the audit after every major change to make sure there's no regression.
Key takeaways:
gatsby serve on port 9000 to test a local production build.@loadable/component.gatsby-plugin-perf-budgets enforces performance as a build contract.In the next episode, episode 16, we'll discuss testing and quality — unit testing with Jest and React Testing Library, integration testing for Gatsby pages, accessibility testing and SEO audits, and static analysis with ESLint.