Report Sample Routing
The report gallery links from VitePress pages to standalone HTML generated under docs/public/samples/. These reports are static public files, not VitePress routes. Treating them as ordinary documentation links can produce a client-side 404 even when the deployed .html file exists and returns HTTP 200.
Required Link Shape
Use withBase, include the .html extension, and force native same-tab navigation:
<script setup lang="ts">
import { withBase } from "vitepress";
</script>
<a :href="withBase('/samples/ruhroh-report.html')" target="_self">
Open the report
</a>Each part is required:
withBase('/samples/...')adds the configured/ruhroh/deployment base;.htmladdresses the generated public file rather than a VitePress route;target="_self"prevents VitePress from intercepting the click and removing.htmlwhile still opening the report in the current tab.
Do not use normal Markdown link syntax for a generated report path such as /samples/ruhroh-report.html.
VitePress renders the correct href, but its client router intercepts the click, normalizes the URL to /samples/ruhroh-report, and tries to load a VitePress page module. The result is the VitePress 404 page. Opening the .html URL directly can still work, which makes source-only and HTTP-only checks insufficient.
Non-HTML assets such as manifest.json are not handled as VitePress pages and may remain ordinary Markdown links.
Known Failure Modes
Three regressions have produced similar symptoms:
| Symptom | Cause | Required fix |
|---|---|---|
URL contains /ruhroh/ruhroh/samples/ | Source hardcodes the deployed base and VitePress adds it again. | Use withBase('/samples/...') or /samples/... in Markdown source. Never put /ruhroh/ in a source sample link. |
| Report repeatedly refreshes | A Markdown route under docs/samples/ emits the same path as a public report under docs/public/samples/. | Do not create route wrappers for generated report HTML. |
Direct .html URL works, but clicking the gallery link reaches an extensionless 404 | VitePress intercepts the click and strips .html because cleanUrls is enabled. | Render an anchor with target="_self". |
Automated Guard
Run:
pnpm run docs:samples:checkscripts/check-docs-samples.mjs regenerates the public samples and rejects:
- source links that already include
/ruhroh/; - generated HTML links that omit
.html; .htmlsample links that VitePress can intercept;- Markdown routes that shadow generated public HTML;
- missing local links inside generated reports;
- stale or untracked generated sample files.
Do not weaken these checks to make a docs build pass. Fix the source link or the generated sample layout instead.
Browser Verification
Static checks prove that files and link attributes exist. A browser click proves that VitePress does not take over navigation.
pnpm run docs:samples:check
pnpm run docs:build
pnpm run docs:preview -- --host 127.0.0.1 --port 4173Then open http://127.0.0.1:4173/ruhroh/report-gallery and click at least one HTML report link. Verify all of the following:
- the destination URL still ends in
.html; - the standalone report title renders;
- the VitePress
PAGE NOT FOUNDview does not render; - an evidence link inside the report resolves.
Run this browser check whenever changing the VitePress base, cleanUrls, router behavior, report-gallery links, generated sample paths, or anything under docs/samples/ and docs/public/samples/.

