Hiring? Start here →

Same store, second storefront

Open the Liquid theme version

PORTVA also runs as a hand-built Liquid theme on the same Shopify store. Shopify development stores always show a password page first — paste the password once and your browser remembers it.

Store passwordpaucai
PORTVA

Your cart

Your cart is empty.

Continue shopping

Performance report

The 3D stays, the cost moves off the first load

The home page ships a real-time 3D scroll story. On a mid-range phone it used to tie up the main thread for seconds, and PageSpeed Insights couldn't finish testing it. These are the changes, the numbers, and what each one costs.

PageSpeed Insights, mobile

PageScoreLCPTBT
HomeBeforeDidn't finish (2 attempts: the renderer crashed, then the run timed out)
After952.6 s80 ms
Product pageBefore933.2 s40 ms
After943.0 s50 ms
CollectionBefore992.0 s10 ms
After982.4 s20 ms

What the browser has to do (local build)

Home

JavaScript
557 → 295 KB-47%
Total transfer
1,018 → 438 KB-57%
Images
23 → 23 KB
Main-thread work
4,435 → 1,596 ms-64%
TBT
522 → 55 ms-89%

Product page

JavaScript
297 → 298 KB
Total transfer
721 → 560 KB-22%
Images
297 → 133 KB-55%
Main-thread work
1,207 → 1,128 ms-7%
TBT
45 → 48 ms+7%

Collection

JavaScript
295 → 295 KB
Total transfer
515 → 515 KB
Images
102 → 102 KB
Main-thread work
740 → 824 ms+11%
TBT
55 → 52 ms-5%

What changed, and what it costs

  1. 01

    three.js waits for the first interaction

    The home hero shows the product render first. three.js, React Three Fiber and the model load on the first scroll, touch or mouse move — which is also when the story starts moving.

    Trade-off: On a phone the live model replaces the poster about a second after the first touch. The poster is the same render, so the swap is a cross-fade, not a jump.

  2. 02

    The canvas renders only while scrolling

    The WebGL canvas used to draw 60 frames a second even on a still page. It now renders a frame only when the scroll timeline moves the model. On machines without a GPU (like PageSpeed's), the old loop never let the page go idle.

    Trade-off: Any future idle animation has to request its own frames.

  3. 03

    The buy area is in the prerendered HTML

    The product view read ?variant= itself, which made the photo, title and price a dynamic hole: the LCP image was only discovered when that part streamed in. The static shell now renders the default variant, so the image preload sits in <head>, and the requested variant replaces it in the same response.

    Trade-off: A deep link to another colour can show the default colour for a moment on a slow connection.

  4. 04

    Images are staged

    Only the photo you arrive on loads with the page. Other gallery photos and swatch colourways mount once the page is idle, then stay mounted so switching is instant. Thumbnails fetch at low priority.

    Trade-off: Hovering a swatch in the first second can show the default colour until its image arrives.

  5. 05

    Lifestyle scenes are pre-sized

    The scene renders were served as 2560 px JPEGs (~180 KB) at every size. A script now writes 640/1080/1920 px WebP versions (6–27 KB) and the image loader picks the nearest one.

    Trade-off: New renders need the script re-run (scripts/scene-variants.mjs).

How it was measured

  • PageSpeed Insights, mobile: Lighthouse 13.6, emulated Moto G Power on simulated slow 4G, run from Google's servers (test location: Asia). Anyone can rerun it from the links below.
  • “Before” is one run of the version that had been live for hours. “After” is the median of three runs (by LCP), taken after one extra run that warms Vercel's edge cache following the deploy.
  • Bytes and main-thread time come from Lighthouse 12.8 on a local production build (median of 3), because they barely depend on the network.

What these numbers don't show

  • On the product and collection pages the PageSpeed difference is within run-to-run noise: the same code measured LCP between 2.0 and 3.7 s on different runs, with the server answering in under 10 ms. Their gain is in what the browser downloads (product page images −55%), not in a provable LCP change.
  • This is lab data. The store has no real traffic, so there is no field (CrUX) data to confirm it.
  • Single PageSpeed runs vary by a few points; treat small differences as noise.
  • The Liquid storefront sits behind the dev store's password, which PageSpeed can't pass, so it isn't in this table.
  • Images come from Shopify's CDN; LCP on a real phone depends mostly on the network between the visitor and that CDN.

Code: before 993ba71, after 97e7b63.