How to Get the Current Page URL in Gatsby

Avatar of Dmitry Mayorov
Dmitry Mayorov on

This seemingly simple task had me scratching my head for a few hours while I was working on my website. As it turns out, getting the current page URL in Gatsby is not as straightforward as you may think, but also not so complicated to understand.

Let’s look at a few methods of making it happen. But first, you might be wondering why on earth we’d even want to do something like this.

Why you might need the current URL

So before we get into the how, let’s first answer the bigger question: Why would you want to get the URL of the current page? I can offer a few use cases.

Meta tags

The first obvious thing that you’d want the current URL for is meta tags in the document head:

<link rel="canonical" href={url} />
<meta property="og:url" content={url} />

Social Sharing

I’ve seen it on multiple websites where a link to the current page is displayed next to sharing buttons. Something like this (found on Creative Market)

Styling

This one is less obvious but I’ve used it a few times with styled-components. You can render different styles based on certain conditions. One of those conditions can be a page path (i.e. part of the URL after the name of the site). Here’s a quick example:

import React from 'react';
import styled from 'styled-components';

const Layout = ({ path, children }) => (
  <StyledLayout path={path}>
    {children}
  </StyledLayout>
);
    
const StyledLayout = styled.main`
  background-color: ${({ path }) => (path === '/' ? '#fff' : '#000')};
`;

export default Layout;

Here, I’ve created a styled Layout component that, based on the path, has a different background color.

This list of examples only illustrates the idea and is by no means comprehensive. I’m sure there are more cases where you might want to get the current page URL. So how do we get it?

Understand build time vs. runtime

Not so fast! Before we get to the actual methods and code snippets, I’d like to make one last stop and briefly explain a few core concepts of Gatsby.

The first thing that we need to understand is that Gatsby, among many other things, is a static site generator. That means it creates static files (that are usually HTML and JavaScript). There is no server and no database on the production website. All pieces of information (including the current page URL) must be pulled from other sources or generated during build time or runtime before inserting it into the markup.

That leads us to the second important concept we need to understand: Build time vs. runtime. I encourage you to read the official Gatsby documentation about it, but here’s my interpretation.

Runtime is when one of the static pages is opened in the browser. In that case, the page has access to all the wonderful browser APIs, including the Window API that, among many other things, contains the current page URL.

One thing that is easy to confuse, especially when starting out with Gatsby, is that running gatsby develop in the terminal in development mode spins up the browser for you. That means all references to the window object work and don’t trigger any errors.

Build time happens when you are done developing and tell Gatsby to generate final optimized assets using the gatsby build command. During build time, the browser doesn’t exist. This means you can’t use the window object.

Here comes the a-ha! moment. If builds are isolated from the browser, and there is no server or database where we can get the URL, how is Gatsby supposed to know what domain name is being used? That’s the thing — it can’t! You can get the slug or path of the page, but you simply can’t tell what the base URL is. You have to specify it.

This is a very basic concept, but if you are coming in fresh with years of WordPress experience, it can take some time for this info to sink in. You know that Gatsby is serverless and all but moments like this make you realize: There is no server.

Now that we have that sorted out, let’s jump to the actual methods for getting the URL of the current page.

Method 1: Use the href property of the window.location object

This first method is not specific to Gatsby and can be used in pretty much any JavaScript application in the browser. See, browser is the key word here.

Let’s say you are building one of those sharing components with an input field that must contain the URL of the current page. Here’s how you might do that:

import React from 'react';

const Foo = () => {
  const url = typeof window !== 'undefined' ? window.location.href : '';

  return (
    <input type="text" readOnly="readonly" value={url} />
  );
};

export default Foo;

If the window object exists, we get the href property of the location object that is a child of the window. If not, we give the url variable an empty string value.

If we do it without the check and write it like this:

const url = window.location.href;

…the build will fail with an error that looks something like this:

failed Building static HTML for pages - 2.431s
ERROR #95312 
"window" is not available during server-side rendering.

As I mentioned earlier, this happens because the browser doesn’t exist during the build time. That’s a huge disadvantage of this method. You can’t use it if you need the URL to be present on the static version of the page.

But there is a big advantage as well! You can access the window object from a component that is nested deep inside other components. In other words, you don’t have to drill the URL prop from parent components.

Method 2: Get the href property of location data from props

Every page and template component in Gatsby has a location prop that contains information about the current page. However, unlike window.location, this prop is present on all pages.

Quoting Gatsby docs:

The great thing is you can expect the location prop to be available to you on every page.

But there may be a catch here. If you are new to Gatsby, you’ll log that prop to the console, and notice that it looks pretty much identical to the window.location (but it’s not the same thing) and also contains the href attribute. How is this possible? Well, it is not. The href prop is only there during runtime.

The worst thing about this is that using location.href directly without first checking if it exists won’t trigger an error during build time.

All this means that we can rely on the location prop to be on every page, but can’t expect it to have the href property during build time. Be aware of that, and don’t use this method for critical cases where you need the URL to be in the markup on the static version of the page.

So let’s rewrite the previous example using this method:

import React from 'react';

const Page = ({ location }) => {
  const url = location.href ? location.href : '';

  return (
    <input type="text" readOnly="readonly" value={url} />
  );
};

export default Page;

This has to be a top-level page or template component. You can’t just import it anywhere and expect it work. location prop will be undefined.

As you can see, this method is pretty similar to the previous one. Use it for cases where the URL is needed only during runtime.

But what if you need to have a full URL in the markup of a static page? Let’s move on to the third method.

Method 3: Generate the current page URL with the pathname property from location data

As we discussed at the start of this post, if you need to include the full URL to the static pages, you have to specify the base URL for the website somewhere and somehow get it during build time. I’ll show you how to do that.

As an example, I’ll create a <link rel="canonical" href={url} /> tag in the header. It is important to have the full page URL in it before the page hits the browser. Otherwise, search engines and site scrapers will see the empty href attribute, which is unacceptable.

Here’s the plan:

  1. Add the siteURL property to siteMetadata in gatsby-config.js.
  2. Create a static query hook to retrieve siteMetadata in any component.
  3. Use that hook to get siteURL.
  4. Combine it with the path of the page and add it to the markup.

Let’s break each step down.

Add the siteURL property to siteMetadata in gatsby-config.js

Gatsby has a configuration file called gatsby-config.js that can be used to store global information about the site inside siteMetadata object. That works for us, so we’ll add siteURL to that object:

module.exports = {
  siteMetadata: {
    title: 'Dmitry Mayorov',
    description: 'Dmitry is a front-end developer who builds cool sites.',
    author: '@dmtrmrv',
    siteURL: 'https://dmtrmrv.com',
  }
};

Create a static query hook to retrieve siteMetadata in any component

Next, we need a way to use siteMetadata in our components. Luckily, Gatsby has a StaticQuery API that allows us to do just that. You can use the useStaticQuery hook directly inside your components, but I prefer to create a separate file for each static query I use on the website. This makes the code easier to read.

To do that, create a file called use-site-metadata.js inside a hooks folder inside the src folder of your site and copy and paste the following code to it.

import { useStaticQuery, graphql } from 'gatsby';

const useSiteMetadata = () => {
  const { site } = useStaticQuery(
  graphql`
    query {
    site {
      siteMetadata {
      title
      description
      author
      siteURL
      }
    }
    }
  `,
  );
  return site.siteMetadata;
};

export default useSiteMetadata;

Make sure to check that all properties — like title, description, author, and any other properties you have in the siteMetadata object — appear in the GraphQL query.

Use that hook to get siteURL

Here’s the fun part: We get the site URL and use it inside the component.

import React from 'react';
import Helmet from 'react-helmet';
import useSiteMetadata from '../hooks/use-site-metadata';

const Page = ({ location }) => {
  const { siteURL } = useSiteMetadata();
  return (
    <Helmet>
      <link rel="canonical" href={`${siteURL}${location.pathname}`} />
    </Helmet>
  );
};

export default Page;

Let’s break it down.

On Line 3, we import the useSiteMetadata hook we created into the component.

import useSiteMetadata from '../hooks/use-site-metadata';

Then, on Line 6, we destructure the data that comes from it, creating the siteURL variable. Now we have the site URL that is available for us during build and runtime. Sweet!

const { siteURL } = useSiteMetadata();

Combine the site URL with the path of the page and add it to the markup

Now, remember the location prop from the second method? The great thing about it is that it contains the pathname property during both build and runtime. See where it’s going? All we have to do is combine the two:

`${siteURL}${location.pathname}`

This is probably the most robust solution that will work in the browsers and during production builds. I personally use this method the most.

I’m using React Helmet in this example. If you haven’t heard of it, it’s a tool for rendering the head section in React applications. Darrell Hoffman wrote up a nice explanation of it here on CSS-Tricks.

Method 4: Generate the current page URL on the server side

What?! Did you just say server? Isn’t Gatsby a static site generator? Yes, I did say server. But it’s not a server in the traditional sense.

As we already know, Gatsby generates (i.e. server renders) static pages during build time. That’s where the name comes from. What’s great about that is that we can hook into that process using multiple APIs that Gatsby already provides.

The API that interests us the most is called onRenderBody. Most of the time, it is used to inject custom scripts and styles to the page. But what’s exciting about this (and other server-side APIs) is that it has a pathname parameter. This means we can generate the current page URL “on the server.”

I wouldn’t personally use this method to add meta tags to the head section because the third method we looked at is more suitable for that. But for the sake of example, let me show you how you could add the canonical link to the site using onRenderBody.

To use any server-side API, you need to write the code in a file called gatsby-ssr.js that is located in the root folder of your site. To add the link to the head section, you would write something like this:

const React = require('react');
const config = require('./gatsby-config');

exports.onRenderBody = ({ pathname, setHeadComponents }) => {
  setHeadComponents([
    <link rel="canonical" href={`${config.siteMetadata.siteURL}${pathname}`} />,
  ]);
};

Let’s break this code bit by bit.

We require React on Line 1. It is necessary to make the JSX syntax work. Then, on Line 2, we pull data from the gatsby-config.js file into a config variable.

Next, we call the setHeadComponents method inside onRenderBody and pass it an array of components to add to the site header. In our case, it’s just one link tag. And for the href attribute of the link itself, we combine the siteURL and the pathname:

`${config.siteMetadata.siteURL}${pathname}`

Like I said earlier, this is probably not the go-to method for adding tags to the head section, but it is good to know that Gatsby has server-side APIs that make it possible to generate a URL for any given page during the server rendering stage.

If you want to learn more about server-side rendering with Gatsby, I encourage you to read their official documentation.

That’s it!

As you can see, getting the URL of the current page in Gatsby is not very complicated, especially once you understand the core concepts and know the tools that are available to use. If you know other methods, please let me know in the comments!

Resources