Building a PWA PDF Editor: Lessons from Shipping ILikePdf
Why PWA for a PDF Editor
PDF processing is fundamentally a local operation — your browser reads a file, manipulates bytes, writes a result. There's no reason this should require a network connection. A Progressive Web App architecture makes the entire ILikePdf toolset installable and fully functional offline. This article covers the PWA decisions we made: service worker strategy, WebAssembly caching, manifest configuration, and the trade-offs in making a data-heavy application work as an installable app.
Service Worker Strategy: Cache-First for Assets, Network-Only for Nothing
ILikePdf is a static application. There are no API endpoints to fetch, no server-side rendering, no dynamic content. Every byte needed to run the tools — HTML pages, JavaScript bundles, CSS, fonts, and the critical WebAssembly modules — ships as static assets. This makes the caching strategy straightforward: cache everything on install, serve from cache on every request, and never fall back to the network.
event.waitUntil(
caches.open('ilikepdf-v1').then(cache =>
cache.addAll([
'/',
'/index.html',
'/compress-pdf.html',
'/merge-pdf.html',
'/split-pdf.html',
'/assets/js/lib/wasm/ghostscript.wasm',
'/assets/js/lib/wasm/ghostscript.js',
'/assets/css/main.css',
'/manifest.json',
])
)
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
});
The critical insight: by precaching the WASM modules during installation, we guarantee the tools are available offline without any additional lazy-loading logic. The trade-off is a larger initial download — about 8 MB for the Ghostscript WASM binary. We handle this by showing a clear installation progress indicator and deferring the service worker activation until after the user's first interaction.
Caching WebAssembly Modules
WASM modules present a unique caching challenge. They are large binary files that the browser compiles before execution. If we cache them via the standard Cache API, the browser must re-compile them on each page load. We experimented with two approaches. The first was compilation caching via `WebAssembly.compileStreaming()` combined with the Cache API — we store the compiled module in IndexedDB as a `WebAssembly.Module` object. The second was simple binary caching with streaming compilation on each load.
We chose the latter for ILikePdf. IndexedDB serialization of compiled modules has inconsistent performance across browsers and is slower than re-compiling from a local cache on most modern hardware. The M1 Mac compiles the 8 MB Ghostscript WASM in under 200 ms. On mobile, it takes roughly 500 ms — acceptable when the alternative is waiting for a server upload.
Manifest Configuration for Installability
The web manifest must meet specific criteria for the browser to offer installation. Our manifest includes all required fields plus several that improve the installable experience:
"name": "ILikePdf — Free PDF Tools",
"short_name": "ILikePdf",
"description": "Free online PDF tools — merge, split, compress, edit. No upload, no sign-up.",
"start_url": "/",
"display": "standalone",
"display_override": ["window-controls-overlay"],
"background_color": "#0f0f0f",
"theme_color": "#3b82f6",
"icons": [
{ "src": "/assets/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/assets/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/assets/icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
Key decisions: we set `display: standalone` for a native-like chrome-less experience. The `window-controls-overlay` display override enables the title bar area customization on desktop PWA installs. We include a maskable icon for Android adaptive icon support.
Offline First: The Technical Flow
When a user visits ILikePdf for the first time, the page loads, registers the service worker, and begins precaching. The user can start using tools immediately — without installation — because the page itself is functional. When the service worker finishes installing, it triggers a `waiting` state. We listen for this and prompt the user to install the PWA if they haven't already.
Once installed and the service worker is active, every subsequent page load serves from the cache. The user can enable airplane mode and process PDFs normally. All 46+ tools work offline because none of them require server communication. The only feature that degrades is analytics — we defer non-critical analytics events to IndexedDB and flush them when connectivity returns.
Lessons Learned
- Precache WASM aggressively: Users expect PDF tools to work instantly. Waiting for WASM download on first tool use creates a poor experience. Precache everything during service worker install.
- Handle stale manifests: When you add new tools or pages, the updated HTML won't be served until the service worker updates. We use a versioned cache name (ilikepdf-v1, ilikepdf-v2) and prompt users to refresh when a new version is available.
- Test offline rigorously: Chrome DevTools offline mode and WebPageTest's offline testing caught edge cases where we referenced external fonts or CDN-hosted icons that broke in offline mode. Self-host everything.
- Manifest icons must exist: Missing icons in the manifest prevent the browser from offering installation. We validate icon paths during the build step.
- IndexedDB for user state: Service workers cannot access localStorage. We use IndexedDB for any user preferences or processing history that needs to persist across sessions.