Skip to content

Cutting a PWA homepage from 4.8 MB to 70 KB

Published

On 2026-10-09 the dev.to account Shahzaib published a report on PdfWord, a PDF tools site the post describes as free, no-signup and client-side, stating that the installed PWA took a long time to open on phones and that users complained. The post calls what followed a 20-minute fix that cut 98.5% of the weight, and it covers two separate changes: what the landing page downloads, and what the service worker precaches on install (the post).

The post’s first move is a measurement rather than an optimisation. It states that the author measured what the homepage actually downloads before any change was made (the post).

The post’s table gives html2pdf.bundle.min.js at 884 KB, xlsx.full.min.js at 861 KB, docx.min.js at 725 KB, pdf-lib.min.js at 512 KB, pptxgen.bundle.js at 466 KB, jspdf.umd.min.js at 410 KB, and pdf.min.js at 312 KB, followed by a row reading “plus 25 more scripts”. The total the post prints under that table is 4.8 MB of JavaScript on a landing page (the post).

The post’s account of the page’s job is that the homepage is just cards that link to tool pages, and that it needed none of the libraries in that table. Its explanation for their presence is that every library was loaded “just in case”, and it adds that the page also carried 22 dead inline tool panels from an older design (the post).

The fix the post describes is subtraction on the landing page, with the loading of heavy dependencies pushed to the pages that use them (the post).

The post states that it deleted the 22 dead tool panels from the homepage, noting that dedicated tool pages already existed and putting 25 KB of dead HTML on the deletion, and that it removed all 33 heavy <script> tags from the homepage. Its description of the new loading rule is that each tool page still loads only the libraries it needs, which the post labels selective per-page loading (the post).

One script survived the pass. The post states that it kept one file, common.js at 15 KB, for navigation, the theme toggle, and filters. The result it prints for the page is 54 KB of HTML plus 15 KB of JavaScript, which it gives as roughly 70 KB in total, down from 4.8 MB (the post).

The post reports a second change on the service worker side (the post).

From a library precache to an app shell precache

Section titled “From a library precache to an app shell precache”

The post states that it also found the service worker was precaching about 2 MB of libraries on install, that it trimmed the precache to the app shell only at about 120 KB, and that heavy libraries now cache on-demand when their tool page is first opened. It adds that HTML uses a network-first strategy so updates reach installed apps without reinstalls (the post).

MDN documents the caching mechanisms those moves use. On precaching, MDN states that when a service worker is installed the browser fires an install event in the service worker’s global scope, and that at this point the service worker can precache resources, fetching them from the network and storing them in the cache. MDN also states that precaching is an appropriate strategy for resources that the PWA is certain to need, that will not change for this version of the app, and that need to be fetched as quickly as possible, giving the basic user interface of the app as its example and adding that if this is precached then the app’s UI can be rendered on launch without needing any network requests (MDN, Caching).

On caching a resource the first time it is requested, MDN states that when a service worker’s fetch event fires, the service worker may forward the request to the network and cache the resulting response, either if the cache did not already contain a response or to update the cached response with a more recent one. On the strategy the post names for HTML, MDN describes “network first” as trying to retrieve the resource from the network, returning the response and updating the cache if the request succeeds, and trying the cache if it fails, and states that this is useful for requests for which it is important to get the most fresh response possible but for which a cached resource is better than nothing (MDN, Caching).

The post states the rule it now follows as bumping the service worker cache version on every deploy, and gives the failure it is guarding against: installed apps serve stale files forever (the post).

MDN states the same practice in its own words. Its page on the Cache interface says that items in a Cache do not get updated unless explicitly requested and do not expire unless deleted, that you are responsible for implementing how your script handles Cache updates, and that you should make sure to version caches by name and use the caches only from the version of the script that they can safely operate on. It also states that you are responsible for periodically purging cache entries (MDN, Cache).

MDN’s caching guide names where that purge belongs. It states that a PWA should clean up any old versions of its cache in the service worker’s activate event, because when this event fires the service worker can be sure that no previous versions of the service worker are running, so old cached data is no longer needed (MDN, Caching).

The post gives three results. It reports a page load of 422 ms, attributing the figure to Cloudflare Web Analytics on real devices. It reports Core Web Vitals as 100% Good across LCP, INP, and CLS. And it states that the PWA opens instantly on 4G. Its closing advice is to audit what a landing page actually needs, and its characterisation of the pattern it removed is that “just in case” script tags are the silent killer of mobile performance (the post).

The three metric names in that second result have published targets. web.dev states that Largest Contentful Paint measures loading performance and that to provide a good user experience LCP should occur within 2.5 seconds of when the page first starts loading; that Interaction to Next Paint measures interactivity and that pages should have an INP of 200 milliseconds or less; and that Cumulative Layout Shift measures visual stability and that pages should maintain a CLS of 0.1 or less. It adds that a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices, and that tools assessing Core Web Vitals compliance should consider a page passing if it meets the recommended targets at the 75th percentile for all three metrics (web.dev, Web Vitals).

The transferable mechanism in the post is the install-time fetch. MDN describes the service worker’s install handler as an opportunity to fetch resources from the network and store them in the cache, a step that runs when the service worker installs rather than when a page navigates (MDN, Caching).

MDN also states a criterion for what belongs in that precache. It names resources the PWA is certain to need, that will not change for this version of the app, and that need to be fetched as quickly as possible, and gives the app’s basic user interface as the example (MDN, Caching).

The cache-versioning rule the post adopts has a documented basis. MDN states that items in a Cache are not updated unless explicitly requested and do not expire unless deleted, that caches should be versioned by name and used only from the version of the script that can safely operate on them, and in the same place that how long a Cache lives is browser dependent and that a browser may delete all of an origin’s Cache Storage (MDN, Cache).

Finally, web.dev’s framing of Core Web Vitals is a 75th-percentile threshold per metric, segmented across mobile and desktop (web.dev, Web Vitals), which is not the method the post states for its own load figure (the post).

  • Performance: the post’s subject is the startup path of an installed app and the Core Web Vitals metrics it reports against, and this page is where OpenPWA documents those metrics and the budgets around them.
  • Storage: the change the post makes on the service worker side is about what occupies the cache and when it is re-keyed, and this page is where the origin-scoped stores, their quotas, and their eviction rules are covered.
  • Capabilities: the libraries the post moved off the landing page exist to drive platform features from a tool page, and this page is where OpenPWA tracks which of those features a PWA can reach.
  • Which browsers and versions the 422 ms figure was collected from is not stated by the post, so the timing cannot be pinned to an engine.
  • How the Core Web Vitals result was collected, and whether it reflects the 75th percentile threshold web.dev describes, is not stated by the post.
  • Which pages the reported page load covers, and whether it is the landing page alone or the site, is not resolved by the post’s wording.
  • Whether the 4.8 MB figure was measured over the network or summed from file sizes is not stated, so the 98.5% figure the post prints cannot be traced to a method.
  • Whether the app shell precache the post describes is cleaned up in the activate event, which MDN names as the place for that work, is not addressed.
  • What the on-demand caching of the heavy libraries does to the origin’s storage footprint over time is not discussed; MDN states that how long a Cache lives is browser dependent and that a browser may delete all of an origin’s Cache Storage (MDN, Cache).
  • Whether the network-first strategy the post adopted for HTML has a documented offline fallback in this app is not stated.