How to create canonical URLs in Gatsby?

Published: (February 1, 2026 at 03:30 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

Cover image for How to create canonical URLs in Gatsby?

Dejan Kostevski

Introduction

In one of my previous posts I described what a canonical URL is. Now, let me show you how to implement this in Gatsby.

Gatsby

What is Gatsby?
Gatsby is a React‑based open‑source framework for creating websites and apps.

  • It is a static site generator (SSG) built on React.
  • It pre‑renders your React components into HTML files.
  • It provides a plugin ecosystem and a lively community.

After Gatsby renders your components into HTML pages you can serve them as a static website on any hosting you want.

React Helmet

What is React Helmet?
React Helmet is a reusable React component that lets you manage all of your <head> tags in a Gatsby project.

import React from "react";
import { Helmet } from "react-helmet";

class Application extends React.Component {
  render() {
    return (
      <Helmet>
        <title>My Title</title>
        {/* other head tags */}
      </Helmet>
    );
  }
}

When Gatsby renders the HTML you will get:

<head>
  <title>My Title</title>
  <!-- other head tags -->
</head>

Installation

npm install gatsby-plugin-react-helmet react-helmet

gatsby-plugin-react-helmet-canonical-urls

React Helmet works great, but it conflicts with gatsby-plugin-canonical-urls. Using both can result in two canonical <link> tags.

The gatsby-plugin-react-helmet-canonical-urls plugin solves this by allowing default canonical tags that can be overridden via React Helmet.

Installation

npm install --save gatsby-plugin-react-helmet gatsby-plugin-react-helmet-canonical-urls

Configuration (gatsby-config.js)

// gatsby-config.js
module.exports = {
  plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-plugin-react-helmet-canonical-urls`,
      options: {
        siteUrl: "https://www.example.com",
      },
    },
  ],
};

See the full list of options here: .

Using Canonical URLs on Static Pages

Example Component (about.jsx)

import React from "react";
import { Helmet } from "react-helmet";
import Layout from "../components/layout";

export default ({ data, location }) => (
  <Layout>
    <Helmet>
      <title>{`${data.site.siteMetadata.title} - About`}</title>
      <link
        rel="canonical"
        href={`${data.site.siteMetadata.siteUrl}${location.pathname}`}
      />
    </Helmet>

    <h1>About me</h1>
    {/* page content */}
  </Layout>
);

Site Metadata (gatsby-config.js)

module.exports = {
  siteMetadata: {
    title: "Kode Skills",
    siteUrl: "https://kodeskills.com/",
  },
};

GraphQL Query

export const query = graphql`
  {
    site {
      siteMetadata {
        title
        siteUrl
      }
    }
  }
`;

The location prop (see Gatsby docs) is automatically passed to page components, allowing you to build the full canonical URL from siteUrl and location.pathname.

Conclusion

By combining React Helmet with gatsby-plugin-react-helmet-canonical-urls, you can:

  1. Define a default canonical URL for the whole site.
  2. Override it on a per‑page basis when needed.
  3. Keep your <link rel="canonical"> tags clean and SEO‑friendly.

Happy building! 🚀

How to Create Canonical URLs in Gatsby

What is a canonical URL?

A canonical URL tells search engines which version of a page is the “master” version when there are multiple URLs with the same (or very similar) content. It prevents duplicate‑content penalties and consolidates link equity.

In Gatsby you can add a <link rel="canonical"> tag to the <head> of each page. The value should be the full URL of the page (including protocol and domain).

Static Pages

For a static page you can build the canonical URL from the site’s base URL (siteUrl) and the page’s pathname (location.pathname).

// src/pages/about.jsx
import React from "react";
import { graphql } from "gatsby";
import { Helmet } from "react-helmet";

export default ({ data, location }) => (
  <>
    <Helmet>
      <title>{data.site.siteMetadata.title} – About</title>
      <link
        rel="canonical"
        href={`${data.site.siteMetadata.siteUrl}${location.pathname}`}
      />
    </Helmet>

    {/* page content */}
  </>
);

export const query = graphql`
  {
    site {
      siteMetadata {
        title
        siteUrl
      }
    }
  }
`;
  • siteUrl comes from gatsby-config.js.
  • location.pathname is the part of the URL after the domain (e.g. /about).

Dynamic Templates

When you have a template that is reused for many pieces of content (blog posts, tag pages, etc.) you can pull the slug from the Markdown front‑matter and construct the canonical URL in the same way.

1. Add a slug to the front‑matter

---
slug: "how-to-create-canonical-urls-in-gatsby"
title: "How to create canonical URLs in Gatsby?"
---

2. Query the data

# src/templates/blog-post.jsx
export const query = graphql`
  query BlogPostQuery($slug: String!) {
    site {
      siteMetadata {
        siteUrl
      }
    }
    markdownRemark(frontmatter: { slug: { eq: $slug } }) {
      frontmatter {
        slug
        title
      }
    }
  }
`;

3. Use the data in the component

// src/templates/blog-post.jsx
import React from "react";
import { Helmet } from "react-helmet";

export default ({ data }) => (
  <>
    <Helmet>
      <title>{data.markdownRemark.frontmatter.title}</title>
      <link
        rel="canonical"
        href={`${data.site.siteMetadata.siteUrl}/${data.markdownRemark.frontmatter.slug}`}
      />
    </Helmet>

    {/* the content of your blog post */}
  </>
);

Reusable SEO Component

To avoid repeating the same <Helmet> logic, create a small Seo component that you can drop into any page or template.

// src/components/Seo.jsx
import React from "react";
import { Helmet } from "react-helmet";

export const Seo = ({ canonicalUrl, siteTitle, title }) => (
  <Helmet>
    <title>{siteTitle} – {title}</title>
    {canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
  </Helmet>
);

Use in a static page

// src/pages/about.jsx
import React from "react";
import { graphql } from "gatsby";
import { Seo } from "../components/Seo";

export default ({ data, location }) => (
  <>
    <Seo
      siteTitle={data.site.siteMetadata.title}
      title="About"
      canonicalUrl={`${data.site.siteMetadata.siteUrl}${location.pathname}`}
    />
    {/* page content */}
  </>
);

export const query = graphql`
  {
    site {
      siteMetadata {
        title
        siteUrl
      }
    }
  }
`;

Use in a dynamic blog‑post template

// src/templates/blog-post.jsx
import React from "react";
import { graphql } from "gatsby";
import { Seo } from "../components/Seo";

export default ({ data }) => (
  <>
    <Seo
      siteTitle={data.site.siteMetadata.title}
      title={data.markdownRemark.frontmatter.title}
      canonicalUrl={`${data.site.siteMetadata.siteUrl}/${data.markdownRemark.frontmatter.slug}`}
    />
    {/* post content */}
  </>
);

export const query = graphql`
  query BlogPostQuery($slug: String!) {
    site {
      siteMetadata {
        title
        siteUrl
      }
    }
    markdownRemark(frontmatter: { slug: { eq: $slug } }) {
      frontmatter {
        slug
        title
      }
    }
  }
`;

Conclusion

Setting up canonical URLs is one of the first SEO steps you should take for any website. It’s especially important when you syndicate content to other platforms (Medium, dev.to, etc.).

With Gatsby you can generate canonical URLs automatically for both static pages and dynamic templates, and a small reusable Seo component keeps your code DRY and easy to maintain.

Back to Blog

Related posts

Read more »

ReactJS ~React Server Components~

!Cover image for ReactJS ~React Server Components~https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev...