What a WebGL Product Page Has to Do to Stay Under 2 Seconds

A founder-voice technical guide to WebGL product page performance: GLB size, texture compression, render-loop scheduling, and the budgets that fit a mobile LCP target.

O
Oeave Team
April 29, 2026
7 min read
What a WebGL Product Page Has to Do to Stay Under 2 Seconds

If you have shipped a WebGL product viewer that loads in 4 seconds on LTE and the conversion lift has not shown up, the issue is rarely the viewer technology. It is the page budget. WebGL viewers fit under a 2-second LCP target on premium DTC if and only if the asset budget, the render loop, and the load timing are all set correctly. This guide covers the three parts of the WebGL performance budget that decide whether the viewer ships fast enough to convert.

What does a WebGL product page have to do to stay under 2 seconds?

A WebGL product page that lands a sub-2-second Largest Contentful Paint on LTE has to fit four constraints. The total payload on the mobile path stays under 3 MB, broken into roughly 200 to 500 KB of viewer code, 1 to 2 MB of compressed model and textures, and the rest for page chrome. The viewer code preloads but the model only fetches when the buyer scrolls near the viewer or taps an affordance. The render loop runs only when something visible is changing, sleeps otherwise to save battery. Textures ship as KTX2 (Basis Universal) and geometry as Draco-compressed GLB. Google's LCP target is 2.5 seconds for a "good" rating at the 75th percentile, and a viewer that meets these four constraints clears that line.

The math is tight on LTE. Every asset gets weighed against the LCP budget. Most production WebGL viewers ship slow because one of the four constraints is loose.

The asset budget in detail

The mobile bundle for a WebGL product page splits roughly:

  • Viewer code (200 to 500 KB). Three.js minified and tree-shaken comes in around 200 KB; with controls, loaders, and post-processing the bundle grows. Stay under 500 KB by removing unused modules and using the modular three/examples/jsm imports rather than loading the whole library.
  • Model geometry (200 to 800 KB). A typical product mesh after Draco mesh compression. Larger products (sofas, lighting fixtures) push toward the high end; smaller products (jewelry, watches) sit at the low end.
  • Model textures (500 KB to 1.2 MB). PBR materials need albedo, metallic-roughness, normal, and sometimes emissive maps. With KTX2 compression at 1K resolution, this fits the budget. With uncompressed JPEG at 2K or 4K, it does not.
  • Environment map (100 to 300 KB). A studio HDR environment for material reflections. KTX2-compressed RGBE works.

These four together fit under 3 MB if every step uses the right format. They blow past 8 MB if any step skips compression. The 8 MB version is the version most production viewers ship.

Texture compression is the biggest win

Textures dominate the model file size on PBR materials. The compression options:

  • JPEG/PNG. The default Blender export. 4K maps run 2 to 5 MB each. Skip these for production.
  • WebP. A 30 to 50% size cut versus JPEG with similar visual quality. Better than nothing; not as good as GPU-native compression.
  • KTX2 / Basis Universal. GPU-native compressed format. 30 to 70% smaller than equivalent JPEG, and the GPU decompresses on the fly without CPU overhead. Modern phones (iOS Safari and Android Chrome) support it cleanly.

For a premium DTC product page, KTX2 is the right choice. The build pipeline cost is a few minutes per product. The mobile LCP win is measurable.

Geometry compression and what to choose

The Khronos glTF spec includes two main geometry compression extensions:

  • Draco mesh compression. Cuts geometry size 50 to 80% with no perceptible quality loss for most product meshes. Universal browser support. The right default.
  • Meshopt compression. Faster decode than Draco; slightly smaller files in some cases. Newer; support is good but verify your viewer setup handles it.

For most premium DTC stores, Draco is the simpler and safer choice. Configure the GLTFLoader with a DRACOLoader pointing at the Draco decoder hosted on a CDN. The decoder itself adds about 100 KB but is shared across all products on the site.

The render loop and battery

WebGL on a static product page is one of the worst battery offenders if the render loop is naive. Most tutorials run the render loop at the browser's animation frame rate (60 fps) regardless of whether anything is happening.

A buyer reading the spec table next to a static viewer does not need the GPU drawing the same model 60 times per second. The fix is an activity-based render loop:

// Pseudocode
let activityCount = 0;
function beginActivity() {
  if (activityCount === 0) startRenderLoop();
  activityCount++;
}
function endActivity() {
  activityCount--;
  if (activityCount === 0) stopRenderLoop();
}

Anything that changes the visible scene calls beginActivity when it starts and endActivity when it stops. Auto-rotation. User drag. Animation. Camera transition. The render loop runs only while at least one activity is active.

A static viewer with this loop drops to 0% GPU when the buyer is reading. The battery cost on mobile drops by an order of magnitude.

Lazy loading is the LCP win

A WebGL viewer that loads on first paint blocks LCP because the GLB and textures sit in the page's critical path. The fix is to defer the model load until the buyer needs it.

The pattern that works:

  1. Page renders with hero photo. LCP is the hero photo, not the viewer.
  2. Viewer code preloads in the background. Add a low-priority script tag or an idle-callback.
  3. Viewer initializes with placeholder. A lightweight 2D placeholder (the hero photo, a still frame, a loading shimmer) sits where the viewer will go.
  4. GLB loads on intersect or interact. When the buyer scrolls the viewer into the viewport (IntersectionObserver) or taps a "rotate" affordance, the GLB and textures fetch and the viewer initializes.
  5. Viewer fades in over the placeholder. A 200 ms cross-fade hides the swap.

This pattern keeps the LCP at the hero photo (fast) while still giving the buyer the viewer when they want it. The 12 MB model that would have killed LCP at the top of the page is now a deferred fetch the buyer triggers consciously.

What about full-screen WebGL

Some premium DTC pages run a full-screen WebGL canvas as the hero (think a hero scene with the product floating in space). For a top-of-funnel landing page, this can work. For a PDP, it almost never converts.

The reasons:

  • The buyer arrives ready to look at the product, not at a hero scene.
  • Full-screen WebGL on first paint kills LCP for everyone, including the buyer who wants to read.
  • The full-screen experience is hard to make accessible (keyboard navigation, screen readers, motion-reduction preferences).

For a PDP, ship the WebGL viewer as a contained block in the product story flow, not as the full-page hero.

How to test the budget

The fastest test is a real phone over a throttled connection.

# In Chrome DevTools, open the page on a real phone via remote debugging.
# Throttle the network to "Slow 3G" or "Fast 3G" and reload.
# Check LCP in the Performance panel.

If the LCP comes in under 2.5 seconds on Slow 3G, the budget is right. If it comes in over 4 seconds, walk the four constraints above and find which one is loose.

Lighthouse and PageSpeed Insights both report LCP. Run the audit weekly on the hero product to catch regressions before the catalog grows.

Where to start

If your WebGL viewer ships slow, the order of operations is:

  1. Switch JPEG/PNG textures to KTX2.
  2. Enable Draco geometry compression on the GLB.
  3. Lazy-load the GLB on intersect or interact.
  4. Add the activity-based render loop.

These four changes usually move a 6-second LCP to under 2 seconds without touching the viewer code itself.

The Three.js production viewer guide covers the renderer setup that ships with the right defaults. The three ways to embed a 3D model covers when to use Three.js versus model-viewer versus an iframe app.

The buyer is on a phone. LCP at 2.5 seconds is the floor; faster wins. The WebGL viewer fits inside that budget if the four constraints are tight.