MAISON CODE .
/ Performance · Core Web Vitals · INP · React

Web Performance: The 100ms Rule and the New INP Metric

Google changed the rules with INP (Interaction to Next Paint). optimizing for React Hydration and Edge Caching.

AB
Alex B.
Web Performance: The 100ms Rule and the New INP Metric

Speed is not a “nice to have.” Speed is the only feature that every user experiences. Amazon found that every 100ms of latency cost them 1% in sales. Walmart found that improving load time by 1 second increased conversions by 2%. In 2025, the bar is higher. Google’s Core Web Vitals have shifted focus from LCP (Loading) to INP (Interaction to Next Paint). It’s no longer enough to load fast. You must respond fast.

Why Maison Code Discusses This

Speed is our brand. We don’t just “optimize images.” We re-architect the entire delivery pipeline.

  • Infrastructure: We deploy to the Edge (Vercel/Cloudflare) to be within 50ms of the user.
  • Hydration: We use Selective Hydration to make the “Buy Button” interactive before the Footer even loads.
  • Result: Our clients consistently pass Core Web Vitals, leading to a measured SEO uplift of 15-20%. We treat performance budgets like financial budgets. Overspending is not an option.

The Speculation Rules API

The fastest network request is the one you don’t make. In 2025, we use the Speculation Rules API to pre-render pages before the user clicks. This is not standard prefetch. Chrome literally constructs the DOM in a background tab. When the user clicks, the transition is 0ms. Instant.

<!-- Injecting Speculation Rules -->
<script type="speculationrules">
{
  "prerender": [
    {
      "source": "document",
      "where": {
        "and": [
          { "href_matches": "/products/*" },
          { "selector_matches": "a:hover" }
        ]
      }
    }
  ]
}
</script>

Safety and Data Usage

One concern with speculation is data usage. If we prerender 10 pages and the user visits 0, we wasted bandwidth. The browser is smart. It only speculates if:

  1. The user is on Wi-Fi (not Data Saver mode).
  2. The device has sufficient memory. However, for an e-commerce site, the “Next Action” is extremely predictable. On a PDP (Product Detail Page), the next action is either “Add to Cart” or “Back to Collection.” Speculating these two routes yields a 90% hit rate.

INP: The React Killer?

INP measures the time from “Click” to “Paint.” React’s Hydration is the enemy of INP. If a user clicks “Add to Cart” while React is hydrating the footer, the main thread is blocked. The button ignores the click. INP score goes to 400ms (Poor). Google penalizes your SEO.

Optimizing INP in Headless

  1. Selective Hydration: Only hydrate the visible components. (See Atomic Design for component isolation).
  2. Web Workers: Move heavy logic (Analytics, GTM) to a Web Worker via Partytown.
  3. Transition API: Use useTransition to mark non-urgent updates.
// React 19 useTransition implementation
import { useTransition } from 'react';

function AddToCart({ id }) {
  const [isPending, startTransition] = useTransition();

  const handleClick = () => {
    // Urgent: Update UI immediately
    setOptimisticCartCount(c => c + 1);
    
    // Non-Urgent: Network request / State update
    startTransition(() => {
      addToCart(id);
    });
  };

  return (
    <button onClick={handleClick} disabled={isPending}>
      {isPending ? 'Adding...' : 'Add to Bag'}
    </button>
  );
}

The Edge: Geography Matters

If your server is in Virginia (us-east-1) and your customer is in Paris, speed of light dictates a 100ms penalty. You cannot beat physics. You must move the compute. Edge Rendering (Oxygen/Vercel) runs the SSR logic in a data center closest to the user.

Stale-While-Revalidate (SWR)

We don’t want to hit the origin for every request. We use the SWR cache strategy, a core benefit of Headless Architecture.

  1. User A visits /products/shoe.
  2. Edge serves the Stale version (Instant).
  3. Edge fetches Fresh version in background.
  4. User B gets the Fresh version.

In Remix/Hydrogen headers:

export function headers() {
  return {
    'Cache-Control': 'public, max-age=60, s-maxage=3600, stale-while-revalidate=86400',
  };
}

Image Optimization: AVIF is King

Forget WebP. AVIF is the standard. It is 30% smaller than WebP and supports HDR color gamuts (essential for fashion). Shopify’s CDN supports AVIF auto-formatting. Always force format=auto in your image Loader.

The “LQIP” Strategy

Low Quality Image Placeholders. While the high-res image loads, show a 10px blurred version. This prevents “Layout Shift” (CLS) and gives the perception of speed. We generate LQIPs at build time or via a serverless function.

Third Party Scripts: The Silent Killer

Agencies love installing scripts. Hotjar, Klaviyo, Yotpo, Facebook Pixel, TikTok Pixel, Snap Pixel. Each one eats 50ms of Main Thread time. Total = 300ms blockage.

The Problem with “Async”

Developers think “Async” means “Non-blocking.” It means “Non-blocking fetch.” But the Execution is blocking. When the Facebook Pixel executes, it freezes the main thread to scrape the DOM.

Solution: Server-Side GTM

Move the pixels to the server.

  1. Browser sends ONE event to your server (e.g. POST /api/events).
  2. Your server (GTM Server Container) receives it.
  3. Your server fans it out to Facebook, TikTok, Google Ads. Zero client-side impact. Bonus: It bypasses AdBlockers because the request goes to your own domain.

Solution: Partytown

If you MUST run client-side scripts (like Hotjar, which needs the DOM), run them in a Web Worker. Partytown is a library that proxies DOM mutations from a Worker to the Main Thread. It creates a sandbox where heavy third-party scripts can run without freezing the UI.

<script type="text/partytown" src="https://connect.facebook.net/en_US/fbevents.js"></script>

Font Loading Strategies

Typography is surprisingly heavy. A custom font file (WOFF2) is ~50kb. If you block rendering until the font loads (FOIT - Flash of Invisible Text), the user stares at a blank screen. If you show fallback font (FOUT - Flash of Unstyled Text), the layout shifts.

The Perfect Font Loading Stack

  1. Subset: Only include the characters you need (Latin-1).
  2. Preload: Use <link rel="preload"> for the critical font (Heading).
  3. Swap: Use font-display: swap. Show text immediately in Helvetica, then swap to your font.
  4. Size Adjust: Use CSS size-adjust to make the fallback font occupy the exact same space as the custom font. This eliminates layout shift.
@font-face {
  font-family: 'Bodoni Fallback';
  src: local('Times New Roman');
  ascent-override: 90%;
  descent-override: 20%;
  size-adjust: 140%;
}

RUM vs Lab Data

Lighthouse (Lab Data) is a simulation. It assumes a Motorola G4 on 3G Network. RUM (Real User Monitoring) is reality. It measures what actual users experience. You might have a 100 Lighthouse score, but if your real users are on old iPhones in a subway, your RUM data will show 3s LCP. We integrate Vercel Analytics or Datadog RUM to track the p75 (75th percentile) of real experiences. If your p75 is green, you are good. If your Lighthouse is 100 but p75 is red, you are failing.

Conclusion

Performance is an engineering culture. You must have a Performance Budget. “If this PR adds 10kb to the bundle, something else must go.” Stick to the 100ms rule. If it takes longer than 100ms, it feels broken. The fastest e-commerce sites don’t just “feel” fast. They feel invisible. The interface disappears, leaving only the desire and the product.


13. Font Subsetting Deep Dive

Why load Cyrillic characters if your site is in English? A standard Google Font file contains 3000 glyphs. We use Subsetting. We generate a WOFF2 file containing ONLY the characters used in our Hero Text. Typically ~50 characters. Size drops from 50KB to 4KB. This enables “Instant Typography”. Tools like glyphhanger automate this analysis during the build process.

14. Resource Hints: The Crystal Ball

preconnect, prefetch, preload. We tell the browser what will happen next. <link rel="preconnect" href="https://cdn.shopify.com"> This sets up the TCP / TLS handshake before the CSS requests the image. It saves ~100ms (Round Trip Time). We inject these headers for our most critical 3rd parties (Shopify CDN, Google Analytics, Vercel Edge). Don’t overuse it (preconnecting to 50 domains slows down the main thread). Stick to the top 3.

15. Conclusion

Gzip is old (1992). Brotli (2013) is the new standard. It compresses text (HTML, CSS, JS) 20% better than Gzip. It is supported by all modern browsers and CDNs (including Shopify). Ensure your build pipeline generates .br files. When sending JSON API responses, compress them too. A 1MB JSON payload becomes 100KB with Brotli.

14. Critical CSS (The Fold)

The browser blocks rendering until it downloads style.css (100KB). But for the “Above the Fold” content (Header + Hero), you only need 5KB of CSS. Critical CSS extracts exactly the styles needed for the viewport and inlines them in the <head>. The rest of the CSS is loaded asynchronously. This improves First Contentful Paint (FCP) dramatically. Next.js handles this automatically, but if you are on a legacy stack, use tools like critters.

15. Conclusion

We do INP Audits and Performance Re-Architecture.


Hire our Architects.