Skip to content

feat(seo): add an llms.txt site index and alias the conventional feed paths - #182

Merged
Decipher merged 4 commits into
mainfrom
feat/llms-txt-and-feed-aliases
Sep 9, 2026
Merged

feat(seo): add an llms.txt site index and alias the conventional feed paths#182
Decipher merged 4 commits into
mainfrom
feat/llms-txt-and-feed-aliases

Conversation

@Decipher

@Decipher Decipher commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Two discoverability gaps found while looking at where the site's traffic actually comes from. Mirrors GitLab !61.

/llms.txt

A plain-text site index in the llmstxt.org format: an H1, a blockquote summary, then ## Writing, ## Pages and ## Optional sections of - [name](url): description links. Generated from the content collection at prerender time, so it cannot drift from the articles that exist.

chatgpt.com already shows up as a referrer and organic search brings 13 sessions a year, so assistant citation is plausibly the more interesting of the two channels. Until now the only machine-readable map of the site was sitemap.xml, which carries URLs and no descriptions.

Article and page links are UTM-tagged (utm_source=llms-txt&utm_medium=ai&utm_campaign=syndication), mirroring what the RSS feeds already do. That is deliberate: the standard's adoption is not settled, and the tagging is the only way to find out whether the file earns its place rather than guessing later. Per-page <link rel="canonical"> means the extra params cost nothing in search. The two RSS links are left bare, since a subscriber's client refetches them and never "arrives" from one.

/feed.xml and /rss.xml/blog.xml

Both returned 404. /blog.xml is the real feed and is advertised in <head>, so nothing correct was broken, but feed readers, browser extensions and link checkers try the conventional names first. Aliased with a 301 rather than moving the canonical path, which Planet Drupal and existing subscribers depend on.

Forced (301!) in public/_redirects and mirrored in public/serve.json, both from review on the GitLab side:

  • The forced entries are defence in depth, not a live bug fix. I checked the build output: Nitro emits no dist/feed.xml or dist/rss.xml stub, apparently because it skips prerendering routes carrying a file extension, so the aliases already worked. Forced anyway because the /articles/* rules above them exist precisely because this repo shipped that bug once, and nothing stops a future change from making these prerender too.
  • serve.json matters because the Playwright suite runs serve -s dist, which reads that file and knows nothing about Nitro routeRules. Without it the aliases 404 locally while production redirects. Verified after the change: 301 -> /blog.xml for both under serve.

Verification

Checked against real build output, not just config:

  • dist/_redirects contains both 301s; dist/llms.txt generates (5.5 kB); dist/_headers carries its text/plain; charset=utf-8
  • 676 tests pass, llmsTxt.ts and llms.txt.get.ts at 100% statements/branches/functions/lines
  • lint:all, typecheck and highlight:check all exit 0

Note on the base branch

This targets main because develop no longer exists on this remote — it was deleted when #178 squash-merged it into main this morning. main's tree is byte-identical to GitLab's develop, so these two commits cherry-picked onto it cleanly. Happy to retarget if develop gets recreated.

Summary by CodeRabbit

  • New Features

    • Added an /llms.txt plain-text site index containing links to articles, pages, and RSS feeds.
    • Article and page links include tracking information, while RSS links remain direct.
    • Added support for serving /llms.txt with the correct plain-text content type.
  • Bug Fixes

    • /feed.xml and /rss.xml now permanently redirect to the canonical /blog.xml feed.
  • Tests

    • Added coverage for index structure, links, attribution parameters, feed handling, and response headers.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 46 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: a71cc6b2-830b-4310-8661-57fceb994676

📥 Commits

Reviewing files that changed from the base of the PR and between 0e6b344 and 85d1791.

📒 Files selected for processing (4)
  • .gitlab-ci.yml
  • CHANGELOG.md
  • nuxt/playwright.config.ts
  • nuxt/unlighthouse.config.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 98683edf-7e8b-430c-ae7e-fcbd1ae9f9b8

📥 Commits

Reviewing files that changed from the base of the PR and between a69e233 and 0e6b344.

📒 Files selected for processing (8)
  • .cspell.json
  • CHANGELOG.md
  • nuxt/nuxt.config.ts
  • nuxt/public/_redirects
  • nuxt/public/serve.json
  • nuxt/server/routes/llms.txt.get.ts
  • nuxt/server/utils/llmsTxt.ts
  • nuxt/tests/server/llmsTxt.spec.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds a generated /llms.txt index for blog posts, static pages, and RSS feeds. It adds route tests, prerender and content-type configuration, redirects feed aliases to /blog.xml, and documents the changes.

Changes

llms.txt site index

Layer / File(s) Summary
Document builder and output coverage
nuxt/server/utils/llmsTxt.ts, nuxt/tests/server/llmsTxt.spec.ts
The builder renders article, page, and feed sections. Article and page links include UTM parameters. Tests verify structure, ordering, URLs, and trailing newline behavior.
Content query and route response
nuxt/server/routes/llms.txt.get.ts, nuxt/tests/server/llmsTxt.spec.ts
The route queries dated article entries, keeps blog posts, uses SITE_ORIGIN, and returns text/plain; charset=utf-8. Route tests verify filtering and response headers.
Static delivery and feed redirects
nuxt/nuxt.config.ts, nuxt/public/_redirects, nuxt/public/serve.json, CHANGELOG.md, .cspell.json
Nuxt prerenders /llms.txt, deployment configurations set its content type, and /feed.xml and /rss.xml redirect to /blog.xml. The changelog and spellcheck dictionary are updated.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 0e6b3

This adds a prerendered plain-text site index and redirects legacy feed aliases to the canonical blog feed. The generated links, response type, and redirect targets are covered, with no remaining merge-blocking risk identified.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant llms.txtRoute
  participant ContentCollection
  participant buildLlmsTxt
  Client->>llms.txtRoute: Request /llms.txt
  llms.txtRoute->>ContentCollection: Query articleEntries ordered by date
  ContentCollection-->>llms.txtRoute: Return article entries
  llms.txtRoute->>buildLlmsTxt: Pass blog posts and SITE_ORIGIN
  buildLlmsTxt-->>llms.txtRoute: Return plain-text index
  llms.txtRoute-->>Client: Return text/plain response
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files. (4 skipped: 4… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes both primary changes: adding the llms.txt site index and aliasing the conventional feed paths.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files. (4 skipped: 4 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llms-txt-and-feed-aliases

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Preview site

pr-182: https://pr-182--stuartclark.netlify.app

Updated 2026-09-09T11:05:25Z

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.77%. Comparing base (a69e233) to head (85d1791).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #182   +/-   ##
=======================================
  Coverage   99.77%   99.77%           
=======================================
  Files          79       81    +2     
  Lines        1323     1340   +17     
  Branches      332      332           
=======================================
+ Hits         1320     1337   +17     
  Misses          3        3           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

✅ Lighthouse Audit · unknown

All pages within budget — 13 pages scanned.

Route FCP LCP CLS TBT Perf
/ 2.7s 3.0s 0.009 19.000 0.90
/about 2.6s 3.2s 0.178 1.500 0.81
/community 2.7s 2.7s 0.016 0.000 0.92
/open-source 2.7s 4.8s 0.000 0.000 0.78
/writing 2.7s 2.9s 0.000 4.000 0.91
/writing/custom-formatters-410-20260731 2.9s 3.4s 0.063 0.000 0.86
/writing/decoupling-configuration-config-pages-20220412 2.7s 3.0s 0.015 0.000 0.90
/writing/drupal-site-settings-over-jsonapi-consumer-20260826 2.9s 3.4s 0.159 0.000 0.80
/writing/druxt-drupal-13x-resource-list-yours-20260909 3.2s 3.5s 0.058 0.000 0.84
/writing/field-tokens-200-20260722 2.7s 2.8s 0.019 0.000 0.91
/writing/hello-world-20211126 2.9s 3.0s 0.142 0.000 0.84
/writing/jsonapi-views-120-20260812 2.7s 2.8s 0.005 0.000 0.91
/writing/layout-paragraphs-module-20220301 2.7s 2.7s 0.055 0.000 0.91

Thresholds: FCP ≤ 3.5s, LCP ≤ 5.0s, CLS ≤ 0.25, TBT ≤ 600.000, Perf ≥ 0.75

@Decipher

Decipher commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Added the CI concurrency fix to this branch (2c3c0cf, 85d1791), because the audit failure on the previous push was partly this.

Unlighthouse defaults maxConcurrency to floor(cpus / 2). That is 2 on a GitHub Actions runner, so the audit was measured with two headless Chrome instances competing, and 9 on the GitLab runner, where the same defect is far more destructive (a static site reporting TBT of 2.2s and 4.5s, a different arbitrary subset of pages failing every run).

The evidence that this is measurement rather than the site: /writing/drupal-site-settings-over-jsonapi-consumer-20260826 scored 0.79 on the main run an hour before this PR and 0.74 here, against a 0.75 threshold. Same page, same content, opposite verdict.

Also included, since it is the same defect in a different tool: Playwright defaults workers to half the cores too. On GitLab that meant 9 workers and produced 34 test timeouts with 6 "Page crashed" and no pixel diffs.

That page is genuinely marginal regardless, and worth its own look — its CLS of 0.159 and LCP are the worst on the site, and the v1.6.0 changelog records its screenshots already being quantized once to clear this same budget. Serialising the audit removes the measurement noise on top of that, so the number this reports now is the page's own.

@Decipher
Decipher merged commit 5cf6028 into main Sep 9, 2026
10 checks passed
@Decipher
Decipher deleted the feat/llms-txt-and-feed-aliases branch September 9, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant