Skip to content

DM-56095: Redirect /v/{edition} without trailing slash - #883

Merged
jonathansick merged 3 commits into
mainfrom
tickets/DM-56095-edition-root-redirect
Nov 9, 2026
Merged

jonathansick merged 3 commits into
mainfrom
tickets/DM-56095-edition-root-redirect

Conversation

@jonathansick

Copy link
Copy Markdown
Member

Summary

  • A request for /v/{edition} with no trailing slash served the edition's index.html in place with a 200 instead of redirecting to /v/{edition}/. The browser then resolved the page's relative asset links against /v/ rather than /v/{edition}/, so Sphinx CSS and JS failed to load and the page rendered unstyled. Reported by a user against https://pipelines.lsst.io/v/weekly.
  • The resolver's directory-index fallback only redirected when route.path was non-empty, a guard meant to stop the main edition's / from redirecting to //. The edition root has an empty route.path for both /v/weekly/ and /v/weekly, so the no-slash form slipped through. The guard now checks the request pathname instead, which also covers the path-prefix scheme's /docs/{project} root.
  • Adds resolver unit tests for the edition root with and without the slash (including query-string preservation) and integration tests for both URL schemes.

Validation steps

  • Deploy the worker to production-rubin with docverse deploy-worker (note: the prod worker has not been redeployed since mid-September, so this deploy also picks up the later worker commits on main, including the cache_profile Cache-Control change).
  • curl -sI https://pipelines.lsst.io/v/weekly returns 301 with Location: https://pipelines.lsst.io/v/weekly/.
  • curl -sI https://pipelines.lsst.io/v/weekly/ still returns 200.
  • curl -sI https://pipelines.lsst.io/ still returns 200 (no // redirect loop on the main edition root).
  • curl -sI https://pipelines.lsst.io/v/weekly/getting-started still returns 301 to the slash-terminated URL.
  • Load https://pipelines.lsst.io/v/weekly in a browser and confirm the page is styled.

References

🤖 Generated with Claude Code

A request for /v/{edition} (no trailing slash) served the edition's
index.html in place with a 200 instead of redirecting to /v/{edition}/.
The browser then resolved the page's relative asset links against /v/
rather than /v/{edition}/, so Sphinx CSS and JS failed to load and the
page rendered unstyled. Reported against pipelines.lsst.io/v/weekly.

The resolver's directory-index fallback only redirected when route.path
was non-empty, a guard meant to stop the main edition's "/" from
redirecting to "//". But the edition root has an empty route.path for
both /v/weekly/ and /v/weekly, so the no-slash form slipped through.
Decide from the request pathname instead: redirect whenever the index
file is found and the pathname does not already end in "/". This also
covers the path-prefix scheme's /docs/{project} root.

Add resolver unit tests for the edition root with and without the slash
(including query-string preservation) and integration tests for the
subdomain and path-prefix schemes.
@jonathansick
jonathansick force-pushed the tickets/DM-56095-edition-root-redirect branch from 7b7c73e to 98899c7 Compare November 9, 2026 19:19
The directory-index fallback built its own URL and Response.redirect,
duplicating the resolveRedirect helper that the /v redirect route
already uses. Route both canonicalising redirects through the helper so
they cannot drift apart.
Response.redirect() emits a bare 301 with only a Location header. The
Cloudflare edge cannot cache a response without a cache directive, so
every no-slash request reached the Worker and paid KV + R2 I/O, while
browsers treat a header-less 301 as heuristically cacheable forever.

Build the 301 by hand in resolveRedirect and attach CACHE_CONTROL_LONG
(5 min browser, 1 day edge). The target is a pure function of the
request URL's shape, never of edition content, so the long profile is
safe regardless of the edition's own cache_profile. Assert the header in
the redirect unit and integration tests.
@jonathansick
jonathansick merged commit 3498527 into main Nov 9, 2026
26 checks passed
@jonathansick
jonathansick deleted the tickets/DM-56095-edition-root-redirect branch November 9, 2026 20:54
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