Skip to main content

Client-only Routes & User Authentication

Often you want to create a site with client-only portions, which allows you to gate them by authentication or load different content based on URL parameters.

Understanding client-only routes

A classic example would be a site that has a landing page, various marketing pages, a login page, and then an app section for logged-in users. The logged-in section doesn’t need to be server rendered as all data will be loaded live from your API after the user logs in. So it makes sense to make this portion of your site client-only.

Client-only routes will exist on the client only and will not correspond to index.html files in an app’s built assets. If you’d like site users to be able to visit client routes directly, you need to set up your site to handle those routes appropriately. Or, if you have control over the configuration of the file server yourself (instead of using another static file host like Netlify), you can set up the server to handle these routes.

A sample site might be set up like this:

Site with a static homepage and client-only routes

Gatsby converts components in the pages folder into static HTML files for the Home page and the App page. A <Router /> is added to the App page so that the profile and details components can be rendered from the App page; they don’t have static assets built for them because they exist only on the client. The profile page can POST data about a user back to an API, and the details page can dynamically load data about a user with a specific id from an API.

Handling client-only routes with Gatsby

Gatsby uses @reach/router under the hood, and it is the recommended approach to create client-only routes.

You first need to set up routes on a page that is built by Gatsby:

src/pages/app.js
import React from "react"
import { Router } from "@reach/router"
import Layout from "../components/Layout"
import Profile from "../components/Profile"
import Details from "../components/Details"
import Login from "../components/Login"
import Default from "../components/Default"
const App = () => {
return (
<Layout>
<Router>
<Profile path="/app/profile" />
<Details path="/app/details" />
<Login path="/app/login" />
<Default path="/app" />
</Router>
</Layout>
)
}
export default App

With routes nested under the <Router /> from Reach Router, it will render the component from the route that corresponds to the location. In the case of the /app/profile path, the Profile component will be rendered.

Adjusting routes to account for authenticated users

With authentication set up on your site, you can create a component like a <PrivateRoute/> to extend the example above and gate content:

src/pages/app.js
import React from "react"
import { Router } from "@reach/router"
import Layout from "../components/Layout"
import Profile from "../components/Profile"
import Details from "../components/Details"
import Login from "../components/Login"
import Default from "../components/Default"
import PrivateRoute from "../components/PrivateRoute"
const App = () => {
return (
<Layout>
<Router>
<PrivateRoute path="/app/profile" component={Profile} />
<PrivateRoute path="/app/details" component={Details} />
<Login path="/app/login" />
<Default path="/app" />
</Router>
</Layout>
)
}
export default App

The <PrivateRoute /> component would look something like this one (taken from the Authentication Tutorial, which implements this behavior):

src/components/PrivateRoute.js
// import ...
import React, { Component } from "react"
import { navigate } from "gatsby"
import { isLoggedIn } from "../services/auth"
const PrivateRoute = ({ component: Component, location, ...rest }) => {
if (!isLoggedIn() && location.pathname !== `/app/login`) {
navigate("/app/login")
return null
}
return <Component {...rest} />
}
export default PrivateRoute

Configuring pages with matchPath

To ensure that users can navigate to client-only routes directly, pages in your site need to have the matchPath parameter set. Add the following code to your site’s gatsby-node.js file:

gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// Only update the `/app` page.
if (page.path.match(/^\/app/)) {
// page.matchPath is a special key that's used for matching pages
// with corresponding routes only on the client.
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}

💡 Note: There’s also a plugin to simplify the creation of client-only routes in your site: gatsby-plugin-create-client-paths.

The above code (as well as the gatsby-plugin-create-client-paths plugin) updates the /app page at build time to add the matchPath parameter in the page object to make it so that the configured pages (in this case, everything after /app, like /app/dashboard or /app/user) can be navigated to by Reach Router.

Without this configuration set up, a user that clicks on a link to <yoursite.com>/app/user will instead be routed to the static /app page instead of the component or page you have set up at /app/user.

Tip: For applications with complex routing, you may want to override Gatsby’s default scroll behavior with the shouldUpdateScroll Browser API.

Configuring and handling client-only routes on a server

If you are hosting on your own server, you can opt to configure the server to handle client-only routes instead of using the matchPath method explained above.

Consider the following router and route to serve as an example:

src/pages/app.js
<Router>
<Route path="/app/why-gatsby-is-awesome" />
</Router>

In this example with a router and a single route for /app/why-gatsby-is-awesome/, the server would not be able to complete this request as why-gatsby-is-awesome is a client-side route. It does not have a corresponding HTML file on the server. The file found at /app/index.html on the server contains all the code to handle the page paths after /app.

A pattern to follow, agnostic of server technology, is to watch for these specific routes and return the appropriate HTML file.

In this example, when making a GET request to /app/why-gatsby-is-awesome, the server should respond with /app/index.html and let the client handle the rendering of the route with the matching path. It is important to note that the response code should be a 200 (an OK) and not a 301 (a redirect).

One result of this method is that the client is completely unaware of the logic on the server, decoupling it from Gatsby.

Additional resources


Edit this page on GitHub
Docs
Tutorials
Plugins
Blog
Showcase