How Does Hot Reloading Work in Gatsby Development? Webpack HMR, Schema Reloading, and Failure Modes
Gatsby hot reloading combines Webpack Hot Module Replacement for instant JavaScript updates, a schema hot reloader for GraphQL changes, and a Socket.io hard-refresh fallback, though it fails due to compilation errors, missing update files, or system watcher limits.
When you run gatsby develop, the framework provides instant visual feedback through a sophisticated hot reloading system that updates React components, GraphQL schemas, and HTML templates without manual refreshes. This system relies on three integrated layers—Webpack HMR, schema inference watchers, and WebSocket fallbacks—to minimize development friction. Understanding how these mechanisms work in the Gatsby source code is essential for diagnosing cases where live updates stop functioning.
The Architecture of Gatsby Hot Reloading
Gatsby's development server coordinates three distinct systems to provide seamless hot reloading.
Webpack HMR for Client-Side Assets
In packages/gatsby/src/utils/start-server.ts, the development server initializes Webpack Hot Module Replacement (HMR) by mounting webpackHotMiddleware with specific configuration parameters:
webpackHotMiddleware(compiler, {
path: '/__webpack_hmr',
heartbeat: 10_000
})
This establishes the /__webpack_hmr endpoint where the browser polls for JavaScript and CSS updates every ten seconds. The webpack-dev-middleware serves compiled assets from memory rather than disk, ensuring rapid rebuilds when source files change.
When you edit a React component, Webpack recompiles the affected module and emits *.hot-update.json and *.hot-update.js chunks. The client-side HMR runtime downloads these files via the middleware endpoint and applies the changes using React Refresh, preserving component state without a full page reload.
Schema Hot Reloader for GraphQL Updates
Data-driven changes trigger a separate path through packages/gatsby/src/bootstrap/schema-hot-reloader.ts. The bootstrapSchemaHotReloader() function initializes the system by capturing a snapshot of the current GraphQL inference metadata and registering Redux event listeners.
This module listens for two specific events: SET_SCHEMA and API_RUNNING_QUEUE_EMPTY. When file changes occur outside the standard sourcing cycle—such as editing Markdown front-matter—the API_RUNNING_QUEUE_EMPTY event fires, triggering the debounced maybeRebuildSchema function configured with a 1000ms delay.
The maybeRebuildSchema workflow performs three critical operations:
- Comparison: Checks if
inferredTypesChangeddetects structural differences in the GraphQL schema. - Rebuild: Calls
rebuild()to regenerate the schema if changes exist. - Query Execution: Invokes
updateStateAndRunQueries()to re-run page queries against the new schema.
Socket.io Hard-Refresh Fallback
Certain changes cannot apply through HMR. In start-server.ts, Gatsby uses chokidar to watch HTML template files and configuration changes:
chokidar.watch(watchGlobs).on(`change`, async () => {
await createIndexHtml(indexHTMLActivity)
socket?.to(`clients`).emit(`reload`)
})
When these files change, the server emits a "reload" event via Socket.io (managed in packages/gatsby/src/utils/websocket-manager.ts), forcing connected browsers to perform a full page refresh to load the new HTML structure.
Common Hot Reload Failures and Root Causes
Hot reloading in Gatsby can fail silently or explicitly due to several architectural constraints.
Webpack Compilation Errors
Syntax errors, missing imports, or loader misconfigurations prevent Webpack from emitting hot-update files. When compilation fails, the HMR client cannot fetch updates, leaving the browser displaying the stale version or the error overlay. The system requires a successful compilation to generate the *.hot-update.json manifest.
Missing Hot-Update Files
If the development server restarts while the browser awaits an update, the in-memory hot-update chunks disappear. The fallback route in start-server.ts handles this by returning 404 for any *.hot-update.json request:
app.use(/.*\.hot-update\.json$/i, (_, res) => {
res.status(404).end()
})
This forces a hard refresh, but if the socket connection is also disrupted, the browser may hang until manually reloaded.
Socket.io Disconnection
Changes to HTML templates or gatsby-ssr.js rely on the Socket.io channel to trigger full page reloads. Network interruptions, proxy misconfigurations, or custom onCreateDevServer implementations that replace the websocket manager can sever this connection, leaving the browser out of sync with the server state.
Debounce Suppression and Unchanged Metadata
The maybeRebuildSchema function uses a 1-second debounce to batch rapid file changes. If you edit data files multiple times per second, the system coalesces these events. Additionally, if inferredTypesChanged returns false—indicating the schema structure remains identical despite content changes—Gatsby skips the rebuild entirely, which can mask updates where only field values change without type structural changes.
File System Watcher Limits
Large projects may exceed operating system limits for file watchers (e.g., max_user_watches on Linux). When chokidar cannot watch specific source directories, changes to those files go undetected, breaking the hot reload trigger entirely.
Cache Corruption
Stale data in the .cache or public directories can cause the development server to serve outdated assets despite source changes. This manifests as apparent hot reload failures where the browser refreshes but displays old content.
Code Examples: Hot Reloading in Practice
Hot Reloading React Components
Editing a page component triggers the Webpack HMR pipeline:
// src/pages/index.js
import * as React from "react"
export default function Home() {
return <h1>Updated Headline</h1>
}
After saving, Webpack compiles the delta, serves it via the HMR endpoint, and React Refresh swaps the component while preserving state.
Schema Changes via Markdown
Adding a new field to Markdown front-matter activates the schema hot reloader:
---
title: "Post"
category: "Tutorial" # New field
---
Content here.
The file watcher detects the change, maybeRebuildSchema validates that the category field alters the inferred GraphQL type, and Gatsby rebuilds the schema and re-runs queries automatically.
HTML Template Modifications
Changes to src/html.js require a full page reload:
// src/html.js
export default function Html({ body }) {
return (
<html>
<head>
<meta charSet="utf-8" />
<title>Updated Site Title</title>
</head>
<body dangerouslySetInnerHTML={{ __html: body }} />
</html>
)
}
The chokidar watcher notices the file change, regenerates the HTML shell via createIndexHtml, and emits the Socket.io reload event to refresh all connected clients.
Summary
- Webpack HMR handles JavaScript and CSS updates via the
/__webpack_hmrendpoint with a 10-second heartbeat, serving compiled assets from memory. - Schema Hot Reloader monitors data changes through
bootstrapSchemaHotReloader()and debounced rebuilds (1s delay) viamaybeRebuildSchema, checkinginferredTypesChangedto avoid unnecessary work. - Socket.io Fallback provides hard refreshes for HTML template changes when HMR cannot apply updates, implemented in
packages/gatsby/src/utils/websocket-manager.ts. - Common failures include Webpack compilation errors preventing update generation, missing hot-update files after server restarts, socket disconnections breaking the reload channel, debounce logic suppressing rapid schema changes, inference metadata checks skipping validation, and OS file watcher limits preventing change detection.
Frequently Asked Questions
Why does Gatsby hot reloading trigger a full page refresh instead of updating modules?
A full refresh occurs when Webpack encounters a hard dependency boundary it cannot hot-swap, such as changes to the HTML template in src/html.js or gatsby-ssr.js, or when the HMR client cannot locate the *.hot-update.json manifest file. The Socket.io channel explicitly emits a "reload" event for these cases to ensure the browser loads the latest server-rendered markup.
Why aren't my GraphQL schema changes hot reloading when I edit Markdown files?
If maybeRebuildSchema determines that inferredTypesChanged returns false—meaning the structural shape of the schema remains identical despite content updates—it skips the rebuild to optimize performance. Additionally, if your edits occur within the 1000ms debounce window of a previous change, the system batches them and may appear to ignore rapid successive modifications.
What causes the "Cannot find update" error in Gatsby development mode?
This error typically appears when the development server restarts or crashes after Webpack compiles a change but before the browser downloads the hot-update chunks. The fallback route in start-server.ts returns a 404 for missing hot-update.json files, which forces a hard refresh, but network latency or socket disconnection can delay this recovery.
How do I fix hot reloading when it stops working entirely?
First, verify that your terminal shows no Webpack compilation errors blocking the build. Next, clear potential cache corruption by deleting the .cache and public directories, then restart the development server. If using a custom proxy or Docker container, ensure the WebSocket connection for Socket.io remains unblocked, and check that your operating system's file watcher limits (fs.inotify.max_user_watches on Linux) are sufficient for your project size.
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 →