wrapRootElement vs wrapPageElement

According to Gatsby documentation, you can use wrapRootElement to wrap your application with provider components and wrapPageElement to wrap your pages with components that won’t get unmounted on page change. But that explanation begs the question: why we don’t do both with wrapPageElement, and instead, we need two separate APIs to accomplish those tasks? Let’s see their differences to understand why.


First of all, wrapPageElement is a child of wrapRootElement as you can see in the following image:

their relationship in html
wrapRootElement vs wrapPageElement

Additionally, wrapPageElement is a child of the routerwrapRootElement is not—so it has access to the router props. You can access those props if you destructure the props field from the first argument:

// gatsby-browser.js or gatsby-ssr.js
exports.wrapPageElement = ({ element, props }) => {  return <Layout {...props}>{element}</Layout>;

In the following snippet you can see an example of that props field:

  "path": "/",  "location": {    "href": "http://localhost:8001/",
    "ancestorOrigins": {},
    "origin": "http://localhost:8001",
    "protocol": "http:",
    "host": "localhost:8001",
    "hostname": "localhost",
    "port": "8001",
    "pathname": "/",
    "search": "",
    "hash": "",
    "state": null,
    "key": "initial"
  "pageResources": {    "json": {
      "pageContext": {
        "isCreatedByStatefulCreatePages": true
    "page": {
      "componentChunkName": "component---src-pages-index-js",
      "path": "/",
      "webpackCompilationHash": "4987305ccd829dc361b7"
  "uri": "/",  "pageContext": {    "isCreatedByStatefulCreatePages": true
  "pathContext": {    "isCreatedByStatefulCreatePages": true

As a result, wrapPageElement renders every time the page changes—wrapRootElement does not—making it ideal for complex page transitions, or for stuff that need the page path, like an internationalization context provider for example. On the other hand, because wrapRootElement doesn’t render when the page changes, it’s a good fit for context providers that don’t need the page, like theme or global application state providers. And that’s their biggest difference.

One similarity they share is that they both mount only once, as opposed to a regular Layout component—that you use inside your pages—that will unmount every time the page changes.

Finally, if you don’t provide an implementation for wrapPageElement in gatsby-ssr.js, it will use the implementation from gatsby-browser.js. This is not true for wrapRootElement.


Coming back to the initial question, yes, you can use wrapPageElement element for everything, but it’s better to use it only for providers that need the router props, or for page transition layouts, and use wrapRootElement for any other provider, like theme and global state providers.


Here I list some links and the code I used to find the differences.


The gatsby-browser.js file:

// gatsby-browser.js
import React from "react";
import Layout from "./src/components/Layout";

export const wrapRootElement = ({ element, ...restProps }, ...args) => {
  return (
    <Layout name="wrapRootElement" props={restProps} args={args} mode="browser">
export const wrapPageElement = ({ element, ...restProps }, ...args) => {
  return (
    // <Layout name="wrapPageElement" props={{}} args={args} mode="browser">
    <Layout name="wrapPageElement" props={restProps} args={args} mode="browser">

And the Layout component:

// src/components/Layout.jsx

import React, { useEffect } from "react";

const Layout = ({ name, props, args, mode, children }) => {
  useEffect(() => {
    console.log(`${name} Layout mounted.`);
    console.log(`${name} Layout restProps:`, JSON.stringify(props, null, 2));
    console.log(`${name} Layout arguments:`, JSON.stringify(args, null, 2));
  }, []);
  console.log(`${name} Layout rendered.`);
  return (
    <div data-name={name} data-mode={mode}>

export default Layout;

Other things to read