A folder converts to an APK cleanly when index.html is at its root, every path in it is relative, nothing points at the internet, and file names match their references exactly, including case. The builder copies the folder into the app's assets and loads index.html; from then on the WebView resolves paths exactly as a browser opening the file from a local disk would. Get those four things right and a multi-page site with images, fonts and libraries works offline in the app the same as it does on your desktop.
The reference layout
my-app/
├── index.html ← must be here, at the top level
├── about.html
├── css/
│ └── style.css
├── js/
│ ├── vendor/
│ │ └── chart.min.js ← downloaded, not linked from a CDN
│ └── app.js
├── img/
│ ├── logo.png
│ └── hero.webp
└── fonts/
└── inter.woff2
And in index.html:
<link rel="stylesheet" href="css/style.css">
<img src="img/logo.png" alt="Logo">
<script src="js/vendor/chart.min.js"></script>
<script src="js/app.js"></script>
Note what is absent: no leading slash on any path, no https:// anywhere, no type="module".
Rule 1 — index.html at the root
The builder looks for index.html at the top level of the ZIP. The classic mistake is zipping the folder rather than its contents, so the archive contains my-app/index.html instead of index.html. Most builders (including this one) look one level down and cope, but nested deeper — site/dist/index.html — and the app opens to a blank screen or a "file not found". On macOS, select the files inside the folder, right-click, Compress. On Windows, same: select the contents, Send to → Compressed folder.
Rule 2 — every path is relative
On a web server /css/style.css means "from the site root". Inside an APK there is no site root — / is the root of the device's file system, and the stylesheet is not there. Write css/style.css or ./css/style.css instead. From a page in a subfolder, go up with ../css/style.css. This applies to href, src, CSS url(), and any path your JavaScript builds. Static-site generators and frameworks often emit root-relative paths by default; look for a "base path" or "relative URLs" option and set it before exporting.
Rule 3 — nothing from the internet
A <script src="https://cdn…"> works only while the phone is online, and it delays the first paint even then. Download the file into js/vendor/ and reference it locally. Same for CSS frameworks, icon fonts and web fonts: Google Fonts can be downloaded as .woff2 files and declared with @font-face. Analytics tags are the exception you may keep on purpose — they fail silently offline — but consider whether an offline app needs one.
Rule 4 — names match exactly
Windows and macOS file systems ignore case; Android's does not. Logo.PNG on disk and logo.png in the HTML works on your laptop and 404s in the app. Use lower-case names with hyphens for everything and the problem never arises. Avoid spaces too — my image.png must be written my%20image.png in HTML and someone will forget.
Frameworks and build tools
| Tool | What to set | Then |
|---|---|---|
| Vite | base: './' in vite.config.js | Build; the dist folder is your app. Convert type="module" to a classic script — see the fixes guide. |
| Create React App | "homepage": "." in package.json | Build; zip the build folder's contents. |
| Next.js | output: 'export', assetPrefix: './', trailingSlash: true | Static export only; server features do not apply. |
| Hugo / Jekyll / Eleventy | Relative URLs option (relativeURLs = true in Hugo) | Build; zip the output folder's contents. |
| Hand-written HTML | Nothing | Follow the four rules. |
Multi-page navigation
Links between your own pages work as they do on a desktop: <a href="about.html"> loads the bundled file. The Android back button walks back through the pages, and exits the app from the first one. Links to external sites open in the phone's browser — usually what you want, since the user is leaving your bundle. Keep a way back to index.html on every page; there is no address bar to type into.
Checking before you zip
- Open
index.htmlfrom disk in Chrome. Click every link, load every page. Watch the console (F12) for red 404s — each one is a wrong path or a wrong name. - Disconnect from the network and reload. Anything that disappears was coming from the internet.
- Paste each page into the Offline Readiness Checker for the things the console does not show — module scripts, root paths in JavaScript, fetch calls.
- Zip the contents. Check the ZIP's size against the tier limit. Upload.