20260916-0740-clean-url-trailing-slash Serve clean URLs without a trailing-slash redirect
- status: completed
- priority: P2
- owner: claude/session-20260915-1901
- createdAt: 2026-09-16 07:40
Description
章节标题:“Description”The Astro migration changed how the deployed site resolves a clean URL. VitePress had
cleanUrls: true and served /docs/architecture directly. Astro's default
build.format: 'directory' emits docs/architecture/index.html, so Cloudflare answers a
request for /docs/architecture with a 307 to /docs/architecture/.
Measured on the live deployment:
/docs/architecture-> 307 ->/docs/architecture/-> 200- in-page links are emitted without a trailing slash (
href="/docs/task/REPO-001"), so every one of the 1271 rewritten in-site links costs an extra round trip - a legacy
_redirectstarget is now a double hop:/bizplan/BUSINESS-PLAN-> 301 -> 307 -> 200
Nothing is unreachable; every page returns 200. The parity work could not see this because it
compared build OUTPUT - both toolchains emit path/index.html - while this is serving-layer
behaviour.
Acceptance criteria:
- A clean URL without a trailing slash resolves 200 directly, with no redirect.
- Directory index pages keep the URL they have today; their route must not change.
- The 63
_redirectsrules still resolve, and no rule gains an extra hop. bun run docs:lint && bun run docs:build && git diff --checkpasses;scripts/check-build.mjspasses.- No file under
bizplan/ordocs/is modified.
ActiveForm
章节标题:“ActiveForm”Restoring clean URLs without a trailing-slash redirect
Dependencies
章节标题:“Dependencies”- blocked by: (none)
- blocks: (none)
Notes
章节标题:“Notes”Follows the Astro + Starlight migration, task 20260915-1901. The candidate change is
build: { format: 'file' } in astro.config.mjs; the risk is that it also changes the emitted
path for the 31 directory index pages, which is what criterion 2 exists to catch.
Outcome
章节标题:“Outcome”build: { format: 'file' } was tried first and REJECTED: it emits .html in every href
(href="/bizplan/product/core.html"), which breaks every published URL, the _redirects
targets and external bookmarks. It fixes a redirect by changing the whole URL scheme.
Taken instead: assets.html_handling: "drop-trailing-slash" in wrangler.jsonc. A
serving-layer setting for a serving-layer problem - build output, emitted links and published
URLs are all unchanged, and the adapter carries it into the generated
dist/server/wrangler.json.
Measured on the live deployment, before and after:
| before | after | |
|---|---|---|
leaf pages (/docs/architecture, 1271 in-site links) |
1 hop | 0 hops |
directory index pages (/bizplan/product/core/, 31 pages) |
0 hops | 1 hop |
legacy _redirects rules |
2 hops | 1 hop |
Criteria 1, 3, 4 and 5 met. Criterion 2 is met in substance - directory index URLs still
resolve 200 - but they now cost one hop, because src/doc-urls.ts emits /{id}/ for index
pages while the asset handler serves the slashless form. Removing that last hop is a
one-expression change to idToHref; it is NOT done here, because it changes which form is
canonical for 31 pages and that is a separate decision.
- complete: html_handling drop-trailing-slash; format:'file' rejected