docs / runtime behaviour

Offline-first checklist

Your files are inside the APK, so the app opens with no connection. Whether it is still useful then depends on everything your pages fetch from elsewhere. Work down this list once and the airplane-mode test stops being scary.

7 min read·8 sections·updated Sep 2026
TL;DR

Bundle every library, stylesheet and font your pages load from a CDN; keep API data in IndexedDB so the last copy shows offline; show a clear state for features that genuinely need the network; and test the built app in airplane mode, from a cold start.

What is offline by default

Every file in your ZIP: HTML, CSS, JavaScript, images, fonts, audio, video, JSON, WebAssembly. They are read from the APK, never from the network — so a site that only uses its own files needs nothing on this page. The checklist is about everything else.

1. Libraries and stylesheets from CDNs

Search your HTML for https:// in <script src> and <link href>. jQuery, Bootstrap, Tailwind's CDN build, Font Awesome, Alpine, Chart.js — each one is a hole. Download them into the ZIP and link them relatively.

CDN LocalizerDownload the CDN scripts, stylesheets and Google Fonts a page loads into the ZIP, so it runs offline Open tool

2. Web fonts

A Google Fonts <link> is two network hops: a stylesheet from fonts.googleapis.com, then font files from fonts.gstatic.com. Offline, text falls back to the phone's default font — readable, but the layout shifts. Localize them (the CDN localizer handles the whole chain) or self-host WOFF2 files with @font-face. Icon fonts (Material Icons, Font Awesome) matter more: offline they render as ligature words or empty boxes.

3. Images and media from elsewhere

Logos hot-linked from your website, avatars from Gravatar, product photos from your CMS, a YouTube embed. Bundle what is static. For dynamic images, give <img> a size and a background colour so a missing picture leaves a neat block instead of a collapsed layout, and use onerror to swap in a local placeholder.

4. Data from your API

Keep the last good response and show it when the network fails:

async function loadMenu() {
  try {
    const res = await fetch('https://api.example.com/menu', { cache: 'no-store' })
    const data = await res.json()
    localStorage.setItem('menu', JSON.stringify(data))
    return data
  } catch {
    const cached = localStorage.getItem('menu')
    if (cached) return JSON.parse(cached)
    // first launch with no network: fall back to the copy shipped in the ZIP
    return (await fetch('data/menu.json')).json()
  }
}

Shipping a data/menu.json snapshot inside the ZIP means even the very first launch has content. For larger data, IndexedDB (directly, or via a small wrapper such as idb-keyval) holds hundreds of megabytes.

5. Tell the user what needs a connection

const setOnline = () => document.body.classList.toggle('offline', !navigator.onLine)
addEventListener('online', setOnline)
addEventListener('offline', setOnline)
setOnline()

With a class on the body, CSS can disable the "Send" button and show a quiet banner rather than letting a request fail silently. navigator.onLine only knows whether there is a network, not whether your server is reachable — still wrap real requests in try/catch.

6. Third-party scripts

Analytics, chat widgets, ads and maps load more code at runtime and are useless offline anyway. They must simply fail quietly: load them with async, never let your own code depend on them having loaded (window.gtag && gtag(…)), and keep them out of the critical path of first render.

7. The airplane-mode test

  1. Install the built APK. Open it once online, then close it from recent apps.
  2. Turn on airplane mode (and make sure Wi-Fi is off too).
  3. Open the app from its icon. Visit every screen; use every button.
  4. Note anything unstyled, blank, spinning forever or silently doing nothing — each is a network dependency from the list above.

Why a cold start matters: an app that was open when you went offline may still have CDN files in the WebView's memory cache. Only a fresh launch shows what a user on a train sees.

FAQ

Does the app cache pages it loads from the internet?

The WebView has a normal HTTP cache, so recently loaded remote files may survive a while. Do not rely on it — it is evicted unpredictably and is empty on first launch. Bundle what matters.

How much data can I store offline?

localStorage holds about 5 MB of strings. IndexedDB is limited by free device storage and comfortably holds hundreds of megabytes on a typical phone.

Is the data kept when I publish an update?

Yes, as long as the new version keeps the same package name and signing key. Storage is deleted only when the app is uninstalled or the user clears its data.

Tools for this step

Unzip it on a phone today

Upload the ZIP, name the app, pick an icon — and download a signed APK a few minutes later. Free builds, no watermark, no Android Studio.

Convert a ZIP — free site.zip → app-release.apk