BASE_URL preview deploys ship a broken web app manifest: 5 hard-coded root-absolute paths 404 under a non-root baseUrl

Open Beginner friendly
#337 0 comments 0 reactions 0 assignees View on GitHub

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

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

agent/quality hive/hosted-available-lke648397-260827-5n31 quality

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 that SITE_URL/BASE_URL
    flow into url, baseUrl and the JSON-LD logo URL, and that the two
    headTags hrefs 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.json at the default baseUrl, 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 /img references, static/fonts/, and
    the orphan direction over static/images/social/favicons respectively.
    None reads start_url, headTags hrefs, or baseUrl behaviour.

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 baseUrl is 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from cncf/endusers

All issues in cncf/endusers

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.