Editor comparing a printed sitemap outline with a muted topic list on a widescreen monitor

Search engines already know how to crawl a discussion site. Discourse’s job is to hand them HTML they can read, a sitemap that matches public topics, a robots.txt that does not accidentally noindex the community, and stable /t/slug/id URLs you do not break when a title changes. That is the 2026 setup. It is not a bag of meta-description plugins and it is not the XenForo SEO options screen.

The XenForo SEO article owns Setup → Options → Search engine optimization (SEO): friendly URLs, title-in-path, XML sitemap generation, IndexNow, and the rewrite that makes /threads/title.12345/ load. This article does not reprint that ACP tour. Discourse does not have that page. It has a core sitemap plugin, prerendered HTML for spiders, IndexNow on a Sidekiq hourly batch, and a URL shape that was designed after friendly URLs were normal.

This is also not the hosting cookbook (RAM, rebuilds, CDN) and not the three-way matrix. Fast hosting helps Core Web Vitals; it does not write a topic that answers a query. Official facts below come from the Discourse SEO notes in the research pack (core sitemap behaviour, IndexNow, prerender, PR 18201, /t/ permalinks) plus Meta’s public install/optional-features list. If a checkbox is not in those sources, this article does not invent it.

What “SEO” means on a Discourse site

A Discourse site is a pile of URLs that grow every time someone clicks Post. Google does not need a marketing landing page to find /t/how-do-i-reset-smtp/18421. It needs four things:

  1. HTML, not an empty JavaScript shell. Discourse prerenders for spiders.
  2. A list of the URLs you actually want indexed. /sitemap.xml, plus /news.xml for what changed recently.
  3. A hint when a URL changed. IndexNow, on a schedule, not a webhook per post.
  4. A robots.txt that allows indexing unless you are running a private community on purpose.

Everything else that people call “forum SEO” is either content (a topic that answers a real query) or architecture (public Support, few categories, tags instead of empty rooms). Discourse will not invent keyword planners. It will give crawlers a clean document and a map.

Two product facts that change how you think about this versus XenForo:

  • Discourse URLs are friendly by default. You do not spend a morning renaming htaccess.txt so /index.php?threads= goes away. You spend the morning deciding which categories guests can read.
  • The default homepage is Latest, not a board index. What ranks is usually a topic, sometimes a category or tag list, almost never “the forum home” as a money page. Plan content accordingly.

If the site is private (login to read), most of this article becomes “make sure you did that on purpose.” A login wall is a valid product. It is also how you get an empty Google result. Decide on day one. The beginner’s guide already said so; SEO is where that decision shows up in Search Console.

The URL you are actually ranking

Discourse topic permalinks look like:

/t/slug/id

Examples of the shape (not a promise about your slug algorithm):

  • /t/how-do-i-reset-smtp/18421
  • /t/how-do-i-reset-smtp/18421/12 — a specific post in the topic
  • Category and tag lists have their own paths (the UI is the source of truth for the exact prefix your site shows).

The numeric id is the durable part. Titles change; ids do not. That is the same idea as XenForo’s .12345 in /threads/title.12345/, with a different separator and no threads/ folder.

Compare, because this is the migration problem:

Discourse XenForo (friendly URLs on)
Topic / thread /t/slug/id /threads/title.id/
Without title in path Still /t/…/id (id remains) Optional /threads/12345/
Engine querystring era Not the default /index.php?threads/… if friendly URLs were off
Who owns rewrite Official Docker / hosted Apache .htaccess, nginx try_files, IIS web.config

XenForo’s SEO article is about turning friendly URLs on without 404ing the front end. Discourse’s SEO article is about not breaking /t/id after a migration, and about making sure the topic is public HTML.

Old links from phpBB (viewtopic.php?t=), vBulletin (showthread.php), or XenForo (/threads/title.id/ or threads/{id}) do not become Discourse URLs by magic. The stock XenForo importer on the Discourse side creates permalinks for board/{node_name} and threads/{thread_id}. Other sources need a permalink map you actually test. A 301 from the old host to the new /t/slug/id is the ranking project. Pretty themes are not.

If you are still on XenForo and the only complaint is “we never turned on friendly URLs,” that is an afternoon on XenForo, not a reason to change products. The two-way matrix already said that. If you are changing products, budget redirects as a workstream, not a footnote.

Core sitemap: /sitemap.xml and /news.xml

Discourse ships a core sitemap plugin. Behaviour the research pack locks down:

Endpoint What it is
/sitemap.xml The sitemap index / map of public content
/news.xml Public topics from the last 3 days
robots.txt Includes a Sitemap line pointing at the sitemap

/news.xml is the “what just changed” feed for crawlers that like a short, fresh list. It is not your Atom/RSS reader for humans, and it is not a Google News publisher product just because the path says news. It is public topics, three days. Do not promise it includes hidden staff topics. It should not.

Operational checks after a new site is up:

  1. Open /sitemap.xml as a guest (private window). You should see URLs you intend to rank.
  2. Open /news.xml after you have posted a public topic. It should appear within the window the endpoint describes.
  3. View /robots.txt. Confirm there is a Sitemap line and that you have not noindexed the whole site by accident.

If /sitemap.xml is empty, you do not have an SEO plugin problem. You have a visibility problem: categories are staff-only, the site requires login to read, or you have no public topics. Fix the IA. Then look at the sitemap again.

XenForo’s equivalent is Setup → Options → XML sitemap generation plus Extra sitemap URLs for pages the generator will not invent. Discourse does not ask you to design that options group. It generates from public content. Your job is to keep Support and Docs public if you want them in the map.

robots.txt and allow index in robots txt

Discourse has a site setting the research pack names: allow index in robots txt. Default: on.

When it is on, robots.txt tells well-behaved crawlers they may index. When you turn it off, you are asking to stay out of search. That is the right switch for a private staff community you stood up on a discourse.diy hostname. It is the wrong switch for a public support site you later forgot you flipped during testing.

Do not edit robots.txt on the VPS like a WordPress file. Use the site setting. Rebuild folklore from 2016 that says “just paste Disallow: /” is how people disappear a community and then blame Google.

If you need to keep one category off Google (Internal, Staff, a customer-named room), that is category security, not a robots.txt hobby. Hidden-from-guests categories should not appear in the sitemap. Verify with a logged-out crawl of /sitemap.xml, not with hope.

IndexNow: hourly Sidekiq batches, not a per-post ping

Discourse supports IndexNow. The important operational fact: it goes out via Sidekiq in hourly batches. It is not a ping on every post save.

That is different in rhythm from how people talk about XenForo’s IndexNow checkbox (on the XF SEO options page — see that article for the ACP label). Both products are trying to tell participating engines “this URL changed.” Discourse does it on a schedule. If you posted a support answer at 14:05 and you are staring at Bing at 14:12, you have not been stood up. Wait for the batch. Then look at Sidekiq if you self-host and nothing ever moves.

Self-host implications:

  • Sidekiq must be running. On the official Docker install it is part of the stack. If you “improved” the install and killed the job runner, IndexNow and a lot of other things die silently.
  • Hourly is the documented batching. This article will not invent a site setting that changes it to “every 30 seconds.”
  • IndexNow is a hint, not a ranking boost. A sitemap plus good public HTML still does the crawl. IndexNow is how some engines hear about changes faster.

Hosted: you do not operate Sidekiq. You still should not expect a per-post instant index.

Do not install a third-party “ping all search engines” plugin because hourly feels slow. You will spend a rebuild on a problem you do not have.

Prerendered HTML for spiders

Discourse is a modern Ember front end. Humans get an application. Spiders get prerendered HTML. That sentence is the whole reason Discourse is not “bad for SEO because it is JavaScript.”

What to do with that fact:

  • Test as Googlebot (Search Console URL Inspection, or a fetch with a crawler user-agent) and confirm the topic title and the first post appear in the HTML. If you only ever test as a logged-in Ember user, you are not testing SEO.
  • Do not “fix SEO” by turning the site into a static export. You already have prerender.
  • A theme that breaks the document outline can still hurt. Custom headers, hidden titles, and display:none on the post body are how people take a working prerender and make it useless. Theme components are not free.

Every public URL also exists as a .json structured endpoint. /t/slug/id.json is a real, first-class representation of the topic. That is excellent for integrations and debugging. It is not a second page you want indexed as a duplicate of the HTML. Official crawler handling is supposed to keep this sane; your job is not to advertise .json URLs in the sitemap. Link humans to /t/slug/id. Let the JSON be an API.

If you are writing a custom front end that reads .json and you expect Google to rank that front end, you have left Discourse SEO and entered “we built a different website.” This article will not pretend that is a site setting.

The h1 history (PR 18201)

For a long time Discourse had a duplicate <h1> problem: the site header used an h1 and the topic title used an h1. Crawlers and accessibility trees saw two top-level headings on every topic.

Core PR 18201 removed the header h1 on topic, tag, and category pages so the content title can be the h1. That is the current core behaviour you should expect on a reasonably up-to-date site.

The documented tradeoff: the homepage can then lack an h1 unless the theme provides one. Latest as home is a list. Lists do not always ship a pretty document title as h1.

What you should do:

  • On a topic, view source / inspector: one h1, and it should be the topic title (or the equivalent your theme still shows as the main heading). If your theme put the site name back as h1 on topics, you undid the PR. Fix the theme.
  • On Latest / the homepage, decide whether you care. A community whose money pages are topics can live with a homepage that is not a brochure. If marketing insists the homepage is a landing page, that is a theme job (or a site in front of Discourse), not a sitemap job.
  • Do not install an “SEO h1” plugin to fight core. Update Discourse. Then theme.

This is the opposite of XenForo’s usual SEO ticket (“we still have index.php? in the address bar”). Discourse’s historical ticket was “two h1s.” Core already took the ticket. Your leftover work is themes and the homepage.

Public versus private: the decision that beats every plugin

Choice What Google sees When it is correct
Guests can read Support + Docs + solved topics Individual /t/ URLs + sitemap Public product support, open source, most technical communities
Login to read everything A login page Customer-only, staff, paid community
Guests can read lists but not posts Frustration Almost never. Pick a side
Mixed: some categories public Those categories only Internal + public Support on one site

A technical community that wants Solved topics to replace tickets must let guests read Support and Docs. Otherwise you are running a private helpdesk with extra steps. SEO is how the next customer finds last month’s solution without opening a new topic.

If you migrate a public XenForo board to a login-walled Discourse “for launch,” you donated the rankings. Put the wall on Internal and Staff. Leave Support public. Restore the wall later if legal actually requires it.

What Discourse will not do for you (and what XenForo’s SEO page also will not do)

Neither product:

  • Writes a topic that matches a query. Staff still have to answer, and someone still has to mark Solved so the next crawler (and the next human) sees the answer near the top.
  • Fixes a 90-node (or 90-category) maze. Empty rooms are SEO waste on both products. Broad rooms plus tags/prefixes is still the rule.
  • Replaces Core Web Vitals work. Hosted Discourse includes a CDN on paid plans; self-host CDN is optional (Meta 14857). Slow TTFB is a hosting article, not an h1 article.
  • Invents per-topic meta description fields as a core ACP religion. If your theme or a plugin adds that, it is extra. This article will not document a setting that is not in the core SEO pack.

XenForo-specific work you do not repeat on Discourse:

  • Enabling Use full friendly URLs and praying mod_rewrite is on.
  • nginx try_files plus internal locations for src/ and internal_data/.
  • Extra sitemap URLs for page nodes. Discourse docs are topics (or Doc Categories), already in the topic sitemap if they are public.

Discourse-specific work you do not pretend XenForo already solved the same way:

  • Prerender / crawler HTML (XF is server-rendered already).
  • /news.xml as a three-day public-topic list.
  • IndexNow as an hourly Sidekiq batch.
  • Header h1 removal on topic/tag/category after PR 18201.

If you are comparing products only on SEO, you are usually comparing URL migration cost, not “which engine Google likes.” Google likes public HTML that answers the query. Both can emit that.

Leaving XenForo, phpBB, or anything with a different path scheme is an SEO project even if the content import is perfect.

Discourse-side XenForo import (stock xenforo.rb) creates permalinks:

  • board/{node_name}
  • threads/{thread_id}

That is not the same as XenForo’s full /threads/title.12345/ pretty path, and it is not showthread.php?t=. You still need:

  1. A map of old URLs → Discourse topic ids (the import log / permalinks table is the start, not the finish).
  2. 301s on the public hostname people actually have in Google. If the hostname changes, do the host redirect and the path redirect.
  3. A crawl of the old sitemap versus the new /sitemap.xml until the important threads 301.

XenForo official importers, in the other direction, list Discourse 3.x as a source and can emit redirect scripts from the import log. The arrows are not symmetric — the matrix already owns that. SEO cares because the side that owns the importer owns the easy 301s.

What dies in a stock XF→Discourse import (page nodes, XFRM, XFMG, custom thread fields, most add-on tables) also dies as URLs. Those URLs need a manual map or they 404. 404 is worse than a 301 to the nearest category.

Do not launch the new hostname until:

  • Twenty random old thread URLs from Search Console 301 to the right topic.
  • /sitemap.xml on the new site lists public topics.
  • robots.txt allows index (unless you are private).
  • A Googlebot-flavoured fetch shows prerendered titles.

Rebuilds (5–10 minutes) are brief outages. They are not a reason to noindex the site “until we are ready” and then forget.

A practical Discourse SEO checklist

Visibility

  • Guests can open a Support topic and a Docs topic in a private window
  • Staff / Internal categories do not appear in /sitemap.xml
  • allow index in robots txt matches the product (on for public)
  • robots.txt contains a Sitemap line

Maps and pings

  • /sitemap.xml loads and lists real public URLs
  • /news.xml shows recent public topics (last 3 days)
  • Sidekiq is healthy if you self-host (IndexNow batches hourly)

Documents

  • Topic pages: one main h1 (PR 18201 behaviour; theme does not reintroduce a header h1)
  • Homepage h1: you either themed one or you accepted Latest without it
  • Crawler fetch shows post HTML, not an empty shell
  • You are not linking humans to .json URLs

Migrations (if you moved)

  • Old thread / topic URLs 301
  • threads/{id} and board/{node} permalinks tested if the source was XenForo
  • Page-node and add-on URLs have a manual home or a 410 you chose
  • Hostname change has a host-level 301 as well

Content habits that actually move the needle

  • Support uses Solved so the answer is in the first screen of HTML (technical community)
  • Few categories; tags instead of empty rooms
  • One question per topic (duplicate titles are how you compete with yourself)

What not to do

  • Do not noindex a public launch “temporarily.”
  • Do not rebuild a 40-forum tree as 40 categories and expect category pages to rank. They will be thin.
  • Do not paste XenForo nginx internal blocks into a Discourse box. Different filesystem, different problem.
  • Do not advertise /t/slug/id.json in the sitemap.
  • Do not fight PR 18201 with a theme that puts the logo back in an h1 on every topic.
  • Do not expect IndexNow in the same minute as the post.
  • Do not treat a CDN as SEO. It is latency.
  • Do not skip 301s because “Google will find the new URLs eventually.” Eventually is a year of duplicate-or-dead listings.

Takeaways

  • Discourse SEO is prerendered HTML + /sitemap.xml + /news.xml (public topics, last 3 days) + robots.txt with a Sitemap line + IndexNow on an hourly Sidekiq batch. It is not an XF-style friendly-URL options page.
  • allow index in robots txt defaults on. Flip it only for sites that must stay out of Google.
  • Topic URLs are /t/slug/id. The id is the permalink. XenForo’s /threads/title.id/ is a redirect project, not a setting.
  • PR 18201 removed the extra header h1 on topic/tag/category pages. The homepage may lack an h1 unless you theme one.
  • Every public URL has a .json twin. That is an API. Do not rank it on purpose.
  • Versus XenForo SEO: XF spends time on rewrites and an ACP sitemap group; Discourse spends time on visibility, prerender, and migration 301s.
  • Private categories belong behind security, not a clever robots.txt. Public Support is how a technical community earns the next visit without a new ticket.

If guests can read a solved topic, the sitemap lists it, robots.txt allows it, and the old /threads/ URLs 301, you have done Discourse SEO. The next ranking problem is whether anyone wrote the answer.