Relative Asset Paths in index.html Break Silently When a CDN Serves the File for Deep-Link Routes
TLDR
Relative asset paths in a root index.html work at / but silently break when a CDN serves the same file for deep-link routes like /app/pdf/. The browser resolves assets/css/main.css relative to the current URL, not the file's original location. Every asset 404s. The page renders as unstyled HTML with no JavaScript. The fix is one character per path: add a leading /.
Symptom
Navigating to https://example.com/ loads the app correctly. Navigating directly to https://example.com/app/pdf/ loads a page that looks like raw HTML: no styles, no fonts, no JavaScript. The browser devtools show a cascade of 404s:
GET /app/pdf/assets/css/portfolio.css 404
GET /app/pdf/assets/os/contracts.js 404
GET /app/pdf/assets/components/shell.js 404
The CDN _redirects rule (/app/* /index.html 200) is working correctly. The file is being served. It just cannot load its dependencies.
Why It Happens
Relative URLs are resolved by the browser against the current document URL, not the file's location on disk. When index.html is at / and contains <link href="assets/css/main.css">, the browser resolves it to /assets/css/main.css. Correct.
When the same index.html is served at /app/pdf/ (via a CDN rewrite rule), the browser resolves assets/css/main.css relative to /app/pdf/. The resolved URL is /app/pdf/assets/css/main.css. That path does not exist.
The CDN rewrite serves the HTML correctly. The HTML itself is the problem. Every relative path becomes wrong the moment the file is served from any route other than its own directory.
The Fix
<!-- wrong: relative paths work only at / -->
<link rel="stylesheet" href="assets/css/main.css" />
<script src="assets/js/app.js" defer></script>
<!-- correct: absolute paths work from any route --> <link rel="stylesheet" href="/assets/css/main.css" /> <script src="/assets/js/app.js" defer></script>
Absolute paths starting with / are resolved against the origin, not the current route. /assets/css/main.css always resolves to https://example.com/assets/css/main.css regardless of what URL the browser is currently at.
This applies to every asset reference in the root index.html: stylesheets, scripts, fonts loaded via <link>, any src or href that points to a local file.
How to Prevent It
Any index.html that will be served for routes other than its exact directory path must use absolute asset references. Two situations where this always applies:
- SPA with a catch-all rewrite rule.
/* โ /index.htmlmeans the file is served for every route. All paths must be absolute. - Specific deep-link rewrites.
/app/* โ /index.html 200means the file is served for any/app/...route. Same requirement.
src=" and href=" that do not start with /, https://, or http://. Any that remain are relative and will break under a rewrite rule.
grep -E '(src|href)="(?!/|https?://)' index.html
If this outputs anything that is not an anchor (<a href="#section">) or a data URI, it needs a leading /.
The Generalizable Lesson
Any file served by a rewrite rule rather than its own canonical URL must use absolute asset paths. The rewrite is invisible to the browser; the browser only sees the URL in the address bar and resolves all relative paths against it.