Docusaurus Blog SEO Checklist for Developer Tool Sites
A practical Docusaurus blog SEO checklist: url and trailingSlash, per-post front matter (description, image, title_meta), truncate markers, the sitemap plugin, unlisted vs draft, tags, feeds and a post-deploy curl check, for Docusaurus 3.
Blog on Docusaurus? See how Blogger Dog publishes into Docusaurus.
Why the Docusaurus blog is worth a checklist
Most Docusaurus sites start life as documentation. The blog plugin comes along for free with preset-classic, someone writes a launch post and two release notes, and then the blog sits there. That is a waste, because a Docusaurus blog is a very good place to rank. Every route is rendered to static HTML at build time, the theme uses semantic markup, the sitemap plugin is already installed, and the domain usually has real authority from people linking to the docs.
What Docusaurus does not do is make good choices for you about the things search engines actually read on each post: the description, the social image, which pages belong in the sitemap, and whether the URL a crawler finds is the same URL your canonical tag points at. Those are small settings, and they are easy to get wrong silently, because nothing in the build fails when a post has a weak description or when tag pages fill your sitemap.
This checklist covers what we would set up on any Docusaurus blog that is meant to bring in search traffic. It targets Docusaurus 3 (the option names below come from the 3.10.2 docs). If you run a different stack, the same ideas are covered for the App Router in the Next.js blog SEO checklist and for content collections in the Astro blog SEO checklist.
1. Get url, baseUrl and trailingSlash right first
Every absolute URL Docusaurus emits, including the canonical link, the og:url and every entry in sitemap.xml, is built from url plus baseUrl in docusaurus.config.js. The config reference defines url as the origin only (for example https://example.com, no path) and baseUrl as the path the site is served under, / by default.
Two common mistakes:
- Leaving
urlset to the template value or a staging domain. The site builds fine, and every canonical tag on the blog points at the wrong host. - Hosting the docs at
/docs/on a marketing domain but leavingbaseUrl: '/'. Assets break visibly, so this one usually gets caught. The canonical problem above does not.
Then decide on trailingSlash. By default it is undefined, which leaves URLs as written. Set to true, Docusaurus adds the slash to URLs and links; set to false, it removes it and emits post.html files. The right value depends on how your host serves static files, which is why the docs point you at your deployment guide before choosing. What matters for SEO is that one style wins everywhere: the URL your host serves without a redirect, the canonical, the internal links and the sitemap.
// docusaurus.config.js
export default {
url: 'https://example.com',
baseUrl: '/',
trailingSlash: false, // pick the style your host serves directly
onBrokenLinks: 'throw',
};
onBrokenLinks already defaults to 'throw' in production builds. Leave it there. A broken internal link on a blog is a crawl dead end, and failing the build is the cheapest place to catch it.
2. Give every post its own description
Docusaurus turns the description front matter into the meta description and og:description. If you leave it out, the blog plugin reference says it falls back to the first line of Markdown content. On many blogs that first line is an intro like "In this post we will look at..." which tells nobody anything.
Google is clear that it builds snippets mostly from page content and only sometimes uses the meta description, but also that "identical or similar descriptions on every page of a site aren't helpful" (Google snippet guidance). A specific, one or two sentence description per post is still the best lever you have on the snippet, and it is what gets shown when someone shares the link.
---
title: How We Cut Our CI Build Time in Half
description: The three caching changes that took our monorepo build from 14 to 7 minutes, with the GitHub Actions config for each.
image: ./ci-build-time.png
keywords: [ci build time, github actions cache, monorepo]
tags: [ci, performance]
---
3. Use title_meta when the headline and the search title should differ
A blog headline written for readers ("We rewrote our parser. Here is what broke.") is not always the query people type. Docusaurus has a front matter field for exactly this: title_meta sets the <title> and og:title while title stays the visible heading. It defaults to title, so you only add it when the two should differ.
---
title: We Rewrote Our Parser. Here Is What Broke.
title_meta: Migrating a TypeScript Parser to Rust, Lessons Learned
---
Keep the meta title close to the actual content. A search title that promises something the post does not deliver tends to earn a click and a fast bounce.
4. Add a social image per post
The image front matter becomes og:image, the preview image for links shared in Slack, X, LinkedIn and chat apps. Posts without one inherit whatever site-wide image your theme config sets, which means every post shares the same card.
For a developer audience a simple, readable card beats a stock photo: the post title, your logo, maybe one diagram. Put it next to the post (Docusaurus resolves relative paths) so it travels with the file. If you also want large preview cards on X, set the global twitter:card metadata once in themeConfig.metadata, as the Docusaurus SEO page shows:
themeConfig: {
metadata: [{name: 'twitter:card', content: 'summary_large_image'}],
},
While you are writing the post, give every in-body image real alt text. Docusaurus passes Markdown alt text straight through to the <img> tag; the image title attribute is only a tooltip.
5. Put a truncate marker in every post
The blog list page shows a summary of each post. Without a truncate marker, Docusaurus has to decide where the summary ends, and the plugin's onUntruncatedBlogPosts option defaults to 'warn' so you notice. Add the marker after the first paragraph or two:
The three caching changes that took our build from 14 to 7 minutes.
<!-- truncate -->
## The problem
In .mdx files use {/* truncate */} instead. For SEO this matters more than it looks: the list and tag pages are the pages crawlers walk to discover your posts, and a clean summary under each link gives them (and readers) context for what is behind it. If your team keeps forgetting, set onUntruncatedBlogPosts: 'throw' so the build tells them.
6. Know the difference between draft and unlisted
Both keep a post out of sight, in different ways, and mixing them up is a common way to leak or lose a page:
draft: truemakes the post available only in development. It is not in the production build at all.unlisted: truebuilds the post in production, but it is hidden from the list pages, excluded from the sitemap and marked so it is not indexed. It is reachable only by direct link.
Use draft for work in progress and unlisted for pages you want to share privately (a customer-specific write-up, a post waiting on a launch). Do not use unlisted for a post you want to rank and then forget to flip it; it will never show up in search.
7. Trim the sitemap to pages worth indexing
The @docusaurus/plugin-sitemap ships with preset-classic and writes sitemap.xml at [baseUrl]/sitemap.xml after a production build. By default it includes every route, including paginated list pages (/blog/page/2) and every tag page. On a blog with 40 posts and 60 tags, the tag pages can outnumber the posts.
The sitemap plugin reference has ignorePatterns and a createSitemapItems hook for exactly this:
// docusaurus.config.js, inside the preset-classic options
sitemap: {
lastmod: 'date',
changefreq: null,
priority: null,
ignorePatterns: ['/blog/tags/**'],
createSitemapItems: async ({defaultCreateSitemapItems, ...rest}) => {
const items = await defaultCreateSitemapItems(rest);
return items.filter((item) => !item.url.includes('/page/'));
},
},
Three notes on those values. lastmod is off by default (null); setting it to 'date' emits a real last-modified date for routes that have one. Google says it uses <lastmod> only when it is "consistently and verifiably" accurate, and that it ignores <priority> and <changefreq> entirely (Google's sitemap guide), so setting those two to null just shortens the file. And dropping tag pages from the sitemap is a judgment call: keep them if a tag page is a genuinely useful hub you would like to rank, drop them if they are thin lists of one or two posts.
Last-modified dates come from git history, so if your CI does a shallow clone, either fetch full history or expect the dates to be wrong.
8. Keep tags few and deliberate
Docusaurus 3 can validate tags against a tags.yml file in the blog folder. The onInlineTags option controls what happens when a post uses a tag that is not in that file, from 'ignore' to 'throw' (the default is 'warn').
From an SEO point of view, a short, curated tag list is better than one tag per post. Each tag creates a page; twenty tags with one post each are twenty thin pages competing with your posts. A tags.yml with a description per tag also gives those pages real content:
# blog/tags.yml
performance:
label: Performance
permalink: /performance
description: Posts about build, runtime and CI performance.
If you write posts with a tool or a teammate who is not in the habit of checking tags.yml, decide up front: either keep onInlineTags: 'warn' and prune later, or set it to 'throw' and add new tags on purpose.
9. Use the blog title and description you already have
The blog plugin has blogTitle and blogDescription options, both defaulting to 'Blog', and the reference lists them as SEO aids. They become the title and meta description of the blog list page. "Blog" is not a useful title for the page that links to everything you have written; something like "Acme Engineering Blog: CI, Monorepos and Build Performance" is.
blog: {
blogTitle: 'Acme Engineering Blog',
blogDescription: 'Deep dives on CI, monorepos and build performance from the Acme team.',
postsPerPage: 10,
blogSidebarCount: 10,
},
blogSidebarCount (5 by default) controls how many recent posts each post links to in its sidebar. Raising it a little gives crawlers more internal links from every post to the others, at no cost.
10. Keep the feeds on
feedOptions defaults to RSS and Atom, built only in production. Leave them on. Feeds are how aggregators and readers pick up new posts, and they are another place AI assistants and crawlers find fresh URLs. If you limit feed size with feedOptions.limit (20 by default), make sure the sitemap still lists everything.
Feeds also matter for being cited by AI answers, which depend on fresh, crawlable, clearly structured pages. We covered that side in how to get a developer blog cited in AI answers.
11. Link posts from the docs, and docs from the posts
This is where Docusaurus sites have an advantage over most blogs. The docs usually get the traffic and the backlinks. A "Related posts" admonition at the end of a relevant doc page, or a link from a how-to page to the post that explains the design decision behind it, passes that authority to the blog and gives crawlers a path in.
It works the other way too: a post that explains a feature should link to its docs page. Docusaurus resolves relative Markdown links between files at build time, and with onBrokenLinks: 'throw' a renamed page cannot leave a dead link behind.
12. Check the live page, not the build
A green build only proves the build. After the deploy, check what a crawler gets:
URL=https://example.com/blog/cut-build-time-in-half
curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" -L "$URL"
curl -s "$URL" | grep -Eo '<link rel="canonical"[^>]*>|<meta (name="description"|property="og:image")[^>]*>'
curl -s https://example.com/sitemap.xml | grep -c "<loc>"
You want a 200 without a redirect chain, a canonical that matches the URL exactly (same host, same trailing slash), a description and an image, and a sitemap count that roughly matches your post count plus the pages you meant to keep. Then request indexing for important new posts in Search Console. The same "is it actually live" check is why we verify every post we publish; the reasoning is in live URL verification for GitHub blog publishing.
The checklist in one place
url,baseUrlandtrailingSlashset for the real host;onBrokenLinks: 'throw'.- A specific
descriptionon every post. title_metawhere the headline is not the search title.- An
imageper post;twitter:cardset once globally; alt text on in-body images. - A truncate marker in every post.
draftfor work in progress,unlistedonly for private pages.- Sitemap trimmed with
ignorePatternsorcreateSitemapItems;lastmod: 'date'with full git history. - A short
tags.ymlwith descriptions. - Real
blogTitleandblogDescription. - RSS and Atom feeds left on.
- Docs and posts linking to each other.
- A curl check of the live post, canonical and sitemap after each deploy.
FAQ
Is Docusaurus good for SEO?
Yes. Every route is static HTML, the theme uses semantic markup, the sitemap plugin is installed with preset-classic, and blog posts support rich results from the date, authors and image out of the box. The gaps are per-post choices (descriptions, images) and sitemap contents.
Should my blog live in the same Docusaurus site as the docs?
Usually yes. Keeping it on the same domain means the blog benefits from the links the docs already earn, and docs and posts can link to each other with build-time checks.
Do I need authors on every post?
No, but they help. Docusaurus uses authors for the post byline and its structured data. If you keep an authors.yml, reference keys from it; Docusaurus prefers authors over the deprecated author_* fields.
Why does my post date look wrong?
If date is not in the front matter, Docusaurus takes it from the file or folder name (such as 2026-10-24-my-post.md) and otherwise from the file creation time, which changes on every fresh clone in CI. Always set date explicitly.
How do I get new posts indexed faster?
Make sure the sitemap is in Search Console, link each new post from the blog list, the sidebar and at least one relevant doc page, and request indexing for the posts that matter most.
Keep the blog moving
A well-configured Docusaurus blog with no new posts is still a quiet blog. Blogger Dog researches and writes cited SEO and AEO posts and commits them into the blog folder your Docusaurus site already serves, as Markdown with title, date, description, tags and slug front matter, then checks that the post is live before counting it. The details are on the Docusaurus integration page, and it works the same way for Next.js, Astro, Hugo, Jekyll and Gatsby, on GitHub or GitLab.
You get 3 posts free, then plans start at $19/month. See pricing and plans.