Hexo SEO

How to do SEO on a Hexo site — the Node.js static site generator. Hexo outputs pure HTML so content is crawlable on the first fetch, but sitemaps, robots.txt, meta descriptions, canonicals, structured data, and stable permalinks all depend on plugins and config.

First published: Jun 26, 2026 · Last updated: Jul 18, 2026 · Advanced
demand #5 in Static Site Generators#49 in Platform SEO#285 in Technical SEO#381 on the site

Hexo is a Node.js static site generator: hexo generate compiles Markdown into pure static HTML, so Googlebot reads your full content on the first fetch with no JavaScript rendering queue. That's the crawlability floor — but it's not finished SEO. Most default themes ship no sitemap, no robots.txt, no per-post meta description, no canonical, and no structured data; you add those with _config.yml settings, front matter fields, theme edits, and plugins (hexo-generator-sitemap, hexo-generator-robotstxt, hexo-generator-feed, hexo-abbrlink). The big traps are a wrong url: in _config.yml, the date-based default permalink that breaks links on rename, and auto-generated category/tag archives that create thin duplicate content. For Baidu-facing sites, the static output removes a real crawling obstacle — though that's not a guarantee of Baidu indexing or ranking.

TL;DR — Hexo (hexo generate) emits pure static HTML, so your content is in the first byte of the response — no Web Rendering ServiceTurning HTML, CSS, and JavaScript into the final visual page and DOM., no Wave 2 delay. That’s the crawlabilityCrawlability is how well search engine crawlers can discover, access, and fetch a site's pages. A crawlability issue is any technical condition — blocked access, broken links, server failures, or bloated URL inventory — that stops pages from reaching the index. floor, not finished SEO. Default themes ship no sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing., no robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere., no per-post meta descriptionThe meta description is an HTML head tag — `<meta name=\"description\" content=\"…\">` — that suggests a short summary of the page for the search snippet. It's not a Google ranking factor, and Google rewrites it the majority of the time, but a good one can still lift click-through., no canonical, and no structured dataStructured data is a standardized way of labeling page content (using the schema.org vocabulary in JSON-LD, Microdata, or RDFa) so search engines can understand its meaning. It's not a direct ranking factor — its value is rich results and entity understanding.; you add them via _config.yml, front matter, theme template edits, and plugins (hexo-generator-sitemap, hexo-generator-robotstxt, hexo-generator-feed, hexo-abbrlink, hexo-indexnow). The three traps that actually bite: a wrong url: in _config.yml breaks canonicals/sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing./feed; the date-based default permalink ties URLs to filenames so a rename breaks links; and auto-generated category/tag archives create thin duplicate contentThe same or very similar primary content reachable at more than one URL. There's no general duplicate content penalty — the real costs are possible signal dilution, the wrong URL getting chosen, and less-efficient crawling.. For Baidu-facing sites the static output is a real advantage.

Hexo’s static output is the crawling advantage

hexo generate runs your Markdown and theme templates through a build step and writes a folder of finished HTML, CSS, and assets. The SEO-relevant fact is when the HTML exists: at build time, once, for everyone — not per request, and not in the browser. Evidence for this claim The Hexo generate command builds static files into the configured public directory. Scope: Hexo generate command. Confidence: high · Verified: Hexo: Commands

Google describes a two-phase process for JavaScript pages: Wave 1 fetches the raw HTML and indexes static content immediately; Wave 2 is a deferred headless-Chromium renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. queue that can take seconds to days. Hexo output skips Wave 2 entirely — every page is raw HTML on the first fetch. Google’s own guidance is blunt that this is the recommended path: dynamic rendering was “a workaround and not a long-term solution… use server-side rendering, static rendering, or hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. as a solution,” and Hexo produces static rendering, one of the three explicitly recommended approaches.

Two more wins fall out of this:

  • Crawlable links by default. Hexo templates emit plain <a href> anchors in static HTML, so they satisfy Google’s requirement that links be real, crawlable URLs — no JavaScript-generated navigation to design around.
  • Cheap to crawl. Static files served from a CDN have effectively zero server-processing cost per request, so GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. can crawl at its allowed rate without tripping crawl-rate throttling. As Gary Illyes put it, “if you are making expensive database calls, that’s going to cost the server a lot” — Hexo makes none.

The caveat: zero render-blockingRender-blocking resources are CSS and synchronous JavaScript files a browser must download and process before it can paint any visible content. They sit on the critical rendering path and delay First Contentful Paint and Largest Contentful Paint. for crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. is not the same as zero render-blocking for users. Theme JS/CSS still affects Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. (more below).

Configure _config.yml first — url: is the big one

Set these global keys before your first deploy:

url: https://yourdomain.com       # must match your live domain, with https://
permalink: :title/                # or :year/:month/:day/:title/ (the default)
title: Site Title
description: Global meta description fallback
author: Your Name
language: en
meta_generator: false             # removes the <meta name="generator" content="Hexo"> tag
  • url: is the single most consequential setting. It’s the site’s configured base URL, which themes and generators can use for canonical tagsA rel=\"canonical\" annotation — in the HTML <head> or an HTTP Link header — that tells search engines which URL is the preferred version of duplicate or near-duplicate content., feeds, sitemaps, and paginationPagination splits a large set of content — product listings, blog archives, search results — across multiple sequentially numbered URLs. For SEO, each paginated page should be crawlable, indexable, and self-canonical; Google no longer uses rel=prev/next, but Bing still does.. A wrong value can propagate into those outputs. Evidence for this claim Hexo's url configuration supplies the website URL used by themes and generators. Scope: Exact downstream fields depend on the installed theme and plugins. Confidence: high · Verified: Hexo: Configuration
  • permalink: — the default :year/:month/:day/:title/ produces date-based URLs that look stale over time and tie the slug to the filename. Cleaner options are /:title/ or /:category/:title/.
  • meta_generator: false strips Hexo’s generator fingerprint from <head>.
  • Deploying to a subdirectory? url: alone isn’t enough — set root: to the subdirectory path too (e.g. root: /blog/). Hexo’s url_for helper builds root-relative links, while full_url_for prepends the full url: value; get root: wrong on a subdirectory deploy and internal linksAn internal link is a hyperlink from one page on a website to another page on the same website. Internal links help search engines discover your pages and pass ranking signals (PageRank and anchor-text context) between them., feeds, and sitemap entries end up pointing at the domain root instead of the actual path.
  • Config precedence: the site’s _config.yml sets the base url/root/permalink values; a theme’s own config (its bundled _config.yml, or a _config.<theme>.yml at the project root) layers on top for display and template settings, but doesn’t override the site-level URL structureURL structure is how the parts of a web address — scheme, domain, path, query string, and fragment — are organized and formatted. It mostly affects crawling, usability, and how engines understand a page, not rankings directly. — a theme can’t fix a wrong url:.
  • Testing in safe mode? hexo server --safe (or hexo --safe) disables every plugin and script, so the output you’re viewing is missing your sitemap, robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere., and any SEO plugin’s tags. Don’t use a safe-mode preview to check SEO output — run a normal hexo generate first.

Front matter SEO controls (and what’s missing)

Native Hexo front matter you can rely on:

---
title: "Article Title for the <title> tag"
date: 2024-01-15
updated: 2024-06-01
tags: [tag1, tag2]
categories: [Category]
permalink: /custom-url/           # overrides the global pattern for this post
published: true
sitemap: false                    # exclude this post from sitemap.xml
---

What is not in native front matter and needs theme support or a template edit:

  • description (the meta descriptionThe meta description is an HTML head tag — `<meta name=\"description\" content=\"…\">` — that suggests a short summary of the page for the search snippet. It's not a Google ranking factor, and Google rewrites it the majority of the time, but a good one can still lift click-through.) — most themes don’t print it without a patch.
  • keywords (per-post keyword meta) — and largely pointless anyway; Google has ignored <meta name="keywords"> since 2009. Treat keywords as an organizational aid, not a ranking lever.
  • canonical (override the canonical URL) — theme-dependent.

Keep the updated: field current when you revise a post — sitemap freshness depends on it (next section).

Essential plugins

Sitemap — hexo-generator-sitemap (official):

npm install hexo-generator-sitemap --save
sitemap:
  path:
    - sitemap.xml
  tags: true
  categories: true

It reads lastmod from each post’s updated field (falling back to date) — which matters because Bing warns that lastmod “should reflect the true last modification time of the page content, not the sitemap file itself.” After hexo generate, submit the resulting sitemap.xml in Google Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results. and Bing Webmaster ToolsMicrosoft's free portal for monitoring and improving how a site appears in Bing search — the peer to Google Search Console, plus IndexNow instant indexing, richer backlink data, and keyword volumes. Because Bing's index also feeds Microsoft Copilot, it doubles as a window into AI-search visibility.. And heed Bing’s other warning: “Do not submit a static sitemap and forget it. New pages won’t get picked up” — for Hexo that means re-deploying and re-submitting (or using IndexNowIndexNow is an open push protocol that lets you instantly tell participating search engines (Bing, Yandex, Naver, Seznam, and Yep) which URLs you've added, changed, or removed via a simple HTTP request — and one submission is shared across all of them. Google does not use it.) after every new post.

TIP Validate Hexo’s generated sitemap, not just the plugin settings

The sitemap can be well-formed while a wrong `_config.yml` `url:` turns its locations into relative or incorrect URLs. Test the deployed artifact after a production generate.

Check the sitemap with my free XML Sitemap Validator Free

  1. Generate and deploy with the production base URL.
  2. Validate locations, lastmod values, duplicates, and XML syntax in the public sitemap.
  3. Fix `_config.yml`, front matter, or the generator source, then regenerate and retest.
A bad Hexo base URL propagates into sitemap locations even when the sitemap plugin itself runs successfully.

The validator reports that the loc element is not an absolute URL and says sitemap URLs must be fully qualified rather than relative paths.

robots.txt — hexo-generator-robotstxt:

npm install hexo-generator-robotstxt --save
robotstxt:
  useragent: "*"
  allow:
    - /
  disallow:
    - /private/
  sitemap: https://yourdomain.com/sitemap.xml

Without it, Hexo produces no robots.txt at all — no sitemap pointer for crawlers.

RSS — hexo-generator-feed: relevant for aggregators and Google News eligibility; John Mueller includes an RSS feed on his own static site.

IndexNow — hexo-indexnow: Bing recommends pairing sitemaps with IndexNow for real-time, URL-level submission. Drop the generated key file into source/ so it deploys as yourdomain.com/<key>.txt. Note this is for Bing, Yandex, and others — not Google, which doesn’t use IndexNow for general pages.

The default permalink has two SEO problems: renaming the Markdown file changes the URL (because the slug comes from the filename), and non-Latin titles (Chinese, emoji) produce garbled URLs. hexo-abbrlink decouples the URL from the filename by writing a stable hash back into front matter:

npm install hexo-abbrlink --save
permalink: posts/:abbrlink/
abbrlink:
  alg: crc32    # crc16 or crc32
  rep: hex      # dec or hex

You get something like /posts/3a9c2b1d/ that survives file renames, category changes, and date edits. The underlying rule stands either way: once a URL is published, don’t change it without a 301 redirectA 301 redirect is the HTTP status code for a permanent move: it tells browsers and search engines a URL has moved for good, and it's the strongest signal for consolidating a page's ranking signals onto the new URL. Google says permanent redirects don't cause a loss in PageRank. — a rename without one destroys link equity and any accumulated ranking signals.

Two more permalink pitfalls worth knowing before you pick a pattern:

  • Don’t use the :id token. Hexo documents post IDs as not persistent across a database cache reset — if your permalink pattern includes :id, a cache rebuild can silently change every post’s URL. Stick to :title, :abbrlink, or another stable token.
  • trailing_index and trailing_html are separate controls, not one setting. Hexo’s pretty_urls config toggles them independently — one governs whether a trailing index.html is stripped from generated links, the other whether a trailing slash is added. If these don’t match how your host actually resolves URLs (some hosts redirectA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. /post to /post/, others don’t), you can end up with internal links, canonicals, and sitemap entries disagreeing with the URL the server actually serves.

NexT theme SEO configuration

NexT is the most-used Hexo theme, configured in _config.next.yml:

canonical: true                 # builds canonical from url: + page path

webmaster_tools:
  google: VERIFICATION_STRING
  bing: VERIFICATION_STRING
  baidu: VERIFICATION_STRING

open_graph:
  enable: true
  twitter_card: summary_large_image

The NexT meta-description gap: NexT does not automatically inject a per-post <meta name="description"> from the front matter description:. You patch the theme’s head template to fall back from page.description to the site description. This is the single most common NexT SEO miss. (NexT does support canonical: true and built-in Open GraphOpen Graph (OG) tags are `<meta>` elements in a page's head, defined by the Open Graph protocol (ogp.me, created by Facebook), that describe a page as a shareable object — its title, description, image, URL, and type. They control how a link preview card looks when the page is shared on Facebook, LinkedIn, Slack, Discord, WhatsApp, and iMessage. They are not a direct Google ranking factor, though Google reads og:title, og:image, and og:site_name as inputs to how a result appears. / Twitter Cards, so those at least work once enabled.)

For non-NexT themes, you usually create or edit a head partial to emit og:title, og:description, og:image, og:url, and twitter:card — support varies widely by theme.

Who owns each tag when a theme and a plugin both emit it. A theme’s built-in Open Graph/canonical support and an all-in-one plugin like hexo-seo (which generates its own breadcrumb, website, and article schemaArticle schema (Article, NewsArticle, or BlogPosting) is structured data marking up a written piece's headline, author, and dates — Google labels it the \"Article rich result,\" but it's a better-displayed existing result, not a distinct new card, and it isn't required for Top Stories or Discover eligibility.) can both try to write the same tag. Turning both on produces duplicate or conflicting <meta> tags, canonicals, or JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. blocks in the same <head>. Pick one layer as the owner for each tag category — theme or plugin, not both — and disable the corresponding feature on the other before you ship.

Category and tag pages: the duplicate-content trap

Hexo auto-generates an archive page for every category and every tag, each filled with post excerpts that also appear on the posts themselves. At scale that’s hundreds of thin pages competing for the same queries and diluting crawl attention. Your options:

  1. sitemap: false per archive (impractical at scale).
  2. Theme-level noindex on the category/tag archive templates.
  3. Keep them indexed only if a category is a genuine, curated topic hub with real value.
  4. Self-referencing canonical on each paginated archive page (page 2 canonicalizes to itself, not page 1).

One caveat on option 2: long-term noindex eventually makes Google stop following the links on that page, so noindexing tag pages can reduce crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. of posts that are only linked from those tags. Make sure such posts are reachable some other way.

Your route inventory is bigger than source/. Category, tag, archive, and pagination pages don’t come from Markdown files — Hexo’s generator API is what actually creates them, and themes or plugins can register their own generators too. That means the crawlable URL surface can silently expand beyond what you’d expect from browsing your source/ folder. After installing or updating a theme or plugin, run hexo generate and diff the route list in public/ rather than assuming you know every URL Hexo is producing.

Structured data / JSON-LD

Hexo injects no structured data by default. Either add a BlogPosting JSON-LD block to your theme’s post template (headline, datePublished, dateModified, author, publisher, url) or use an all-in-one plugin like hexo-seo that generates breadcrumb, website, and article schema for you.

Core Web Vitals

Static output is fast, but themes can undo that. NexT and many themes bundle large JS/CSS and icon-font libraries (Font Awesome is a frequent LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good./render-blocking culprit). Audit with LighthouseLighthouse is Google's free, open-source tool that audits a page under simulated lab conditions and scores it 0–100 across Performance, Accessibility, Best Practices, and SEO. It's lab data — useful for debugging, not a ranking signal., then reach for hexo-all-minifier (HTML/CSS/JS compressionCompression (HTTP content encoding) shrinks text-based responses — HTML, CSS, JS, JSON, SVG, XML sitemaps — before they're sent over the network, using an algorithm like Gzip, Brotli, or Zstd, so the browser or crawler downloads fewer bytes. It's not a ranking factor, but it speeds up page loads and helps pages stay under crawler fetch limits. at build time), hexo-lazyload-image, and defer/async on third-party scripts like analytics and ads.

Targeting Baidu (Chinese-audience sites)

Baidu’s crawler handles JavaScript poorly, so Hexo’s static HTML removes one obstacle to Baidu discovery. Add baidu_site_verification in NexT, generate a Baidu-format sitemap with hexo-generator-baidu-sitemap, and push URLs actively with hexo-submit-urls-to-search-engine. One hosting note: GitHub Pages is blocked in mainland China — use Gitee Pages, Cloudflare, or a Chinese CDN for Baidu traffic.

Treat that as a crawlabilityCrawlability is how well search engine crawlers can discover, access, and fetch a site's pages. A crawlability issue is any technical condition — blocked access, broken links, server failures, or bloated URL inventory — that stops pages from reaching the index. advantage, not a guarantee. Static HTML doesn’t by itself prove Baidu is discovering, indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed., or ranking your pages — confirm actual crawl activity and indexing status directly in Baidu Ziyuan (Baidu’s webmaster tools) rather than assuming static output alone secures visibility.

Deploying to GitHub Pages — the SEO gotchas

  • Set url: to your live address (custom domain with https://) before the first deploy.
  • Put a CNAME file in source/ for custom domains so it isn’t reset on every hexo deploy.
  • Enforce HTTPSHTTPS is the encrypted version of HTTP — it uses TLS to authenticate the server and protect data in transit between a browser and a website. Google announced it as a lightweight ranking signal in 2014 and today conditionally prefers HTTPS pages as canonical; Chrome marks plain HTTP pages 'Not Secure.' in repo Settings → Pages after DNS propagates.
  • Prefer a GitHub Actions workflow over hexo-deployer-git to avoid CNAME-reset issues.

Hexo vs. Hugo vs. Jekyll vs. Astro, for SEO

All four ship pure static HTML, so the indexability floor is identical — there’s no framework-level SEO disadvantage in Hexo. The differences are operational: Hugo (Go) builds far faster at large scale and has a built-in sitemap; Jekyll is the GitHub Pages default; Astro ships zero JS by default via islands. Hexo’s edge is a rich npm plugin ecosystem and a large Chinese-language community. Content quality, not the generator, decides rankings — as Mueller said of switching generators, “Will your site’s SEO suddenly explode? No. Also No.”

For the cluster overview and the other generators, see Static Site GeneratorsA build tool that pre-renders every page into static HTML files at deploy time, so the server delivers complete HTML without executing code per request — eliminating JavaScript rendering delays for search engine crawlers.; for the broader rendering picture, see JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript..

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.