Skip to content

Updated sitemap to work with lazy routing - #29302

Merged
allouis merged 5 commits into
mainfrom
fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing
Jul 15, 2026
Merged

Updated sitemap to work with lazy routing#29302
allouis merged 5 commits into
mainfrom
fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing

Conversation

@allouis

@allouis allouis commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

ref https://linear.app/ghost/issue/HKG-1919

The sitemap generators have a single write path, addUrl, and today its only caller is the eager URL service's per-URL event stream. The lazy URL service emits no such events, so under lazy routing every sitemap would render empty. This PR adds a second path to addUrl without touching the generators or the XML rendering.

The URL service now exposes enumeration of routable resources. facade.getRoutableResources covers all three modes: eager reads the in-memory cache, lazy fetches on demand through the same raw-knex path and visibility gates the eager boot walk uses, and compare returns the eager result while diffing the lazy id set in the background, single-flighted per type and logging only counts and sample ids. Bulk callers pass skipComparison so a full build does not queue a background computation per resource.

SiteMapManager builds its index once on first read in every mode, computing URLs through the URL service and feeding them to the generators. Invalidation (site.changed, routers.reset, router.created) applies only under pure lazy. A build invalidated mid-flight fails the read with a 503 SITEMAP_BUILD_SUPERSEDED instead of serving pre-invalidation data the CDN would pin for the full maxAge, with deliberately no retry. Static and collection router entries are recorded and replayed into every rebuild, and the /404/ sentinel is matched exactly so a resource slugged "404" stays listed.

The key rollout property: wherever the eager service runs (flag off and compare mode) the per-URL events keep the index current after the first build and nothing invalidates, so deploying this is a serving no-op until the lazy flip.

Commits are reviewable independently and each is green on its own. An integration parity test pins enumeration against the eager cache on a real database across the default routing shape plus primary_tag and primary_author permalinks.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d9af55d5-6242-4e11-87e7-d18313b54fe7

📥 Commits

Reviewing files that changed from the base of the PR and between cfd35db and a1dbe47.

📒 Files selected for processing (8)
  • ghost/core/core/frontend/services/sitemap/handler.js
  • ghost/core/core/frontend/services/sitemap/site-map-manager.js
  • ghost/core/core/server/services/url/index.js
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/integration/sitemap-parity.test.js
  • ghost/core/test/unit/frontend/services/sitemap/handler.test.js
  • ghost/core/test/unit/frontend/services/sitemap/manager.test.js
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
🚧 Files skipped from review as they are similar to previous changes (7)
  • ghost/core/test/unit/frontend/services/sitemap/handler.test.js
  • ghost/core/test/integration/sitemap-parity.test.js
  • ghost/core/core/server/services/url/index.js
  • ghost/core/test/unit/frontend/services/sitemap/manager.test.js
  • ghost/core/core/frontend/services/sitemap/handler.js
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/core/frontend/services/sitemap/site-map-manager.js

Walkthrough

The sitemap manager now builds lazy indexes from routable resources, coordinates concurrent reads and invalidation, and replays router entries during rebuilds. The URL service adds lazy resource enumeration, eager/lazy parity checks, and comparison opt-out support. Sitemap routes await asynchronous XML generation and forward failures through Express. Tests cover resource filtering, parity, rebuilding, routing, SQLite batching, and asynchronous failures.

Possibly related PRs

  • TryGhost/Ghost#28890: Updates URL-service lazy routing behavior used by sitemap resource fetching and URL computation.
  • TryGhost/Ghost#29304: Touches the same sitemap handlers and manager router-registration logic.

Suggested reviewers: vershwal

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: sitemap support for lazy routing.
Description check ✅ Passed The description clearly matches the changeset and explains the lazy-routing sitemap and URL service updates.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing

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

@nx-cloud

nx-cloud Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit a1dbe47

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 2m 46s View ↗
nx run ghost:test:integration ✅ Succeeded 2m 52s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 38s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 3s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 29s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 7s View ↗
nx run @tryghost/admin:build ✅ Succeeded 7s View ↗
nx run-many -t lint -p ghost,ghost-monorepo ✅ Succeeded 22s View ↗
Additional runs (3) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-15 10:15:27 UTC

@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch 3 times, most recently from 91309b9 to 343fa2f Compare July 14, 2026 04:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/frontend/services/sitemap/resource-fetcher.js`:
- Around line 10-13: Update the author fetch in the sitemap resource-fetcher
path to use an explicit projection or a tighter exclude list, removing email and
every other unused users column while retaining only fields required by sitemap
caching and URL computation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9ed954f8-109e-4de4-82fb-1f05b8818289

📥 Commits

Reviewing files that changed from the base of the PR and between 557244f and 343fa2f.

📒 Files selected for processing (15)
  • ghost/core/core/frontend/services/sitemap/base-site-map-generator.js
  • ghost/core/core/frontend/services/sitemap/handler.js
  • ghost/core/core/frontend/services/sitemap/parity-check.js
  • ghost/core/core/frontend/services/sitemap/resource-fetcher.js
  • ghost/core/core/frontend/services/sitemap/site-map-manager.js
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/integration/sitemap-parity.test.js
  • ghost/core/test/integration/url-service-parity.test.js
  • ghost/core/test/unit/frontend/services/sitemap/generator.test.js
  • ghost/core/test/unit/frontend/services/sitemap/handler.test.js
  • ghost/core/test/unit/frontend/services/sitemap/manager.test.js
  • ghost/core/test/unit/frontend/services/sitemap/parity-check.test.js
  • ghost/core/test/unit/frontend/services/sitemap/resource-fetcher.test.js
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
  • ghost/core/test/utils/url-service-utils.js

Comment thread ghost/core/core/frontend/services/sitemap/resource-fetcher.js Outdated
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch 2 times, most recently from a2c846d to b37e364 Compare July 14, 2026 06:34
@allouis
allouis changed the base branch from main to fabien-hkg-1919-sitemap-tidies July 14, 2026 06:34
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch 2 times, most recently from a2d4ddc to d5716d5 Compare July 14, 2026 07:04
@allouis
allouis marked this pull request as ready for review July 14, 2026 07:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ghost/core/core/server/services/url/url-service-facade.ts (1)

138-199: 🚀 Performance & Scalability | 🔵 Trivial

Compare-mode bulk fetch doubles DB load on every sitemap rebuild.

Every getRoutableResources(type) call in compare mode kicks off a full (SQLite-batched) DB fetch via _compareRoutableResources purely for parity logging, in addition to the eager answer already served. Since index.js wires compare: true whenever lazyRouting is enabled, and site-map-manager.js's _buildIndex() calls this for all 4 types on every rebuild, each site.changed/routers.reset invalidation now costs 4 extra full-table (batched) reads — precisely during the heavy-editing windows the PR already flags as producing SITEMAP_BUILD_SUPERSEDED 503s. Unlike the existing single-resource compare paths, this is bulk work with no throttling/dedup if invalidations outpace a compare cycle.

Worth watching DB/connection-pool pressure during compare-mode soak testing on busy sites, or considering a cheaper comparison strategy (e.g., sampling, or a lower-frequency comparison cadence) for this particular check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/server/services/url/url-service-facade.ts` around lines 138 -
199, Throttle or deduplicate the bulk parity fetch triggered by
getRoutableResources and _compareRoutableResources so repeated sitemap rebuilds
do not start a full database read for every type on every call. Preserve the
eager response and parity logging, while limiting comparisons to a
lower-frequency or single in-flight check per type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/frontend/services/sitemap/site-map-manager.js`:
- Around line 222-227: Update _applyResource to stop filtering URLs via the
broad url.includes('/404/') check; use the URL service’s not-found sentinel or
an equivalent suffix-only check, while continuing to add valid canonical
URLs—including resources whose slug contains “404”—through this[type].addUrl.

---

Nitpick comments:
In `@ghost/core/core/server/services/url/url-service-facade.ts`:
- Around line 138-199: Throttle or deduplicate the bulk parity fetch triggered
by getRoutableResources and _compareRoutableResources so repeated sitemap
rebuilds do not start a full database read for every type on every call.
Preserve the eager response and parity logging, while limiting comparisons to a
lower-frequency or single in-flight check per type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c05a8589-2b9c-4b38-9013-9095e539e8a2

📥 Commits

Reviewing files that changed from the base of the PR and between 343fa2f and d5716d5.

📒 Files selected for processing (10)
  • ghost/core/core/frontend/services/sitemap/handler.js
  • ghost/core/core/frontend/services/sitemap/site-map-manager.js
  • ghost/core/core/server/services/url/index.js
  • ghost/core/core/server/services/url/routable-resources.js
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/integration/sitemap-parity.test.js
  • ghost/core/test/unit/frontend/services/sitemap/handler.test.js
  • ghost/core/test/unit/frontend/services/sitemap/manager.test.js
  • ghost/core/test/unit/server/services/url/routable-resources.test.js
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • ghost/core/test/unit/frontend/services/sitemap/handler.test.js
  • ghost/core/test/integration/sitemap-parity.test.js

Comment thread ghost/core/core/frontend/services/sitemap/site-map-manager.js
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch from d5716d5 to 4f6711b Compare July 14, 2026 08:07

@vershwal vershwal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, small nits. I also think some of these files have too many comments. We should clean them up either now or later when we remove comparison mode and tidy up this code.

Comment thread ghost/core/core/server/services/url/url-service-facade.ts Outdated
Comment thread ghost/core/test/unit/frontend/services/sitemap/manager.test.js
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch from 4f6711b to 4bcec70 Compare July 15, 2026 03:10
@allouis
allouis force-pushed the fabien-hkg-1919-sitemap-tidies branch from ebd2f45 to 1176124 Compare July 15, 2026 05:55
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch 2 times, most recently from 11051d1 to e197bdb Compare July 15, 2026 05:57
Base automatically changed from fabien-hkg-1919-sitemap-tidies to main July 15, 2026 06:11
ref https://linear.app/ghost/issue/HKG-1919

The sitemap needs the complete set of routable resources, and the lazy
URL service deliberately holds no such set in memory. This fetches it
on demand through the same raw-knex fast path and public-visibility
gates the eager boot walk uses, batched in id order on SQLite to stay
under the bound-variable limit. Callers name the extra columns they
want back rather than maintaining exclude lists, and the columns and
relations URL computation needs come from the lazy service itself, so
rows can never be thin for whatever routing config is active.
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch from e197bdb to cfd35db Compare July 15, 2026 06:22
allouis added 4 commits July 15, 2026 10:03
ref https://linear.app/ghost/issue/HKG-1919

Enumeration joins the facade so it answers like every other URL read:
eager from its in-memory cache, lazy from the on-demand fetch, and
compare returning the eager answer while diffing the lazy fetch id set
in the background. The comparison is single-flighted per type and logs
counts and id samples, never row bodies, because a large site has
hundreds of thousands. Per-URL parity already comes from organic
request traffic, so bulk callers pass skipComparison — teeing every
row of a full build would capture a stack and queue a background
computation per resource. Lazy mode without an injected fetcher throws
so a miswired boot fails fast.
ref https://linear.app/ghost/issue/HKG-1919

Under lazy routing no per-URL events fire, so the manager builds its
index itself: one fetch of every routable resource on first read, URLs
computed through the URL service, fed to the untouched generators via
addUrl. Wherever the eager service runs — flag off and compare mode —
the per-URL events keep the index current from then on and nothing
invalidates, which makes deploying this a serving no-op until the lazy
flip. Under pure lazy the invalidation signals empty the index and the
next read rebuilds; a build invalidated mid-flight fails the read with
a 503 rather than ever serving pre-invalidation data, since the CDN
would pin a stale 200 for the full cache maxAge, and there is
deliberately no retry until the logs justify one. Static and
collection route entries only arrive via router.created at boot and
routes reload, so they are recorded and replayed into every rebuild,
and the /404/ sentinel is matched exactly so a real resource slugged
"404" stays in the sitemap.
ref https://linear.app/ghost/issue/HKG-1919

getIndexXml and getSiteMapXml are async now that the manager builds
its index on first read, and awaiting inside the manager means no
caller can forget to. Express 4 does not forward async handler
rejections, so each handler body is fully wrapped: a failed build or
render becomes an error response the next request can retry, instead
of an unhandled rejection and a socket that never responds.
ref https://linear.app/ghost/issue/HKG-1919

The unit tests pin the enumeration against stubbed queries, which can
never prove that the filters, has-posts gates and loaded relations
agree with what the eager URL service actually caches. This runs both
against a real database and asserts identical URL sets, across the
default routing shape plus primary_tag and primary_author permalinks,
the two URL shapes that silently degrade when a relation goes
missing.
@allouis
allouis force-pushed the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch from cfd35db to a1dbe47 Compare July 15, 2026 10:05
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.45803% with 94 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.03%. Comparing base (a48dffe) to head (a1dbe47).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ore/core/server/services/url/url-service-facade.ts 50.00% 48 Missing and 1 partial ⚠️
...core/frontend/services/sitemap/site-map-manager.js 86.22% 23 Missing ⚠️
...ore/core/server/services/url/routable-resources.js 89.65% 12 Missing ⚠️
...ost/core/core/frontend/services/sitemap/handler.js 73.07% 7 Missing ⚠️
ghost/core/core/server/services/url/index.js 25.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #29302      +/-   ##
==========================================
+ Coverage   74.00%   74.03%   +0.03%     
==========================================
  Files        1577     1578       +1     
  Lines      136928   137430     +502     
  Branches    16577    16647      +70     
==========================================
+ Hits       101328   101742     +414     
- Misses      34560    34643      +83     
- Partials     1040     1045       +5     
Flag Coverage Δ
e2e-tests 76.11% <77.45%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

@allouis
allouis merged commit 710e865 into main Jul 15, 2026
45 checks passed
@allouis
allouis deleted the fabien-hkg-1919-update-sitemap-to-work-with-lazy-routing branch July 15, 2026 10:38
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.

2 participants