Client-side plugin APIs for handling routing, service workers, rendering customization, and user interactions through the gatsby-browser.js file.
Main interface for client-side plugin APIs that control browser behavior, routing, rendering, and user interactions.
/**
* Main interface for gatsby-browser.js plugin APIs
*/
interface GatsbyBrowser {
/** Client-side entry point */
onClientEntry?: (args?: object, pluginOptions?: PluginOptions) => void;
/** Initial client render complete */
onInitialClientRender?: (args?: object, pluginOptions?: PluginOptions) => void;
/** Route change occurred */
onRouteUpdate?: (args: RouteUpdateArgs, pluginOptions?: PluginOptions) => void;
/** Before route change */
onPreRouteUpdate?: (args: RouteUpdateArgs, pluginOptions?: PluginOptions) => void;
/** Delayed route update */
onRouteUpdateDelayed?: (args: RouteUpdateDelayedArgs, pluginOptions?: PluginOptions) => void;
/** Disable core prefetching */
disableCorePrefetching?: (args?: object, pluginOptions?: PluginOptions) => boolean;
/** Before prefetch pathname */
onPrefetchPathname?: (args: PrefetchPathnameArgs, pluginOptions?: PluginOptions) => void;
/** After prefetch pathname */
onPostPrefetchPathname?: (args: PrefetchPathnameArgs, pluginOptions?: PluginOptions) => void;
/** Register service worker */
registerServiceWorker?: (args?: object, pluginOptions?: PluginOptions) => boolean;
/** Service worker active */
onServiceWorkerActive?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/** Service worker installed */
onServiceWorkerInstalled?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/** Service worker redundant */
onServiceWorkerRedundant?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/** Service worker update found */
onServiceWorkerUpdateFound?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/** Service worker update ready */
onServiceWorkerUpdateReady?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/** Replace hydrate function */
replaceHydrateFunction?: () => (element: React.ReactElement, container: Element, callback?: () => void) => void;
/** Control scroll behavior */
shouldUpdateScroll?: (args: ShouldUpdateScrollArgs, pluginOptions?: PluginOptions) => boolean | string | [number, number];
/** Wrap page element */
wrapPageElement?: (args: WrapPageElementBrowserArgs, pluginOptions?: PluginOptions) => React.ReactElement;
/** Wrap root element */
wrapRootElement?: (args: WrapRootElementBrowserArgs, pluginOptions?: PluginOptions) => React.ReactElement;
}Handle client-side initialization and rendering lifecycle events.
/**
* Client entry point - called when the app first loads
*/
onClientEntry?: (args?: object, pluginOptions?: PluginOptions) => void;
/**
* Initial client render complete - called after React hydration
*/
onInitialClientRender?: (args?: object, pluginOptions?: PluginOptions) => void;
/**
* Replace the default React hydrate function
*/
replaceHydrateFunction?: () => (
element: React.ReactElement,
container: Element,
callback?: () => void
) => void;Usage Examples:
// gatsby-browser.js - Client lifecycle
exports.onClientEntry = () => {
// Initialize analytics
if (typeof window !== "undefined" && window.gtag) {
window.gtag("config", "GA_MEASUREMENT_ID");
}
// Load polyfills
if (!window.IntersectionObserver) {
import("intersection-observer");
}
};
exports.onInitialClientRender = () => {
// Set up global event listeners
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
console.log("Page is hidden");
} else {
console.log("Page is visible");
}
});
// Initialize third-party libraries
if (typeof window !== "undefined" && window.Prism) {
window.Prism.highlightAll();
}
};
// Custom hydration for React 18
exports.replaceHydrateFunction = () => {
return (element, container, callback) => {
const { hydrateRoot } = require("react-dom/client");
hydrateRoot(container, element);
if (callback) callback();
};
};Handle navigation, route changes, and prefetching behavior.
/**
* Route update arguments
*/
interface RouteUpdateArgs {
location: WindowLocation;
prevLocation?: WindowLocation;
}
/**
* Delayed route update arguments
*/
interface RouteUpdateDelayedArgs {
location: WindowLocation;
prevLocation?: WindowLocation;
}
/**
* Called on every route change
*/
onRouteUpdate?: (args: RouteUpdateArgs, pluginOptions?: PluginOptions) => void;
/**
* Called before route change
*/
onPreRouteUpdate?: (args: RouteUpdateArgs, pluginOptions?: PluginOptions) => void;
/**
* Called when route update is delayed
*/
onRouteUpdateDelayed?: (args: RouteUpdateDelayedArgs, pluginOptions?: PluginOptions) => void;Usage Examples:
// gatsby-browser.js - Route handling
exports.onRouteUpdate = ({ location, prevLocation }) => {
// Track page views
if (typeof window !== "undefined" && window.gtag) {
window.gtag("config", "GA_MEASUREMENT_ID", {
page_location: window.location.href,
page_path: location.pathname,
});
}
// Close mobile menu on navigation
const mobileMenu = document.querySelector(".mobile-menu");
if (mobileMenu && mobileMenu.classList.contains("open")) {
mobileMenu.classList.remove("open");
}
// Scroll to top on new page
if (prevLocation && location.pathname !== prevLocation.pathname) {
window.scrollTo(0, 0);
}
};
exports.onPreRouteUpdate = ({ location }) => {
// Show loading indicator
const loader = document.querySelector(".page-loader");
if (loader) {
loader.style.display = "block";
}
};
exports.onRouteUpdateDelayed = ({ location }) => {
// Handle slow navigation
console.log("Route update delayed for:", location.pathname);
// Show network error message
const errorMsg = document.querySelector(".network-error");
if (errorMsg) {
errorMsg.style.display = "block";
}
};Control and customize resource prefetching behavior.
/**
* Prefetch pathname arguments
*/
interface PrefetchPathnameArgs {
pathname: string;
loadableReady?: Promise<void>;
}
/**
* Disable core prefetching - return true to disable
*/
disableCorePrefetching?: (args?: object, pluginOptions?: PluginOptions) => boolean;
/**
* Called before prefetching pathname
*/
onPrefetchPathname?: (args: PrefetchPathnameArgs, pluginOptions?: PluginOptions) => void;
/**
* Called after prefetching pathname
*/
onPostPrefetchPathname?: (args: PrefetchPathnameArgs, pluginOptions?: PluginOptions) => void;Usage Examples:
// gatsby-browser.js - Prefetching
exports.disableCorePrefetching = () => {
// Disable prefetching on slow connections
return (
typeof navigator !== "undefined" &&
navigator.connection &&
(navigator.connection.effectiveType === "slow-2g" ||
navigator.connection.effectiveType === "2g")
);
};
exports.onPrefetchPathname = ({ pathname }) => {
console.log("Prefetching:", pathname);
// Custom prefetch logic
if (pathname.startsWith("/blog/")) {
// Prefetch blog-specific assets
const link = document.createElement("link");
link.rel = "prefetch";
link.href = "/assets/blog-styles.css";
document.head.appendChild(link);
}
};
exports.onPostPrefetchPathname = ({ pathname, loadableReady }) => {
console.log("Prefetched:", pathname);
// Track prefetch completion
if (typeof window !== "undefined" && window.gtag) {
window.gtag("event", "prefetch_complete", {
page_path: pathname,
});
}
};Handle service worker lifecycle and events.
/**
* Service worker arguments
*/
interface ServiceWorkerArgs {
serviceWorker?: ServiceWorker;
}
/**
* Register service worker - return true to register
*/
registerServiceWorker?: (args?: object, pluginOptions?: PluginOptions) => boolean;
/**
* Service worker became active
*/
onServiceWorkerActive?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/**
* Service worker was installed
*/
onServiceWorkerInstalled?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/**
* Service worker became redundant
*/
onServiceWorkerRedundant?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/**
* Service worker update found
*/
onServiceWorkerUpdateFound?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;
/**
* Service worker update ready
*/
onServiceWorkerUpdateReady?: (args: ServiceWorkerArgs, pluginOptions?: PluginOptions) => void;Usage Examples:
// gatsby-browser.js - Service Worker
exports.registerServiceWorker = () => {
// Only register in production
return process.env.NODE_ENV === "production";
};
exports.onServiceWorkerActive = () => {
console.log("Service worker is active");
// Show app ready message
const updateBanner = document.querySelector(".update-banner");
if (updateBanner) {
updateBanner.textContent = "App is ready for offline use";
updateBanner.classList.add("show");
setTimeout(() => updateBanner.classList.remove("show"), 3000);
}
};
exports.onServiceWorkerUpdateReady = () => {
console.log("Service worker update ready");
// Show update available message
const updateBanner = document.querySelector(".update-banner");
if (updateBanner) {
updateBanner.innerHTML = `
<div>
New version available!
<button onclick="window.location.reload()">Update</button>
</div>
`;
updateBanner.classList.add("show");
}
};
exports.onServiceWorkerUpdateFound = () => {
console.log("Service worker update found");
// Show downloading message
const updateBanner = document.querySelector(".update-banner");
if (updateBanner) {
updateBanner.textContent = "Downloading update...";
updateBanner.classList.add("show");
}
};Control scroll behavior during navigation.
/**
* Scroll update arguments
*/
interface ShouldUpdateScrollArgs {
routerProps: {
location: WindowLocation;
navigate: NavigateFn;
};
getSavedScrollPosition: (location: WindowLocation) => [number, number] | null;
}
/**
* Control scroll behavior on navigation
* @returns boolean - true to update scroll, false to prevent
* @returns string - element selector to scroll to
* @returns [number, number] - x,y coordinates to scroll to
*/
shouldUpdateScroll?: (
args: ShouldUpdateScrollArgs,
pluginOptions?: PluginOptions
) => boolean | string | [number, number];Usage Examples:
// gatsby-browser.js - Scroll management
exports.shouldUpdateScroll = ({ routerProps, getSavedScrollPosition }) => {
const { location } = routerProps;
// Don't scroll on hash changes
if (location.hash) {
return false;
}
// Restore saved position for browser back/forward
const savedPosition = getSavedScrollPosition(location);
if (savedPosition) {
return savedPosition;
}
// Custom scroll behavior for specific routes
if (location.pathname.startsWith("/docs/")) {
// Scroll to top for docs pages
return [0, 0];
}
if (location.pathname.startsWith("/blog/")) {
// Preserve scroll position for blog
return false;
}
// Default behavior - scroll to top
return true;
};Wrap page and root elements with custom components.
/**
* Wrap page element arguments (browser)
*/
interface WrapPageElementBrowserArgs {
element: React.ReactElement;
props: PageProps;
}
/**
* Wrap root element arguments (browser)
*/
interface WrapRootElementBrowserArgs {
element: React.ReactElement;
}
/**
* Wrap each page with custom elements
*/
wrapPageElement?: (
args: WrapPageElementBrowserArgs,
pluginOptions?: PluginOptions
) => React.ReactElement;
/**
* Wrap the root element
*/
wrapRootElement?: (
args: WrapRootElementBrowserArgs,
pluginOptions?: PluginOptions
) => React.ReactElement;Usage Examples:
// gatsby-browser.js - Element wrapping
import React from "react";
import { Provider } from "react-redux";
import { ThemeProvider } from "styled-components";
import Layout from "./src/components/Layout";
import store from "./src/store";
import theme from "./src/theme";
// Wrap root with global providers
exports.wrapRootElement = ({ element }) => (
<Provider store={store}>
<ThemeProvider theme={theme}>
{element}
</ThemeProvider>
</Provider>
);
// Wrap each page with layout
exports.wrapPageElement = ({ element, props }) => (
<Layout {...props}>
{element}
</Layout>
);