ScreenshotNeo

BlogEngineering

How to Optimize Media-Heavy Apps with Progressive Web App Techniques

Improve a media-heavy PWA by measuring Core Web Vitals, prioritizing critical assets, deferring non-critical media, and caching only what the experience needs.

By the ScreenshotNeo team4 October 202611 min read

Optimize a media-heavy progressive web app (PWA) by measuring real user experience, making the initial viewport’s most important content load first, sending appropriately sized media, and caching only the assets that support the interface or a defined offline experience. A PWA is still a website: its HTML, CSS, JavaScript, images, video, fonts, and data all compete for network and browser resources. Service workers and browser storage add control over selected caching and offline behavior; they do not make large media free to download or store. web.dev’s guide to PWA assets and data describes that broader asset model.

1. Establish a field-measured baseline

Start with the current Core Web Vitals: Largest Contentful Paint (LCP) for loading, Interaction to Next Paint (INP) for responsiveness, and Cumulative Layout Shift (CLS) for visual stability. The “good” thresholds are LCP at or below 2.5 seconds, INP at or below 200 milliseconds, and CLS at or below 0.1, evaluated at the 75th percentile and segmented by mobile and desktop. These are user-experience targets, not guarantees that every visit will meet a deadline. See web.dev’s Web Vitals guidance.

  • Choose important routes and templates: for example, a media feed, product detail page, or gallery.
  • Use field data where available to see what real users experience; segment by device class and route when the data allows.
  • Use diagnostic tools such as Lighthouse to investigate a specific opportunity, then confirm whether the change improves field results.
  • Record the current metric, suspected bottleneck, and the one change you plan to make. Change one main factor at a time where practical.

A local run is useful for diagnosis but represents a particular device, network, cache state, and page state. Do not treat one run as a substitute for the 75th-percentile field targets.

Single-page app route transitions

Traditional page-load measurements do not automatically describe every in-app route transition. The supplied web.dev SPA FAQ reports that Chrome 151 introduced APIs for measuring Core Web Vitals across SPA route transitions, while other browser engines did not yet support those APIs in its August 11, 2026 update. Browser support can change; check the current status before building a measurement plan around those APIs. Read the SPA FAQ.

2. Find the LCP element and how it is discovered

Identify the element that becomes LCP on the route you are improving. It may be a hero image, a video poster, or a block of text. If it is an image, inspect when the browser discovers its URL and what priority it receives. A resource discovered only after JavaScript runs, a stylesheet downloads, or a lazy-loading library activates may start too late.

  • Put the critical image in the initial HTML when possible, with a real src or srcset rather than adding it only after client-side rendering.
  • Do not lazy-load the image that determines initial LCP. Lazy loading is for below-the-fold and otherwise non-critical content.
  • Give the critical image appropriate priority. If it is otherwise late-discovered, consider a carefully targeted preload; do not preload every image.
  • Check that responsive image selection chooses a suitable source for the viewport. Preloading a different, oversized asset can waste bandwidth.

See web.dev’s LCP optimization guide for resource discovery and prioritization details. An image is the LCP element on approximately 80% of web pages, according to the supplied web.dev business guidance; that makes image delivery a frequent investigation target, not a reason to assume every page’s LCP is an image. Source.

3. Reduce media bytes without removing useful content

Images and video are commonly much larger than text and can compete with the resources needed to render and interact with the initial page. Keep useful media, but deliver it in forms and dimensions suited to the web experience.

  • Generate image variants close to their rendered dimensions instead of shipping a full-resolution source to every viewport.
  • Use responsive image markup such as srcset and sizes when the layout needs different image widths. Choose formats based on your supported browsers and delivery pipeline.
  • Provide explicit width and height, or an equivalent aspect ratio, so the browser can reserve space before an image loads.
  • Lazy-load below-the-fold images and defer non-critical media. Keep the initial viewport’s essential content discoverable.
  • Use a video poster for the initial visual when appropriate, and avoid putting large autoplaying video in a position where it delays the main content unless video is essential to the experience.
  • Review duplicate or unused media requests and avoid downloading full assets when a thumbnail or preview meets the immediate need.

There is no universal total page-weight budget established by the sources for every app. Set budgets using your audience, content, network conditions, and measurements. web.dev’s guidance emphasizes optimizing useful media and its loading order rather than simply removing it. Read the business guidance.

4. Apply a deliberate loading pattern

The PRPL pattern is a useful way to organize work: preload late-discovered resources, render the initial route promptly, precache remaining assets, and lazy-load other routes and non-critical assets. Treat it as a collection of techniques to choose from, not a mandatory bundle. web.dev’s PRPL guide.

  1. Prioritize initial content. Ensure the browser can find the critical HTML, styles, fonts, and LCP resource early.
  2. Defer non-critical work. Load below-the-fold images and other route content when needed, rather than contending with initial rendering.
  3. Use priority hints narrowly. Fetch Priority can influence request ordering, but it does not replace correct markup, suitable resource sizing, or measurement.
  4. Validate across supported browsers and devices. Scheduling and bandwidth are shared; the result depends on the page and the browser.

Example markup for one critical hero image and a later image:

<!-- Critical image: keep discoverable and do not lazy-load it. -->
<img
  src="/media/hero-960.webp"
  srcset="/media/hero-480.webp 480w, /media/hero-960.webp 960w"
  sizes="(max-width: 700px) 100vw, 960px"
  width="960"
  height="600"
  fetchpriority="high"
  alt="A descriptive image of the featured item"
>

<!-- Below-the-fold image: defer until it approaches the viewport. -->
<img
  src="/media/gallery-640.webp"
  width="640"
  height="480"
  loading="lazy"
  alt="A descriptive image from the gallery"
>

Use fetchpriority="high" only for the resource that genuinely deserves early bandwidth. Avoid using it on a long list of images; doing so removes the distinction the hint is meant to provide. For more on priority hints, see web.dev’s Fetch Priority guide.

5. Choose what the service worker caches

Cache Storage controlled by a service worker can make selected responses available later, including offline. Start with the smallest set of resources that supports your chosen experience: the app’s start page, core CSS and JavaScript, interface images, fonts, and any data needed for a basic state. Add large media when caching it serves a clear user need, such as deliberately available offline content.

Asset or situation Reasonable starting decision Question to resolve
Small app shell Consider caching during install if it is needed to start the interface. Can the shell be updated without leaving users on stale code?
Large gallery or video Do not precache everything by default. Does offline access justify download time and storage use?
Section-specific content Consider caching when a user visits that section. How will freshness and invalidation work?
Optional assets Consider fetching when needed or when network conditions are suitable. Can the app remain useful if the asset is unavailable?

Caching every large media file can consume bandwidth and device storage, and stale cached responses can show outdated content. Decide when to cache—during installation for a small shell, after first load, on section entry, or during idle time—based on the product’s offline promise and freshness needs. A useful offline fallback may be more appropriate than a complete offline copy. See web.dev’s PWA caching guide.

A minimal install-time shell example

This example illustrates a deliberately small precache list. Replace the paths with assets your app actually needs, version the cache when your shell changes, and provide an update and cleanup strategy appropriate to your deployment. It does not implement a complete offline media strategy.

const CACHE_NAME = "app-shell-v1";
const SHELL_URLS = ["/", "/app.css", "/app.js", "/offline.html"];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_URLS))
  );
});

self.addEventListener("fetch", (event) => {
  const request = event.request;
  if (request.method !== "GET") return;

  const url = new URL(request.url);
  if (url.origin !== self.location.origin) return;

  event.respondWith(
    caches.match(request).then((cached) => {
      if (cached) return cached;
      return fetch(request).catch(async () => {
        if (request.mode === "navigate") {
          const offline = await caches.match("/offline.html");
          if (offline) return offline;
        }
        throw new Error("Network request failed and no fallback is cached");
      });
    })
  );
});

For production, plan for cache versioning, old-cache cleanup, failed precache entries, and the difference between navigations and asset requests. Avoid assuming that a cache-first response is fresh enough for every route or media item. Service-worker caching behavior is a product decision as much as an implementation detail.

6. Re-measure after each focused change

Repeat the baseline process after changing a specific bottleneck. Compare results by route and device class where possible, and check the relevant dimensions:

  • LCP: Did the main content appear sooner? Was the critical resource discovered earlier and given suitable priority?
  • Initial bytes and requests: Did non-critical media stop competing with the initial viewport?
  • INP: Did media decoding, scripting, or other main-thread work make interactions less responsive after content appeared?
  • CLS: Did reserving image and video space prevent layout movement?
  • Repeat visits: Did caching improve return visits without serving content that is too stale?
  • Offline behavior: Does the cached subset support the promised experience at acceptable download and storage cost?
  • Browser coverage: Do the APIs and hints work across the browsers your app supports?

Keep the measurement loop simple: set a metric goal, identify a bottleneck, change one thing, then measure again. web.dev recommends combining field experience with diagnostics to find and address opportunities. PWA assets and data.

7. Troubleshooting common problems

Symptom Likely cause What to check or change
The hero image appears late despite being small. The URL is discovered after JavaScript or CSS, is lazy-loaded, or receives low priority. Expose it in initial HTML, remove lazy loading from the LCP image, and consider targeted priority or preload only after checking the request waterfall.
Mobile LCP is much worse than desktop. An oversized source, constrained bandwidth, or competing requests may delay the initial content. Inspect the selected responsive source and initial requests; serve a suitable image size and defer non-critical media.
Images cause content to jump. The browser has no reserved dimensions before the asset loads. Set width and height or an aspect ratio matching the rendered media.
A lazy image never appears. It may be incorrectly marked critical, have invalid source markup, or rely on script behavior that did not run. Inspect the element and network request; ensure below-fold images have valid URLs and do not depend on a failed script to become visible.
First visit is slow after adding precaching. Installation is downloading too many or too-large resources. Reduce the install-time shell; defer optional media until it is needed or choose not to cache it.
Users see old images or app files. Cache versioning or invalidation does not match the deployment lifecycle. Review cache names, update activation, and which responses are cache-first; define freshness expectations for each asset class.
Offline navigation fails even though some assets are cached. The navigation fallback or required shell resources were not included, or the fetch handler does not cover that request. Test the intended offline route and ensure the fallback is precached and returned for the relevant navigation.
INP worsens when a gallery loads. Image decoding, rendering, or related script work may be competing with user input on the main thread. Inspect interaction and main-thread diagnostics, reduce unnecessary work, and avoid loading the whole gallery at once.
A preload does not improve LCP. The preloaded URL may not match the responsive image selected, or the bottleneck may be elsewhere. Confirm the actual requested resource and LCP timing before keeping the preload; remove hints that do not help.

8. Performance, reliability, and cost considerations

Every media request has tradeoffs: network transfer costs time and bandwidth, image processing and decoding use device resources, and cached bytes use storage. Aggressively downloading media can burden limited connections and devices; caching it can also increase repeat-visit usefulness when the content is needed offline. There is no universal offline quota or total-byte target in the cited guidance, so derive budgets from user needs and measured behavior.

For reliability, make the interface useful when optional media fails: reserve its layout space, provide a sensible placeholder or fallback, and avoid making the whole screen depend on a non-essential image request. For freshness, define which assets can safely be reused and how updates reach clients. For performance, prioritize the initial experience and verify the result in field data after deployment.

Or skip the browser setup

When you need a screenshot of a route to inspect its layout or document a visual change, ScreenshotNeo is a website screenshot API and MCP server for developers. Its API can capture a URL as an image or PDF; the request below uses the supplied example endpoint and saves the response body. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed, and response headers report the page verdict and billing status. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card.

Frequently asked questions

Should every PWA work fully offline?

No. Choose an offline experience that fits the product. A useful shell or fallback can be enough; cache larger content when offline access provides clear value.

Should I preload every image above the fold?

No. Preload is most useful for a genuinely critical resource that would otherwise be discovered late. Too many high-priority requests compete with one another.

Does a service worker make media-heavy pages faster automatically?

No. It gives the app control over selected caching and offline responses. Poorly chosen caching can waste bandwidth and storage or serve stale resources.

What should I fix first?

Use field data to pick a route and metric, then inspect the LCP element and initial media requests. Fix the measured bottleneck and compare results again.