The one rule
In the app, your index.html lives at
https://appassets.androidplatform.net/web/index.html
A browser resolves every URL on the page against that address. Anything that starts with / is resolved from the host, not from your folder — /css/app.css becomes https://appassets.androidplatform.net/css/app.css, which is outside /web/, is not one of your files, and fails. On your web server the same link worked because your site was at the root.
Resolution table
For a page at /web/index.html and one at /web/blog/post.html:
| Written in the page | From /web/index.html | From /web/blog/post.html | OK? |
|---|---|---|---|
css/app.css | /web/css/app.css | /web/blog/css/app.css | ✓ relative to each page |
./css/app.css | /web/css/app.css | /web/blog/css/app.css | ✓ same thing |
../css/app.css | /css/app.css ✗ | /web/css/app.css | ✓ from sub-pages only |
/css/app.css | /css/app.css | /css/app.css | ✗ outside /web/ |
/web/css/app.css | /web/css/app.css | /web/css/app.css | ✓ works, but ties the site to the app |
https://cdn…/lib.js | the CDN | the CDN | ✓ online only |
//cdn…/lib.js | https://cdn… | https://cdn… | ✓ online only |
What each kind of URL is relative to
- HTML attributes (
src,href,srcset,poster,action): the page — or its<base href>, if it has one. - CSS
url()and@import: the stylesheet, not the page.css/app.cssreferencing../fonts/a.woff2finds/web/fonts/a.woff2from every page. fetch(),XMLHttpRequest,new Image().src: the page, even when the call is insidejs/app.js. A script that doesfetch('data.json')needs the path from the HTML file's location.- ES module
import: the importing module. For assets next to a module, usenew URL('./data.json', import.meta.url). - Web workers:
new Worker('js/worker.js')is relative to the page;importScripts()inside it, to the worker script. - manifest.json icons: the manifest file. (The app ignores the web manifest anyway — see the PWA doc.)
<base href>: a shortcut with a trap
A <base href="./"> or <base href="/web/"> in the head makes every relative URL on that page resolve from there. It is how Angular sets its root. The trap: it also changes where in-page anchors (href="#top") point, turning them into navigations to base + #top. If your page uses fragment links, check them after adding a base.
Fixing an existing site
- Search your HTML, CSS and JS for
="/,url(/and'/. Every hit that is a path to your own files needs changing. - In HTML at the root, remove the leading slash:
/img/a.png→img/a.png. In pages inside subfolders, add../per level. - For a framework build, change the setting instead of the output —
base: './'and friends. The build configurator has the setting for each tool. - Rebuild, and check the new
index.html: nosrc="/orhref="/should remain.
Links that should leave the app
Absolute https:// links to other sites are unaffected by any of this: they load normally when the phone is online. How they open — inside the app or in another app — is covered in links, downloads and pop-ups.