fix(redirects): restore the hyphenated legal page URLs - #3481
Conversation
`deploy/privacy-policy.md` and `deploy/terms-and-conditions.md` were renamed to underscores, but only the `/deploy/classic/` form of each old URL was carried over in page frontmatter. The bare hyphenated paths have 404ed since, and the site search still surfaces `/deploy/privacy-policy`. Added to `oldurls.json` rather than the pages' `oldUrl` frontmatter so the freshness check does not demand a `last_modified` bump on two legal documents whose content did not change.
avocet-bot
left a comment
There was a problem hiding this comment.
Review: #3481 — fix(redirects): restore the hyphenated legal page URLs
Reviewed head SHA: 44b2c79745f3c16885e36b8f9ecf0082c6de2ace
Verdict: Approve — no blocking issues.
Review skill evidence
Primary review performed with the Claude Code pr-review-toolkit:code-reviewer subagent (invoked via the Task tool), which fetched the PR metadata/diff, cloned the repo at the reviewed SHA, and traced the redirect resolution path. I then independently re-verified its load-bearing claims against the checkout at the same SHA (middleware logic, destination files, JSON validity, duplicate-key and loop checks).
What this PR changes
This is a documentation site (denoland/docs) built with Lume. When a page is renamed, its old URL has to be redirected to the new one or inbound/external links 404. Redirects live in two places: per-page oldUrl: frontmatter, and a central map oldurls.json. At request time, middleware/redirects.ts loads that map and issues HTTP 301s.
The two Deploy legal pages were renamed from hyphenated to underscored filenames (privacy-policy.md → privacy_policy.md, terms-and-conditions.md → terms_and_conditions.md), which changed their emitted URLs. Only the /deploy/classic/ variant of the old URLs was preserved, so the bare hyphenated paths have been 404ing — and the site search still surfaces the dead /deploy/privacy-policy link as a top result.
The fix adds exactly two exact-match entries to oldurls.json (lines 171–172):
"/deploy/privacy-policy/": "/deploy/privacy_policy/",
"/deploy/terms-and-conditions/": "/deploy/terms_and_conditions/",Control/data flow verified
middleware/redirects.ts:68-71 resolves a path with trailing-slash normalization:
let redirect = redirects[url.pathname] ||
(url.pathname.endsWith("/")
? redirects[url.pathname.slice(0, -1)]
: redirects[url.pathname + "/"]);- For
/deploy/privacy-policy(no slash, the form site search emits): exact miss, then the+ "/"branch hits the new key → 301 to/deploy/privacy_policy/. - For
/deploy/privacy-policy/(with slash): exact hit directly.
So the single trailing-slash key genuinely covers both inbound forms. Wildcard matching at redirects.ts:74 is guarded by if (!redirect), so these exact entries always take precedence over the nearby /deploy/* wildcard rules.
Checks performed (all pass)
- Destinations resolve.
deploy/privacy_policy.mdanddeploy/terms_and_conditions.mdexist, with nourl:frontmatter override, so Lume emits them at exactly/deploy/privacy_policy/and/deploy/terms_and_conditions/. The old hyphenated source files are confirmed deleted (why the bare paths 404'd). - No redirect loop. Neither destination has its own entry in
oldurls.json(confirmedundefinedfor both), and no wildcard prefix-matches them, so each 301 lands on a terminal 200. - No collisions / duplicates / invalid JSON. File parses to 190 keys; the two new keys appear once each (lines 171–172) and did not previously exist. The pre-existing
/deploy/classic/privacy-policyredirect is independent and does not conflict. - Design rationale sound. Placing these in
oldurls.jsonrather than pageoldUrl:frontmatter deliberately avoids tripping the freshness check, which would otherwise demand a misleadinglast_modifiedbump on legal documents whose content did not change. This matches the file's existing convention.
Tests
No test was added, and none is warranted. There is no existing test harness exercising oldurls.json through the middleware, and this is a pure two-line data addition whose resolution path is fully covered by existing middleware logic. The author also documented production verification in the PR body (/deploy/privacy-policy 404 → /deploy/privacy_policy/ 200, etc.). A dedicated test would be low value for this class of change.
Findings
None. The mappings are correct (hyphenated → underscored), both trailing-slash variants are covered, there are no conflicts/duplicates/loops, destinations resolve to existing pages, and the JSON is valid. Safe to merge.
/deploy/privacy-policyand/deploy/terms-and-conditionshave404ed since the pages were renamed to underscores; only the
/deploy/classic/form of each old URL was carried over in the pagefrontmatter. The site search still surfaces the bare hyphenated
privacy URL, so the first result is a dead link.
oldurls.json. The redirect middleware normalizestrailing slashes (
redirects[pathname] || redirects[pathname±"/"]),so one entry covers
/deploy/privacy-policyand/deploy/privacy-policy/. Exact matches are resolved before wildcardrules, and neither key collides with an existing one.
oldurls.jsonrather than the pages'oldUrlfrontmatter onpurpose: editing those files makes the freshness check require a
last_modifiedbump, and dating two legal documents a day later thantheir stated effective date would be misleading when their content
has not changed.
Verified against production: