# How to Handle Authentication and Protected Routes in a Gatsby Application

> Secure your Gatsby application by implementing authentication and protected routes. Learn to use wrapRootElement, localStorage, and matchPath for client-only routes and content gating.

- Repository: [Gatsby/gatsby](https://github.com/gatsbyjs/gatsby)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Use `wrapRootElement` in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) to inject an authentication provider, store session state in `localStorage`, and configure client‑only routes via `matchPath` in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js) to gate content behind a custom `PrivateRoute` component.**

Gatsby generates static HTML at build time, but you can still handle authentication and protected routes in a Gatsby application by leveraging client‑only routes and React context. The `gatsbyjs/gatsby` repository provides official patterns and examples—such as the Auth0 integration in `examples/functions-auth0` and the authentication tutorial—that demonstrate how to secure pages without sacrificing static performance.

## Understanding Client‑Only Routes for Authentication

Static sites cannot rely on server‑side sessions, so Gatsby uses **client‑only routes** to handle dynamic, user‑specific URLs. These routes are created at runtime in the browser rather than at build time, allowing you to intercept paths like `/app/*` and render React components that check authentication state.

### Configuring matchPath in gatsby-node.js

To enable client‑only routing, you must tell Gatsby to treat a specific path prefix as a wildcard. In [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js), use the `onCreatePage` API to assign a `matchPath` property to the page:

```javascript
// gatsby-node.js
exports.onCreatePage = async ({ page, actions }) => {
  const { createPage } = actions

  // Any path beginning with /app is handled by the app shell page.
  if (page.path.match(/^\/app/)) {
    page.matchPath = "/app/*"
    createPage(page)
  }
}

```

*Source:* [`docs/tutorial/authentication-tutorial.md`](https://github.com/gatsbyjs/gatsby/blob/main/docs/tutorial/authentication-tutorial.md) in the `gatsbyjs/gatsby` repository.

## Setting Up an Authentication Provider

Because Gatsby builds static HTML, authentication SDKs (like Auth0 or Firebase) must only execute in the browser. The standard pattern is to wrap your application in an authentication provider using the **`wrapRootElement`** API in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) (and optionally [`gatsby-ssr.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-ssr.js) to prevent hydration mismatches).

### Wrapping the Root Element in gatsby-browser.js

The `wrapRootElement` export allows you to inject a React context provider around the entire component tree. For Auth0, this looks like:

```javascript
// gatsby-browser.js
import * as React from "react"
import { navigate } from "gatsby"
import { Auth0Provider } from "@auth0/auth0-react"

const onRedirectCallback = (appState) => {
  navigate(appState?.returnTo || "/")
}

export const wrapRootElement = ({ element }) => (
  <Auth0Provider
    domain={process.env.GATSBY_AUTH0_DOMAIN}
    clientId={process.env.GATSBY_AUTH0_CLIENT_ID}
    audience={process.env.GATSBY_AUTH0_AUDIENCE}
    scope={process.env.GATSBY_AUTH0_SCOPE}
    redirectUri={window.location.origin}
    onRedirectCallback={onRedirectCallback}
  >
    {element}
  </Auth0Provider>
)

```

*Source:* [`examples/functions-auth0/gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/examples/functions-auth0/gatsby-browser.js) in the `gatsbyjs/gatsby` repository.

### Handling Server‑Side Rendering Considerations

When using `wrapRootElement` in [`gatsby-ssr.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-ssr.js), ensure the provider does not access browser‑only globals like `window` during the server render. The Auth0 example above references `window.location.origin`, so it should only run in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js). For universal rendering, guard browser‑specific logic with a check like `typeof window !== "undefined"`.

## Creating a Protected Route System

With the provider in place, you need a mechanism to check login status and redirect unauthenticated users. Because Gatsby pages are static, this check must happen at runtime inside a **shell page** that uses `@reach/router` (the router Gatsby uses internally).

### Building a Client‑Side Auth Service

Create a lightweight service to persist session state across refreshes. The Gatsby tutorial provides a minimal implementation using `localStorage`:

```javascript
// src/services/auth.js
export const isBrowser = () => typeof window !== "undefined"

export const getUser = () =>
  isBrowser() && window.localStorage.getItem("gatsbyUser")
    ? JSON.parse(window.localStorage.getItem("gatsbyUser"))
    : {}

const setUser = user =>
  window.localStorage.setItem("gatsbyUser", JSON.stringify(user))

export const handleLogin = ({ username, password }) => {
  if (username === `john` && password === `pass`) {
    setUser({
      username: `john`,
      name: `Johnny`,
      email: `johnny@example.org`,
    })
    return true
  }
  return false
}

export const isLoggedIn = () => {
  const user = getUser()
  return !!user.username
}

export const logout = callback => {
  setUser({})
  callback()
}

```

*Source:* [`docs/tutorial/authentication-tutorial.md`](https://github.com/gatsbyjs/gatsby/blob/main/docs/tutorial/authentication-tutorial.md) in the `gatsbyjs/gatsby` repository.

### Implementing PrivateRoute Components

Combine the auth service with a router to guard specific paths. In your shell page (e.g., [`src/pages/app.js`](https://github.com/gatsbyjs/gatsby/blob/main/src/pages/app.js)), use the `Router` from `@reach/router` and conditionally render components based on `isLoggedIn()`:

```jsx
// src/pages/app.js
import React from "react"
import { Router } from "@reach/router"
import Layout from "../components/layout"
import Profile from "../components/profile"
import Login from "../components/login"
import { isLoggedIn } from "../services/auth"
import { navigate } from "gatsby"

const PrivateRoute = ({ component: Component, ...rest }) => {
  if (!isLoggedIn()) {
    navigate("/app/login")
    return null
  }
  return <Component {...rest} />
}

const App = () => (
  <Layout>
    <Router>
      <PrivateRoute path="/app/profile" component={Profile} />
      <Login path="/app/login" />
    </Router>
  </Layout>
)

export default App

```

When a user visits `/app/profile`, the `PrivateRoute` component checks `isLoggedIn()`. If the user is unauthenticated, `navigate` redirects them to `/app/login` before the protected component ever renders.

## Summary

- **Use `wrapRootElement`** in [`gatsby-browser.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-browser.js) to inject authentication providers like Auth0 only in the browser, avoiding server‑side rendering errors.
- **Persist sessions** with `localStorage` via a custom service (e.g., [`src/services/auth.js`](https://github.com/gatsbyjs/gatsby/blob/main/src/services/auth.js)) so login state survives page refreshes.
- **Enable client‑only routes** by setting `page.matchPath = "/app/*"` inside `onCreatePage` in [`gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-node.js), allowing dynamic routing under a specific path prefix.
- **Guard protected content** with a `PrivateRoute` component that checks `isLoggedIn()` and uses Gatsby’s `navigate` helper to redirect unauthenticated users before rendering sensitive UI.

## Frequently Asked Questions

### Can Gatsby handle server‑side authentication?

No. Because Gatsby generates static HTML at build time, it cannot maintain server‑side sessions or validate JWTs on the server. Authentication must happen entirely in the browser using client‑side JavaScript, and protected data should be fetched from secure API endpoints that validate tokens at request time.

### How do I persist login state across page refreshes in Gatsby?

Store the user object or token in `localStorage` (or `sessionStorage`) via a helper service like [`src/services/auth.js`](https://github.com/gatsbyjs/gatsby/blob/main/src/services/auth.js). On app initialization, read from storage to set the initial auth state. This ensures the user remains logged in after refreshing the page or closing and reopening the browser tab.

### What is the difference between createPages and client‑only routes for authentication?

`createPages` generates static HTML pages at build time for known routes (e.g., `/about`). Client‑only routes (configured via `matchPath` in `onCreatePage`) tell Gatsby to serve a single "shell" page for any URL matching a pattern (e.g., `/app/*`), allowing React to handle routing dynamically at runtime—essential for authenticated routes that cannot be statically generated.

### How do I prevent a flash of unauthenticated content in Gatsby?

Check authentication status before mounting protected components. In your `PrivateRoute` component, return `null` or a loading spinner while verifying the session (e.g., checking `localStorage` or waiting for an auth SDK to initialize). Only render the protected UI once `isLoggedIn()` confirms the user is authenticated, preventing the protected content from flashing before the redirect completes.