Updated 2026-10-21

SEO for Docusaurus: titles, descriptions, link previews and structured data

Docusaurus renders a static HTML file for every route, so crawlers and link previewers get real content without running JavaScript. Out of the box each page also gets a <title>, a canonical link, og:url, twitter:card set to summary_large_image, and hreflang links when you have more than one locale. What it can't do on its own is write a good description for every page, pick a preview image per page, or decide which pages belong in search. This guide goes through each setting in order, for Docusaurus 3 (Docusaurus: SEO).

1. Site URL and trailing slashes

Every absolute URL Docusaurus writes (canonical, og:url, og:image, sitemap entries) is built from url and baseUrl in docusaurus.config.js. Set them to the production address:

export default {
  url: 'https://docs.example.com',
  baseUrl: '/',
  trailingSlash: true,
};

Set trailingSlash explicitly. With the default (undefined) Docusaurus leaves links as written and emits /docs/intro/index.html for docs/intro.md, but the canonical and sitemap URL is /docs/intro without a slash. Many static hosts, GitHub Pages among them, answer that URL with a redirect to /docs/intro/. The result is a canonical that points at a redirect, and Search Console reports such as "Page with redirect" or "Alternate page with proper canonical tag".

SettingFile written for docs/intro.mdURL in canonical and sitemap
truebuild/docs/intro/index.html/docs/intro/
falsebuild/docs/intro.html/docs/intro
undefinedbuild/docs/intro/index.html/docs/intro (as linked)

Pick the value that matches how your host serves those files without redirecting; the Docusaurus deployment docs point to the trailing slash guide, which lists the behavior of each host. Then check one page:

curl -sI https://docs.example.com/docs/intro | grep -iE '^(HTTP|location)'

A 200 means the canonical URL is served directly. A 301 or 308 means trailingSlash and your host disagree.

2. Site-wide metadata and the default preview image

Two settings in themeConfig apply to every page (theme configuration):

export default {
  themeConfig: {
    image: 'img/social-card.png',
    metadata: [
      {name: 'twitter:site', content: '@example'},
    ],
  },
};

Without image, pages have no preview image at all unless the doc sets one. If a card still comes out without a picture, og:image not showing covers the other causes.

3. Per-page front matter

The docs, blog and pages plugins all accept the same four SEO fields (docs front matter):

---
title: Configure the CLI
description: Every option in the config file, with defaults and an example for monorepos.
image: /img/social/configure.png
keywords: [cli, configuration]
sidebar_label: Configuration
---

The first-line description

If a doc has no description, Docusaurus uses the first line of Markdown content. Imports, code blocks and the # title are skipped, but other headings, admonitions and short intro sentences aren't. A page that opens with ## Prerequisites gets "Prerequisites" as its description, and so does every other page that opens the same way. To see what your build produced:

grep -rhoE --include='*.html' '<meta name="description" content="[^"]*"' build | sort | uniq -c | sort -rn | head

Any description with a count above 1 is shared. Duplicate titles and descriptions on a docs site goes through finding and fixing them, including versioned docs.

4. Other head tags

For tags the front matter doesn't cover, there are three places, from widest to narrowest.

Every page: headTags in docusaurus.config.js, a list of objects with tagName, attributes and optionally innerHTML:

export default {
  headTags: [
    {
      tagName: 'link',
      attributes: {rel: 'preconnect', href: 'https://fonts.example.com'},
    },
  ],
};

One Markdown or MDX page: a <head> block in the file itself:

# Migrating from v2

<head>
  <meta property="og:type" content="article" />
</head>

A React page in src/pages: the @docusaurus/Head component. The default Layout also takes title and description props, which fill the same tags as front matter:

import Layout from '@theme/Layout';
import Head from '@docusaurus/Head';

export default function Pricing() {
  return (
    <Layout title="Pricing" description="Plans and limits for the hosted version.">
      <Head>
        <meta property="og:image" content="https://docs.example.com/img/social/pricing.png" />
      </Head>
      {/* page content */}
    </Layout>
  );
}

Tags set this way go into the static HTML at build time, so crawlers see them.

5. Keeping pages out of search

Don't use robots.txt for this: it stops crawling, not indexing, and a blocked page can't show Google its noindex.

6. The sitemap

@docusaurus/plugin-sitemap is part of preset-classic and writes sitemap.xml during docusaurus build (plugin-sitemap). Configure it through the preset:

presets: [
  [
    'classic',
    {
      sitemap: {
        lastmod: 'date',
        changefreq: null,
        priority: null,
        ignorePatterns: ['/tags/**', '/blog/page/**'],
      },
    },
  ],
],

Point crawlers at it with a line in static/robots.txt, Sitemap: https://docs.example.com/sitemap.xml, and submit it in Search Console.

7. Structured data

Recent Docusaurus 3 releases already emit BreadcrumbList JSON-LD on docs pages and BlogPosting on blog posts, so check a built page before adding your own. Site-wide data such as an Organization goes in headTags as a script with type: 'application/ld+json'; one-off data goes in the page's <head> block. JSON-LD for Astro and Docusaurus has working code, the types worth using and how to validate them.

8. Check the build

Everything above ends up in the HTML in build, so check that rather than the config. After npm run build, list pages without a description and pages without a preview image:

grep -rL --include='*.html' 'name="description"' build
grep -rL --include='*.html' 'property="og:image"' build

Then check a few deployed pages with the free SEO checker, which shows the Google result and the LinkedIn, Slack and X cards each page produces, or the site checker for up to 50 pages from your sitemap at once.

Automating it with Headlark

The settings above are one-time work. Writing a distinct title, description and image alt text for each of hundreds of docs is the part that doesn't get done. Headlark's build step runs after docusaurus build on the HTML in build:

"build": "docusaurus build && headlark build --site-url https://docs.example.com"

The first build after headlark is the output folder. If you set a baseUrl such as /repo/, include it: --site-url https://example.github.io/repo/. Set HEADLARK_KEY in the build environment.

Try it

The free plan covers one site and 25 pages. Add your site, copy its secret key, and your next build ships with SEO.