Repository navigation
Serve Notion's server-rendered pages to crawlers - #191
Merged
Merged
Conversation
notion.site answers bot-like user agents with a server-rendered page that contains the page content, but the worker always fetched pages as Chrome, so crawlers that don't run JavaScript only saw the empty app shell. Fetch pages with a fixed WorknotBot user agent for crawlers and agents that ask for Markdown. The visitor's user agent is not forwarded, since notion.site rejects Googlebot and Bingbot requests from a Worker. CUBOT phones are excluded, because the server-rendered page has no Notion app. In that page, drop Notion's canonical link (the worker adds its own) and point notion.site links to this domain, using slugs for mapped pages. The render check now also verifies the crawler view. Closes #185 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #185.
notion.site answers bot-like user agents with a server-rendered page that contains the page content as plain HTML. The worker always fetched pages as Chrome, so crawlers that don't run JavaScript (GPTBot, ClaudeBot, PerplexityBot and others) only saw "JavaScript must be enabled in order to use Notion".
isCrawler): the user agent matchesbot\b|crawler|spider|chatgpt-user|claude-user|perplexity-user|meta-externalagent, or the request asks fortext/markdown. The server-rendered page has no Notion app (no scripts, no#notion-app), so people must never get it. CUBOT phones are excluded explicitly, because their model name matchesbot.Mozilla/5.0 (compatible; WorknotBot/1.0; +https://worknot.classmethod.live/). The visitor's user agent is not forwarded, because notion.site returns 403 for Googlebot or Bingbot user agents from IPs that aren't Google's or Bing's. A fixed UA with a bot token gets the server-rendered page.<link rel="canonical">pointing to notion.site.LinkRewriterremoves it, andHeadRewriteradds the site's own canonical as before.AnchorRewriterpointsa[href]links on the notion.site domain to the custom domain, using the slug for mapped pages.crawler contentcheck fetches the root page as GPTBot. It verifies the page text, a single canonical URL on the site, and that no links go to notion.site. Like every check, it also runs against notion.site, so if Notion stops server-rendering for bots it shows up asNOTION, notFAIL.Decision for review: Googlebot
Googlebot and Bingbot match the pattern too, so they now get the server-rendered page instead of the JavaScript app. Google renders JavaScript, so this isn't strictly needed, but it is what notion.site serves to the real Googlebot. I couldn't test it as the real Googlebot. If you'd rather keep search engines on the JavaScript app for now, excluding
googlebot|bingbotis a one-line change.Not included
attachment:URLs in the server-rendered body (images and files) are left as is. They aren't loadable, but crawlers mostly need the text.Verification
npm test: 18 tests pass. Six new cases check the user agent sent to Notion: GPTBot, ChatGPT-User, Googlebot and an agent asking for Markdown get WorknotBot; a desktop browser and a CUBOT phone get Chrome.npm run check:renderagainst live Notion:crawler contentpasses, with no FAIL. Four checks areNOTION, meaning they fail on notion.site too: database views, database rows, database item, and navigation.In
wrangler dev, with a slugitemsmapped to the test page's database, comparing the old and new worker:/as GPTBot/as ChromeFor GPTBot, the new worker returns one canonical URL,
https://render-check.example.com. The links becomehttps://render-check.example.com/itemsandhttps://render-check.example.com/<page-id>./itemsas GPTBot returns the database page, with canonical andog:urlset to/items.Merges cleanly with Add Content Signals to the generated robots.txt #189, which also touches the test file.