BASE_URL preview deploys ship a broken web app manifest: 5 hard-coded root-absolute paths 404 under a non-root baseUrl
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 85/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- javascript
- Domain
- build-system, web-dev
Research direction
Start with static/manifest.json and docusaurus.config.js, then run the documented BASE_URL=/endusers/ Docusaurus build command. Verify the generated index.html prefixes both headTags links and that manifest.json uses relative start_url and icon paths, while the default root build remains unchanged.
Written by the indexing model from the issue text.
Description
Finding
docusaurus.config.js:26-28 documents SITE_URL/BASE_URL overrides as the
supported way to deploy a non-root preview:
Override with SITE_URL/BASE_URL for non-production deployments such as GitHub
Pages previews (e.g.SITE_URL=https://castrojo.github.io BASE_URL=/endusers/).
Five paths 404 when you do that. Verified by running that exact build, not
by inference:
SITE_URL=https://castrojo.github.io BASE_URL=/endusers/ npx docusaurus build
at 00b44df, node v26.8.2, 2026-09-19.
What the build emits
Config-derived references are baseUrl-prefixed correctly:
<link data-rh=true rel=icon href=/endusers/img/favicon.ico />
"logo":"https://castrojo.github.io/endusers/img/cloud-native-end-users.svg"
The two headTags entries are emitted verbatim, with no prefix:
<link rel=manifest href=/manifest.json>
<link rel=apple-touch-icon sizes=180x180 href=/favicons/apple-touch-icon.png>
Docusaurus applies baseUrl to favicon and to fields it owns, but it does
not rewrite headTags attribute values — they are passed straight through.
And build/manifest.json is copied out of static/ byte-for-byte, because
Docusaurus performs no transformation on static/:
"start_url": "/",
"src": "/favicons/favicon.svg",
"src": "/favicons/android-chrome-192x192.png",
"src": "/favicons/android-chrome-512x512.png",
Net effect on a preview deploy
| path emitted | resolves to | correct target |
|---|---|---|
/manifest.json |
origin root | /endusers/manifest.json |
/favicons/apple-touch-icon.png |
origin root | /endusers/favicons/... |
start_url: "/" |
origin root | /endusers/ |
3x icons[].src |
origin root | /endusers/favicons/... |
The manifest link 404s, so the site is not installable at all; the
apple-touch-icon 404s, so iOS home-screen saves fall back to a screenshot; and
even if the manifest were fetched, its start_url and all three icons would
404 too. On castrojo.github.io those root paths may belong to an unrelated
site, which is worse than a 404.
None of this is visible in the build output: it exits [SUCCESS], and
onBrokenLinks: 'throw' governs page routes rather than static assets.
Recommendation
Two independent edits. Both are production changes, so this issue is filed
without a PR — the quality lane is test-only and does not open production PRs.
See the note at the bottom.
1. static/manifest.json — switch to manifest-relative paths
Per the appmanifest spec,
member URLs are resolved against the manifest's own URL. Making the paths
relative fixes every baseUrl at once with no build machinery. Exact
replacement for the four affected lines:
- "start_url": "/",
+ "start_url": ".",
...
- "src": "/favicons/favicon.svg",
+ "src": "favicons/favicon.svg",
...
- "src": "/favicons/android-chrome-192x192.png",
+ "src": "favicons/android-chrome-192x192.png",
...
- "src": "/favicons/android-chrome-512x512.png",
+ "src": "favicons/android-chrome-512x512.png",
With the manifest served at /endusers/manifest.json, start_url: "."
resolves to /endusers/ and each icon to /endusers/favicons/.... At the
production root it is unchanged from today.
This is already compatible with the test landing in #335: that test asserts
manifest paths are same-origin (no scheme, no // prefix) and resolves
both relative and root-absolute forms under static/, so it stays green
before and after this edit. It was written that way deliberately so it
would not block this fix.
2. docusaurus.config.js — prefix the two headTags hrefs
const siteUrl = process.env.SITE_URL || 'https://endusers.cncf.io';
const baseUrl = process.env.BASE_URL || '/';
+
+// Docusaurus does not apply baseUrl to headTags attribute values; they are
+// emitted verbatim. Prefix them here so non-root preview deploys resolve.
+const withBaseUrl = (path) =>
+ `${baseUrl.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
attributes: {
rel: 'manifest',
- href: '/manifest.json',
+ href: withBaseUrl('/manifest.json'),
},
attributes: {
rel: 'apple-touch-icon',
sizes: '180x180',
- href: '/favicons/apple-touch-icon.png',
+ href: withBaseUrl('/favicons/apple-touch-icon.png'),
},
At the default baseUrl: '/' both expressions produce exactly the strings
that are there today, so production output is byte-identical and the build
stays green.
Verifying a fix
SITE_URL=https://castrojo.github.io BASE_URL=/endusers/ npx docusaurus build --out-dir /tmp/b
grep -o '<link[^>]*manifest[^>]*>' /tmp/b/index.html # expect href=/endusers/manifest.json
grep -o '<link[^>]*apple[^>]*>' /tmp/b/index.html # expect href=/endusers/favicons/...
grep -E 'start_url|"src"' /tmp/b/manifest.json # expect relative paths
Disjointness from open PRs
- #275 (
tests/site-config.test.mjs) asserts thatSITE_URL/BASE_URL
flow intourl,baseUrland the JSON-LD logo URL, and that the two
headTagshrefs exist on disk. It does not assert that those hrefs are
baseUrl-prefixed, and it changes no production code. The config edit above
does not conflict with it:withBaseUrl('/manifest.json')still yields
/manifest.jsonat the defaultbaseUrl, which is the value #275 checks. - #335 covers the manifest's install contract and is written to accept
either path form, as described above. - #289, #317, #321 claim
/imgreferences,static/fonts/, and
the orphan direction overstatic/images/social/faviconsrespectively.
None readsstart_url,headTagshrefs, orbaseUrlbehaviour.
Priority
- Impact: high — every non-root preview deploy ships a wholly broken manifest and icon set, silently, via the workflow the config itself documents
- Effort: low — four one-line JSON edits plus a three-line config helper; production output at the default
baseUrlis byte-identical
Note on why there is no PR
Both edits are production changes (static/manifest.json and
docusaurus.config.js). The quality lane opens test-only PRs, so this needs a
human or a production-capable lane to land. The exact replacement text is
given above so applying it is mechanical.
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5
- Dominant language
- JavaScript
- Stars
- 0
- Forks
- 2
- Avg merge
- 2d 22h
- Merged PRs (30d)
- 12
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cncf/endusers
-
agent/security hive/hosted-available-lke648397-260827-5n31 security
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
[scanner] PR #178 has zero linked issues — implements gov.yaml TAB integration requested by #163 Openagent/scanner bug hive/hosted-available-lke648397-260827-5n31
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
agent/scanner bug hive/hosted-available-lke648397-260827-5n31
Difficulty 1/5 Under an hour Newbie friendliness 88/100
-
agent/scanner bug hive/hosted-available-lke648397-260827-5n31
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
agent/quality hive/hosted-available-lke648397-260827-5n31 quality testing
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
HarperFast/skills#96 ·
-
[Block] Latest Posts [Type] Bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
sugarlabs/musicblocks#8847 ·