Skip to content
~sami-ullah

Concept project · runs in your browserTypeScript · React.js · Next.js

WordPress Speed Optimization: A Load Simulator Demo

A page-load simulator for a typical WordPress home page with a slider, a page builder and Google Fonts. Switch fixes on and off (page cache, image formats, lazy loading, deferred scripts, critical CSS, self-hosted fonts, fewer plugins) and the modelled waterfall, LCP, CLS, TBT and request count update. The numbers come from a model I wrote and show in full, not from a real site.

Read the case study →

Interactive demo

samiullah.tech/lab/wordpress-speed-optimization

Modelled, not measured. These numbers come from the simulation in model.ts (open “How the model works” below or View source). They show how each fix changes the shape of the page load, not what a real site would score.

LCP

19.3 s

Rating: poor. before: 19.3 s

FCP

8.9 s

Rating: poor. before: 8.9 s

TBT

290 ms

Rating: needs improvement. before: 290 ms

CLS

0.250

Rating: needs improvement. before: 0.250

Requests

27

before: 27

Transferred

3.48 MB

before: 3.48 MB

optimisations (0/11)
waterfallhtmlcssjsfontimg connect / wait FCP LCP
request
0s4s8s12s16s
  1. main thread

Grey = connection setup and waiting for the first byte. Red on the main thread = the part of a task over 50 ms (counts towards TBT after first paint).

/wp-content/uploads/hero-1.jpg
type / mode
img / none
size
480 KB
priority weight
1
found by
exec: slider-js
discovered
8.8 s
first byte
8.9 s
finished
19.3 s
note
LCP element

CLS breakdown (fixed estimates)

  • Slider injects the hero after its script runs0.150
  • Logo without width/height0.020
  • Share bar injected by JavaScript0.060
  • Text reflows when web fonts arrive0.020

## How it works

buildResources() turns the toggles into a list of requests: each has a size, an origin, how it is found (by the HTML, by a stylesheet, or by a script after it runs) and whether it blocks rendering. Turning on "replace slider" removes two requests and moves the hero image from "found by slider.js" to "found in the HTML".

simulate() steps through time in 5 ms ticks with Lighthouse's mobile throttling numbers: 150 ms round trips, about 1.6 Mbps shared by priority, three round trips to open a new origin, and one main thread running at a quarter speed.

metrics() reads the timeline: first paint waits for blocking CSS, head scripts and (without swap) web fonts; LCP is the hero image; TBT adds up the part of each task over 50 ms after first paint. That is why deferring scripts can make TBT worse in the model, which also happens on real pages.

## View source

The code running in the window above, exactly as deployed.

app/lab/wordpress-speed-optimization/model.ts355 lines
/**
 * A transparent page-load model for a typical WordPress home page. It is NOT a measurement of a real site.
 *
 * What it does: builds the list of requests a page makes (depending on which optimisations are on),
 * then simulates loading them over a throttled mobile connection in 5 ms steps:
 *   - each new origin costs DNS + TCP + TLS = 3 round trips,
 *   - every request waits one round trip (plus server time) for its first byte,
 *   - downloads share the bandwidth by priority (HTML, CSS and fonts get more than images),
 *   - one main thread runs HTML parsing and scripts, slowed down like a mid-range phone,
 *   - resources are discovered by the HTML (preload scanner), by CSS, or by a script after it runs.
 * Metrics (FCP, LCP, TBT, CLS) are derived from that timeline with the simple rules in `metrics()`.
 * Constants follow Lighthouse's mobile throttling preset (150 ms RTT, ~1.6 Mbps, 4x CPU); the rest are my estimates.
 */

/* ------------------------------------------------------------------ */
/* Constants                                                           */
/* ------------------------------------------------------------------ */

export const MODEL = {
  rttMs: 150,
  bandwidthKBps: 200, // ≈ 1.6 Mbps
  cpuSlowdown: 4,
  stepMs: 5,
  connectRtts: 3, // DNS + TCP + TLS for a new origin
  serverMsUncached: 900, // PHP + MySQL with ~25 active plugins
  serverMsPerUnusedPluginSet: 150, // what removing the unused plugins saves on an uncached request
  serverMsCached: 40, // full-page cache hit (static HTML from disk or memory)
  staticServerMs: 10,
  thirdPartyServerMs: 40,
  fontBlockMs: 3000, // font-display: block/auto hides text for up to 3 s
  renderMs: 20,
  /** Bandwidth share per request, like Chrome's priorities: render-blocking first, images last. */
  weights: { document: 10, highest: 4, high: 3, medium: 2, low: 1 },
  longTaskMs: 50,
} as const;

export type OptId =
  | "pageCache"
  | "imageFormats"
  | "dimensions"
  | "lazyLoad"
  | "replaceSlider"
  | "preloadHero"
  | "deferJs"
  | "criticalCss"
  | "fonts"
  | "removePlugins"
  | "delayThirdParty";

export const OPTIMISATIONS: { id: OptId; label: string; how: string }[] = [
  { id: "pageCache", label: "Full-page cache", how: "Serve cached HTML instead of running PHP and MySQL on every visit. Server time 900 ms → 40 ms." },
  { id: "removePlugins", label: "Remove unused plugin assets", how: "Dequeue the contact form and share-button CSS/JS on pages that do not use them; deactivate one unused plugin. −3 requests, −150 ms uncached server time." },
  { id: "replaceSlider", label: "Replace slider with a static hero", how: "No slider CSS/JS, no extra slides, and the hero image is in the HTML instead of being injected by JavaScript." },
  { id: "imageFormats", label: "AVIF/WebP + right-sized srcset", how: "Images re-encoded and served at the size they are shown. Bytes × 0.3." },
  { id: "dimensions", label: "width/height on images", how: "The browser reserves space before images arrive, so content does not jump." },
  { id: "lazyLoad", label: "Lazy-load below-the-fold images", how: "loading=\"lazy\" on the gallery only; they load when scrolled to, not during page load." },
  { id: "preloadHero", label: "Preload hero with fetchpriority=high", how: "The hero is requested from the <head> at high priority instead of when it is found." },
  { id: "deferJs", label: "Defer JavaScript", how: "Scripts stop blocking the first paint. They still run (and can still block input) after parsing." },
  { id: "criticalCss", label: "Inline critical CSS, load the rest async", how: "+14 KB in the HTML; stylesheets no longer block rendering." },
  { id: "delayThirdParty", label: "Load analytics on first interaction", how: "gtag.js is injected after the first scroll, tap or key press, so it is not part of the page load." },
  { id: "fonts", label: "Self-host 2 font files, font-display: swap", how: "No Google Fonts origins, 4 weights → 2, preloaded, text shows immediately in a size-adjusted fallback." },
];

export type Opts = Record<OptId, boolean>;
export const NONE: Opts = Object.fromEntries(OPTIMISATIONS.map((o) => [o.id, false])) as Opts;
export const ALL: Opts = Object.fromEntries(OPTIMISATIONS.map((o) => [o.id, true])) as Opts;

/* ------------------------------------------------------------------ */
/* Resources                                                           */
/* ------------------------------------------------------------------ */

export type ResType = "html" | "css" | "js" | "font" | "img";
export type Mode = "blocking" | "async" | "sync" | "defer" | "lazy" | "none";

export type Resource = {
  id: string;
  url: string;
  type: ResType;
  origin: "self" | "fonts.googleapis.com" | "fonts.gstatic.com" | "www.googletagmanager.com";
  kb: number;
  /** How the browser finds it: from the HTML at a position (0–1), after a CSS file loads, or after a script runs. */
  discover: { by: "nav" } | { by: "html"; pos: number } | { by: "css"; id: string } | { by: "exec"; id: string };
  mode: Mode;
  cpuMs?: number; // script execution / parse cost on a fast desktop
  high?: boolean;
  lcp?: boolean;
  note?: string;
};

export function buildResources(o: Opts): Resource[] {
  const imgKb = (kb: number) => Math.round(o.imageFormats ? kb * 0.3 : kb);
  const imgExt = o.imageFormats ? ".avif" : ".jpg";
  const cssMode: Mode = o.criticalCss ? "async" : "blocking";
  const jsMode: Mode = o.deferJs ? "defer" : "sync";
  const r: Resource[] = [
    { id: "doc", url: "/", type: "html", origin: "self", kb: 95 + (o.criticalCss ? 14 : 0), discover: { by: "nav" }, mode: "none", cpuMs: 30, high: true },
    { id: "theme-css", url: "/wp-content/themes/flavor/style.css", type: "css", origin: "self", kb: 180, discover: { by: "html", pos: 0.02 }, mode: cssMode },
    { id: "builder-css", url: "/wp-content/plugins/page-builder/frontend.min.css", type: "css", origin: "self", kb: 120, discover: { by: "html", pos: 0.03 }, mode: cssMode },
  ];
  if (!o.replaceSlider) r.push({ id: "slider-css", url: "/wp-content/plugins/hero-slider/slider.css", type: "css", origin: "self", kb: 35, discover: { by: "html", pos: 0.04 }, mode: cssMode });
  if (!o.removePlugins) r.push({ id: "cf7-css", url: "/wp-content/plugins/contact-form-7/styles.css", type: "css", origin: "self", kb: 12, discover: { by: "html", pos: 0.05 }, mode: cssMode, note: "unused on this page" });

  if (o.fonts) {
    for (const w of ["400", "700"]) r.push({ id: `font-${w}`, url: `/wp-content/themes/flavor/fonts/inter-${w}.woff2`, type: "font", origin: "self", kb: 20, discover: { by: "html", pos: 0.01 }, mode: "none", high: true });
  } else {
    r.push({ id: "gfonts-css", url: "fonts.googleapis.com/css2?family=Inter:wght@400;600;700", type: "css", origin: "fonts.googleapis.com", kb: 2, discover: { by: "html", pos: 0.06 }, mode: "blocking" });
    for (const w of ["400", "400i", "600", "700"]) r.push({ id: `font-${w}`, url: `fonts.gstatic.com/s/inter/v13/${w}.woff2`, type: "font", origin: "fonts.gstatic.com", kb: 24, discover: { by: "css", id: "gfonts-css" }, mode: "none" });
  }

  r.push({ id: "gtag", url: "www.googletagmanager.com/gtag/js", type: "js", origin: "www.googletagmanager.com", kb: 90, discover: { by: "html", pos: 0.06 }, mode: o.delayThirdParty ? "lazy" : "async", cpuMs: 55 });
  r.push({ id: "jquery", url: "/wp-includes/js/jquery/jquery.min.js", type: "js", origin: "self", kb: 87, discover: { by: "html", pos: 0.07 }, mode: jsMode, cpuMs: 25 });
  r.push({ id: "jquery-migrate", url: "/wp-includes/js/jquery/jquery-migrate.min.js", type: "js", origin: "self", kb: 11, discover: { by: "html", pos: 0.08 }, mode: jsMode, cpuMs: 4 });
  r.push({ id: "builder-js", url: "/wp-content/plugins/page-builder/frontend.min.js", type: "js", origin: "self", kb: 150, discover: { by: "html", pos: 0.09 }, mode: jsMode, cpuMs: 70 });
  if (!o.replaceSlider) r.push({ id: "slider-js", url: "/wp-content/plugins/hero-slider/slider.min.js", type: "js", origin: "self", kb: 110, discover: { by: "html", pos: 0.1 }, mode: jsMode, cpuMs: 60, note: "builds the hero from JSON" });

  r.push({ id: "logo", url: `/wp-content/uploads/logo${o.imageFormats ? ".svg" : ".png"}`, type: "img", origin: "self", kb: o.imageFormats ? 6 : 40, discover: { by: "html", pos: 0.12 }, mode: "none" });

  const heroDiscover: Resource["discover"] = o.preloadHero ? { by: "html", pos: 0 } : o.replaceSlider ? { by: "html", pos: 0.15 } : { by: "exec", id: "slider-js" };
  r.push({ id: "hero", url: `/wp-content/uploads/hero-1${imgExt}`, type: "img", origin: "self", kb: imgKb(480), discover: heroDiscover, mode: "none", lcp: true, high: o.preloadHero, note: "LCP element" });
  if (!o.replaceSlider) {
    for (const n of [2, 3]) r.push({ id: `hero-${n}`, url: `/wp-content/uploads/hero-${n}${imgExt}`, type: "img", origin: "self", kb: imgKb(450), discover: { by: "exec", id: "slider-js" }, mode: "none", note: "hidden slide" });
  }
  for (let i = 1; i <= 6; i++) {
    r.push({ id: `gallery-${i}`, url: `/wp-content/uploads/gallery-${i}${imgExt}`, type: "img", origin: "self", kb: imgKb(180), discover: { by: "html", pos: 0.35 + i * 0.07 }, mode: o.lazyLoad ? "lazy" : "none", note: "below the fold" });
  }
  if (!o.removePlugins) {
    r.push({ id: "cf7-js", url: "/wp-content/plugins/contact-form-7/index.js", type: "js", origin: "self", kb: 30, discover: { by: "html", pos: 0.9 }, mode: jsMode, cpuMs: 15, note: "unused on this page" });
    r.push({ id: "share-js", url: "/wp-content/plugins/social-share/share.js", type: "js", origin: "self", kb: 45, discover: { by: "html", pos: 0.92 }, mode: jsMode, cpuMs: 40, note: "injects a floating bar" });
  }
  return r;
}

/* ------------------------------------------------------------------ */
/* Simulation                                                          */
/* ------------------------------------------------------------------ */

export type Timing = {
  id: string;
  discoveredAt: number;
  connectStart: number | null; // set when this request opened its origin's connection
  sendAt: number;
  firstByteAt: number;
  endAt: number;
};

export type Task = { id: string; label: string; start: number; end: number };

export type SimResult = {
  resources: Resource[];
  timings: Record<string, Timing>;
  tasks: Task[];
  deferred: Resource[]; // lazy: not loaded during page load
};

export function simulate(o: Opts): SimResult {
  const M = MODEL;
  const all = buildResources(o);
  const deferred = all.filter((x) => x.mode === "lazy");
  const res = all.filter((x) => x.mode !== "lazy");
  const byId = new Map(res.map((x) => [x.id, x]));
  const serverMs = o.pageCache ? M.serverMsCached : M.serverMsUncached - (o.removePlugins ? M.serverMsPerUnusedPluginSet : 0);

  const t: Record<string, Partial<Timing> & { left?: number }> = {};
  const originReady = new Map<string, number>();
  const execEnd = new Map<string, number>();
  const tasks: Task[] = [];
  let mainFreeAt = 0;
  let parseDone: number | null = null;

  const scripts = res.filter((x) => x.type === "js");
  const syncScripts = scripts.filter((x) => x.mode === "sync");
  const deferScripts = scripts.filter((x) => x.mode === "defer");
  const blockingCss = res.filter((x) => x.type === "css" && x.mode === "blocking");
  const doc = byId.get("doc")!;

  const discovered = (r: Resource, now: number): boolean => {
    const d = r.discover;
    if (d.by === "nav") return true;
    if (d.by === "css") return (t[d.id]?.endAt ?? Infinity) <= now;
    if (d.by === "exec") return (execEnd.get(d.id) ?? Infinity) <= now;
    const h = t.doc;
    if (h?.firstByteAt === undefined || h.firstByteAt > now) return false;
    const received = 1 - (h.left ?? doc.kb) / doc.kb;
    return received >= d.pos;
  };

  for (let now = 0; now < 30_000; now += M.stepMs) {
    // 1. discovery + connection + request
    for (const r of res) {
      if (t[r.id]) continue;
      if (!discovered(r, now)) continue;
      let ready = originReady.get(r.origin);
      let connectStart: number | null = null;
      if (ready === undefined) {
        connectStart = now;
        ready = now + M.connectRtts * M.rttMs;
        originReady.set(r.origin, ready);
      }
      const sendAt = Math.max(now, ready);
      const server = r.id === "doc" ? serverMs : r.origin === "self" ? M.staticServerMs : M.thirdPartyServerMs;
      t[r.id] = { id: r.id, discoveredAt: now, connectStart, sendAt, firstByteAt: sendAt + M.rttMs + server, left: r.kb };
    }

    // 2. bandwidth sharing between everything currently downloading
    const active = res.filter((r) => {
      const x = t[r.id];
      return x && x.firstByteAt! <= now && x.endAt === undefined;
    });
    const weight = active.reduce((s, r) => s + priority(r), 0);
    for (const r of active) {
      const x = t[r.id]!;
      x.left! -= ((M.bandwidthKBps / 1000) * M.stepMs * priority(r)) / weight;
      if (x.left! <= 0) x.endAt = now + M.stepMs;
    }

    // 3. main thread: one task at a time
    if (now >= mainFreeAt) {
      const done = (id: string) => (t[id]?.endAt ?? Infinity) <= now;
      const run = (id: string, label: string, cpu: number) => {
        const start = now;
        const end = now + cpu * M.cpuSlowdown;
        tasks.push({ id, label, start, end });
        mainFreeAt = end;
        return end;
      };
      // Sync scripts run in document order, each after earlier blocking CSS has loaded (CSS blocks script execution).
      const nextSync = syncScripts.find((s) => !execEnd.has(s.id));
      const cssBefore = (s: Resource) => blockingCss.filter((c) => c.discover.by === "html" && s.discover.by === "html" && c.discover.pos < s.discover.pos);
      const asyncReady = scripts.find((s) => s.mode === "async" && !execEnd.has(s.id) && done(s.id));
      // Footer scripts (after the content) only run once the parser reaches them.
      const isHead = (x: Resource) => x.discover.by === "html" && x.discover.pos < 0.5;
      if (nextSync && done(nextSync.id) && (isHead(nextSync) || parseDone !== null) && cssBefore(nextSync).every((c) => done(c.id))) {
        execEnd.set(nextSync.id, run(nextSync.id, `Evaluate ${short(nextSync.url)}`, nextSync.cpuMs ?? 10));
      } else if (parseDone === null && done("doc") && syncScripts.filter(isHead).every((x) => execEnd.has(x.id))) {
        parseDone = run("parse", "Parse HTML", doc.cpuMs ?? 20);
      } else if (asyncReady) {
        execEnd.set(asyncReady.id, run(asyncReady.id, `Evaluate ${short(asyncReady.url)}`, asyncReady.cpuMs ?? 10));
      } else if (parseDone !== null && parseDone <= now && syncScripts.every((s) => execEnd.has(s.id))) {
        const nextDefer = deferScripts.find((s) => !execEnd.has(s.id));
        if (nextDefer && done(nextDefer.id)) execEnd.set(nextDefer.id, run(nextDefer.id, `Evaluate ${short(nextDefer.url)}`, nextDefer.cpuMs ?? 10));
      }
    }

    const allLoaded = res.every((r) => t[r.id]?.endAt !== undefined);
    const allRun = scripts.every((s) => execEnd.has(s.id)) && parseDone !== null && now >= mainFreeAt;
    if (allLoaded && allRun) break;
  }

  const timings: Record<string, Timing> = {};
  for (const r of res) {
    const x = t[r.id];
    if (x?.endAt !== undefined) timings[r.id] = x as Timing;
  }
  return { resources: res, timings, tasks, deferred };
}

/** Request priority → bandwidth weight. fetchpriority=high / preload lifts an image to the top. */
export function priority(r: Resource): number {
  const w = MODEL.weights;
  if (r.type === "html") return w.document;
  if (r.high) return w.highest;
  if (r.type === "css") return r.mode === "blocking" ? w.highest : w.low;
  if (r.type === "font") return w.high;
  if (r.type === "js") return r.mode === "sync" ? w.high : r.mode === "defer" ? w.medium : w.low;
  return w.low;
}

function short(url: string) {
  const parts = url.split("/");
  const last = parts.pop() ?? url;
  return last.length < 4 ? `${parts.pop()}/${last}` : last;
}

/* ------------------------------------------------------------------ */
/* Metrics                                                             */
/* ------------------------------------------------------------------ */

export type Metrics = {
  ttfb: number;
  fcp: number;
  lcp: number;
  tbt: number;
  cls: number;
  load: number;
  requests: number;
  kb: number;
  clsParts: { label: string; value: number }[];
};

export function metrics(o: Opts, sim: SimResult): Metrics {
  const M = MODEL;
  const end = (id: string) => sim.timings[id]?.endAt ?? 0;
  const task = (id: string) => sim.tasks.find((x) => x.id === id);

  // First paint needs: HTML parsed up to the body, blocking CSS loaded, head scripts run.
  let fcp = Math.max(
    task("parse")?.end ?? 0,
    ...sim.resources.filter((r) => r.type === "css" && r.mode === "blocking").map((r) => end(r.id)),
    ...sim.resources.filter((r) => r.mode === "sync" && r.discover.by === "html" && r.discover.pos < 0.5).map((r) => task(r.id)?.end ?? 0),
  );
  // Web fonts without font-display: swap hide text until they load, for up to 3 s.
  if (!o.fonts) {
    const fonts = sim.resources.filter((r) => r.type === "font");
    const firstFontStart = Math.min(...fonts.map((f) => sim.timings[f.id]!.discoveredAt));
    fcp = Math.max(fcp, Math.min(Math.max(...fonts.map((f) => end(f.id))), firstFontStart + M.fontBlockMs));
  }
  fcp += M.renderMs;

  const hero = sim.resources.find((r) => r.lcp)!;
  const heroExec = hero.discover.by === "exec" ? (task(hero.discover.id)?.end ?? 0) : 0;
  const lcp = Math.max(fcp, end(hero.id) + M.renderMs, heroExec + M.renderMs);

  // Total Blocking Time: the part of each main-thread task over 50 ms, counted after first paint.
  let tbt = 0;
  for (const tk of sim.tasks) {
    const start = Math.max(tk.start, fcp);
    if (tk.end <= fcp) continue;
    const blocking = tk.end - tk.start - M.longTaskMs;
    if (blocking > 0) tbt += Math.min(blocking, tk.end - start);
  }

  // Layout shift estimates: fixed values per cause, chosen by me (see the case study).
  const clsParts: { label: string; value: number }[] = [];
  if (!o.replaceSlider) clsParts.push({ label: "Slider injects the hero after its script runs", value: 0.15 });
  else if (!o.dimensions) clsParts.push({ label: "Hero image without width/height", value: 0.09 });
  if (!o.dimensions) clsParts.push({ label: "Logo without width/height", value: 0.02 });
  if (!o.removePlugins) clsParts.push({ label: "Share bar injected by JavaScript", value: 0.06 });
  clsParts.push(o.fonts ? { label: "Font swap with size-adjusted fallback", value: 0.005 } : { label: "Text reflows when web fonts arrive", value: 0.02 });
  const cls = clsParts.reduce((s, p) => s + p.value, 0);

  const load = Math.max(...Object.values(sim.timings).map((x) => x.endAt), ...sim.tasks.map((x) => x.end));
  return {
    ttfb: sim.timings.doc!.firstByteAt,
    fcp: Math.round(fcp),
    lcp: Math.round(lcp),
    tbt: Math.round(tbt),
    cls: Math.round(cls * 1000) / 1000,
    load: Math.round(load),
    requests: sim.resources.length,
    kb: Math.round(sim.resources.reduce((s, r) => s + r.kb, 0)),
    clsParts,
  };
}

/** Core Web Vitals thresholds (good / needs improvement / poor). TBT uses Lighthouse's lab thresholds. */
export function rating(metric: "lcp" | "cls" | "tbt" | "fcp", v: number): "good" | "ni" | "poor" {
  const th = { lcp: [2500, 4000], fcp: [1800, 3000], tbt: [200, 600], cls: [0.1, 0.25] }[metric];
  return v <= th[0]! ? "good" : v <= th[1]! ? "ni" : "poor";
}

export const run = (o: Opts) => {
  const sim = simulate(o);
  return { sim, m: metrics(o, sim) };
};
app/lab/wordpress-speed-optimization/php/acme-speed.php149 lines
<?php
/**
 * Plugin Name: Acme Speed (must-use)
 * Description: The speed fixes from the case study, in code rather than hidden in a plugin's settings screen.
 *
 * Production design for the speed demo. Lives in wp-content/mu-plugins/ so it cannot be switched off by accident.
 * Page caching itself is done at the server (Nginx FastCGI cache or the host's cache), not here.
 */

defined( 'ABSPATH' ) || exit;

/* ---------- Remove unused plugin assets per page ---------- */

add_action( 'wp_enqueue_scripts', 'acme_speed_dequeue', 100 );

function acme_speed_dequeue(): void {
	// Contact Form 7 only where a form is actually on the page.
	if ( ! is_singular() || ! has_shortcode( (string) get_post_field( 'post_content' ), 'contact-form-7' ) ) {
		wp_dequeue_style( 'contact-form-7' );
		wp_dequeue_script( 'contact-form-7' );
	}
	// Share buttons only on blog posts.
	if ( ! is_singular( 'post' ) ) {
		wp_dequeue_script( 'social-share' );
		wp_dequeue_style( 'social-share' );
	}
	// jQuery Migrate is only needed by very old plugin code.
	if ( ! is_admin() && isset( wp_scripts()->registered['jquery'] ) ) {
		wp_scripts()->registered['jquery']->deps = array_diff( wp_scripts()->registered['jquery']->deps, array( 'jquery-migrate' ) );
	}
}

/* ---------- Defer scripts ---------- */

add_action( 'wp_enqueue_scripts', 'acme_speed_defer', 200 );

function acme_speed_defer(): void {
	// WordPress 6.3+ understands loading strategies and keeps dependency order intact.
	foreach ( array( 'jquery-core', 'page-builder-frontend' ) as $handle ) {
		if ( wp_script_is( $handle, 'enqueued' ) || wp_script_is( $handle, 'registered' ) ) {
			wp_script_add_data( $handle, 'strategy', 'defer' );
		}
	}
}

/* ---------- Analytics after the first interaction ---------- */

add_action( 'wp_footer', 'acme_speed_delayed_analytics', 99 );

function acme_speed_delayed_analytics(): void {
	$id = 'G-XXXXXXX';
	?>
	<script>
	(() => {
		const load = () => {
			['scroll', 'pointerdown', 'keydown'].forEach((e) => removeEventListener(e, load));
			const s = document.createElement('script');
			s.src = 'https://www.googletagmanager.com/gtag/js?id=<?php echo esc_js( $id ); ?>';
			s.async = true;
			document.head.append(s);
		};
		['scroll', 'pointerdown', 'keydown'].forEach((e) => addEventListener(e, load, { once: true, passive: true }));
	})();
	</script>
	<?php
}

/* ---------- Hero image: preload at high priority, never lazy ---------- */

add_filter( 'wp_preload_resources', 'acme_speed_preload' );

function acme_speed_preload( array $resources ): array {
	$hero = (int) get_theme_mod( 'acme_hero_image' );
	if ( is_front_page() && $hero ) {
		$resources[] = array(
			'href'          => wp_get_attachment_image_url( $hero, 'full' ),
			'as'            => 'image',
			'imagesrcset'   => wp_get_attachment_image_srcset( $hero, 'full' ),
			'imagesizes'    => '100vw',
			'fetchpriority' => 'high',
		);
	}
	$resources[] = array( 'href' => get_theme_file_uri( 'fonts/inter-400.woff2' ), 'as' => 'font', 'type' => 'font/woff2', 'crossorigin' => 'anonymous' );
	$resources[] = array( 'href' => get_theme_file_uri( 'fonts/inter-700.woff2' ), 'as' => 'font', 'type' => 'font/woff2', 'crossorigin' => 'anonymous' );
	return $resources;
}

// The theme prints the hero with wp_get_attachment_image(), which adds width, height, srcset and sizes.
// Core lazy-loads images by default; the first content image is skipped, and this makes sure the hero is too.
add_filter(
	'wp_get_attachment_image_attributes',
	static function ( array $attr, WP_Post $attachment ): array {
		if ( (int) get_theme_mod( 'acme_hero_image' ) === $attachment->ID ) {
			$attr['loading']       = 'eager';
			$attr['fetchpriority'] = 'high';
			$attr['decoding']      = 'async';
		}
		return $attr;
	},
	10,
	2
);

/* ---------- Modern image formats ---------- */

// WordPress 6.1+ can write WebP sub-sizes on upload; AVIF needs 6.5+ and server support.
add_filter(
	'image_editor_output_format',
	static fn( array $formats ): array => $formats + array( 'image/jpeg' => 'image/avif', 'image/png' => 'image/webp' )
);

/* ---------- Critical CSS inline, the rest async ---------- */

add_action( 'wp_head', 'acme_speed_critical_css', 1 );

function acme_speed_critical_css(): void {
	$file = get_theme_file_path( 'assets/critical-' . ( is_front_page() ? 'home' : 'page' ) . '.css' ); // generated at build time
	if ( is_readable( $file ) ) {
		echo '<style id="acme-critical">' . wp_strip_all_tags( (string) file_get_contents( $file ) ) . '</style>'; // phpcs:ignore WordPress.WP.AlternativeFunctions
	}
}

add_filter( 'style_loader_tag', 'acme_speed_async_css', 10, 3 );

function acme_speed_async_css( string $tag, string $handle, string $href ): string {
	if ( is_admin() || ! in_array( $handle, array( 'flavor-style', 'page-builder-frontend' ), true ) ) {
		return $tag;
	}
	// media="print" downloads at low priority without blocking render; onload switches it on.
	return sprintf(
		'<link rel="stylesheet" id="%1$s-css" href="%2$s" media="print" onload="this.media=\'all\'"><noscript>%3$s</noscript>' . "\n",
		esc_attr( $handle ),
		esc_url( $href ),
		$tag
	);
}

/* ---------- Fonts: self-hosted, two weights, swap ---------- */

add_action( 'wp_enqueue_scripts', static fn() => wp_dequeue_style( 'flavor-google-fonts' ), 100 );
// theme.css then declares:
//   @font-face { font-family: Inter; src: url(fonts/inter-400.woff2) format("woff2"); font-weight: 400; font-display: swap; }
//   @font-face { font-family: Inter-fallback; src: local(Arial); size-adjust: 107%; ascent-override: 90%; }

/* ---------- Smaller things ---------- */

remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
app/lab/wordpress-speed-optimization/Demo.tsx289 lines
"use client";

import { useMemo, useState } from "react";
import { usePersistentState } from "@/components/lab/wp/usePersistentState";
import { ALL, MODEL, NONE, OPTIMISATIONS, priority, rating, run, type Opts, type Resource } from "./model";

const KEY = "lab:wordpress-speed-optimization:v1";

const TYPE_COLOR: Record<Resource["type"], string> = {
  html: "#60a5fa",
  css: "#c084fc",
  js: "#fbbf24",
  font: "#f472b6",
  img: "#4ade80",
};

const RATING_CLASS = { good: "text-accent", ni: "text-warn-fg", poor: "text-danger" } as const;
const RATING_LABEL = { good: "good", ni: "needs improvement", poor: "poor" } as const;

const fmtMs = (ms: number) => (ms >= 1000 ? `${(ms / 1000).toFixed(1)} s` : `${Math.round(ms)} ms`);

export default function Demo() {
  const [opts, setOpts, reset] = usePersistentState<Opts>(KEY, () => ({ ...NONE }));
  const [selected, setSelected] = useState<string | null>("hero");
  const [showModel, setShowModel] = useState(false);

  const current = useMemo(() => run({ ...NONE, ...opts }), [opts]);
  const baseline = useMemo(() => run(NONE), []);
  const { sim, m } = current;
  const enabled = OPTIMISATIONS.filter((o) => opts[o.id]).length;

  const scale = Math.max(m.load, 1000);
  const pct = (ms: number) => `${(ms / scale) * 100}%`;
  const ticks = Array.from({ length: Math.floor(scale / 1000) + 1 }, (_, i) => i * 1000).filter((t) => t <= scale);
  const tickEvery = scale > 12000 ? 4 : scale > 6000 ? 2 : 1;

  const sel = sim.resources.find((r) => r.id === selected);
  const selT = sel ? sim.timings[sel.id] : undefined;

  return (
    <div className="space-y-5 p-3 sm:p-5">
      <p className="rounded-md border border-warn-fg/40 bg-warn-bg px-3 py-2 text-sm text-warn-fg">
        Modelled, not measured. These numbers come from the simulation in <code>model.ts</code> (open “How the model works” below or View source). They show
        how each fix changes the shape of the page load, not what a real site would score.
      </p>

      {/* Metrics */}
      <section aria-label="Estimated metrics" aria-live="polite" className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6">
        <Metric label="LCP" value={fmtMs(m.lcp)} before={fmtMs(baseline.m.lcp)} r={rating("lcp", m.lcp)} />
        <Metric label="FCP" value={fmtMs(m.fcp)} before={fmtMs(baseline.m.fcp)} r={rating("fcp", m.fcp)} />
        <Metric label="TBT" value={fmtMs(m.tbt)} before={fmtMs(baseline.m.tbt)} r={rating("tbt", m.tbt)} />
        <Metric label="CLS" value={m.cls.toFixed(3)} before={baseline.m.cls.toFixed(3)} r={rating("cls", m.cls)} />
        <Metric label="Requests" value={String(m.requests)} before={String(baseline.m.requests)} />
        <Metric label="Transferred" value={`${(m.kb / 1024).toFixed(2)} MB`} before={`${(baseline.m.kb / 1024).toFixed(2)} MB`} />
      </section>

      <div className="grid gap-5 lg:grid-cols-[19rem_1fr]">
        {/* Toggles */}
        <fieldset className="space-y-1.5">
          <legend className="mb-2 flex w-full flex-wrap items-center gap-2">
            <span className="font-mono text-sm font-semibold">optimisations ({enabled}/{OPTIMISATIONS.length})</span>
          </legend>
          <div className="mb-2 flex flex-wrap gap-1.5">
            <button type="button" onClick={() => setOpts({ ...NONE })} className="rounded border border-border px-2 py-1 font-mono text-xs hover:border-accent">
              before
            </button>
            <button type="button" onClick={() => setOpts({ ...ALL })} className="rounded border border-border px-2 py-1 font-mono text-xs hover:border-accent">
              after (all on)
            </button>
            <button type="button" onClick={reset} className="rounded border border-border px-2 py-1 font-mono text-xs hover:border-accent">
              reset demo
            </button>
          </div>
          {OPTIMISATIONS.map((o) => {
            const on = opts[o.id];
            return (
              <label key={o.id} className={`flex cursor-pointer gap-2 rounded-md border p-2 text-sm has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-accent ${on ? "border-accent bg-accent-soft/50" : "border-border"}`}>
                <input type="checkbox" checked={opts[o.id]} onChange={(e) => setOpts({ ...opts, [o.id]: e.target.checked })} className="mt-0.5" aria-describedby={`opt-${o.id}`} />
                <span>
                  <span className="font-medium">{o.label}</span>
                  <span id={`opt-${o.id}`} className="block text-xs text-fg-muted">
                    {o.how}
                  </span>
                </span>
              </label>
            );
          })}
        </fieldset>

        {/* Waterfall */}
        <section aria-label="Request waterfall" className="min-w-0">
          <div className="mb-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-fg-muted">
            <span className="font-mono text-sm font-semibold text-fg">waterfall</span>
            {(Object.keys(TYPE_COLOR) as Resource["type"][]).map((t) => (
              <span key={t} className="flex items-center gap-1">
                <span className="inline-block size-2.5 rounded-sm" style={{ background: TYPE_COLOR[t] }} />
                {t}
              </span>
            ))}
            <span className="flex items-center gap-1">
              <span className="inline-block h-2.5 w-3 rounded-sm bg-fg-muted/40" /> connect / wait
            </span>
            <span className="flex items-center gap-1">
              <span className="inline-block h-3 w-0.5 bg-accent-2" /> FCP
            </span>
            <span className="flex items-center gap-1">
              <span className="inline-block h-3 w-0.5 bg-accent-3" /> LCP
            </span>
          </div>

          <div className="overflow-x-auto rounded-md border border-border">
            <div className="min-w-[36rem]">
              {/* time axis */}
              <div className="grid grid-cols-[11rem_1fr] border-b border-border bg-bg-muted text-[10px] text-fg-muted">
                <div className="px-2 py-1 font-mono">request</div>
                <div className="relative h-5">
                  {ticks
                    .filter((_, i) => i % tickEvery === 0)
                    .map((t) => (
                      <span key={t} className="absolute top-1 -translate-x-1/2 font-mono" style={{ left: pct(t) }}>
                        {t / 1000}s
                      </span>
                    ))}
                </div>
              </div>
              <ol className="relative">
                {/* markers */}
                <li aria-hidden="true" className="pointer-events-none absolute inset-y-0 right-0 left-[11rem]">
                  <span className="absolute inset-y-0 w-px bg-accent-2" style={{ left: pct(m.fcp) }} />
                  <span className="absolute inset-y-0 w-px bg-accent-3" style={{ left: pct(m.lcp) }} />
                </li>
                {sim.resources.map((r) => {
                  const t = sim.timings[r.id]!;
                  const wait = t.connectStart ?? t.sendAt;
                  return (
                    <li key={r.id}>
                      <button
                        type="button"
                        onClick={() => setSelected(r.id)}
                        aria-pressed={selected === r.id}
                        aria-label={`${r.url}, ${r.kb} KB, starts ${fmtMs(t.discoveredAt)}, finishes ${fmtMs(t.endAt)}`}
                        className={`grid w-full grid-cols-[11rem_1fr] items-center text-left text-[11px] hover:bg-bg-muted focus-visible:bg-bg-muted ${selected === r.id ? "bg-accent-soft/40" : ""}`}
                      >
                        <span className="truncate px-2 py-0.5 font-mono" title={r.url}>
                          {r.lcp && <span className="text-accent-3">★ </span>}
                          {r.url.replace(/^\/wp-content\//, "…/")}
                        </span>
                        <span className="relative h-4">
                          <span className="absolute top-1/2 h-1.5 -translate-y-1/2 rounded-l-sm bg-fg-muted/30" style={{ left: pct(wait), width: pct(t.firstByteAt - wait) }} />
                          <span
                            className="absolute top-1/2 h-2.5 -translate-y-1/2 rounded-sm"
                            style={{ left: pct(t.firstByteAt), width: `max(2px, ${pct(t.endAt - t.firstByteAt)})`, background: TYPE_COLOR[r.type], opacity: r.mode === "blocking" || r.mode === "sync" ? 1 : 0.7 }}
                          />
                          {(r.mode === "blocking" || r.mode === "sync") && (
                            <span className="absolute top-1/2 -translate-y-1/2 pl-1 text-[9px] text-danger" style={{ left: pct(t.endAt) }}>
                              blocking
                            </span>
                          )}
                        </span>
                      </button>
                    </li>
                  );
                })}
                {/* main thread */}
                <li className="grid grid-cols-[11rem_1fr] items-center border-t border-border text-[11px]">
                  <span className="px-2 py-1 font-mono text-fg-muted">main thread</span>
                  <span className="relative h-5">
                    {sim.tasks.map((tk) => {
                      const long = tk.end - tk.start > MODEL.longTaskMs;
                      return (
                        <span
                          key={tk.id}
                          title={`${tk.label}: ${Math.round(tk.end - tk.start)} ms`}
                          className="absolute top-1/2 h-3 -translate-y-1/2 overflow-hidden rounded-sm bg-[#a1a1aa]"
                          style={{ left: pct(tk.start), width: `max(2px, ${pct(tk.end - tk.start)})` }}
                        >
                          {long && <span className="absolute inset-y-0 right-0 bg-danger" style={{ left: `${(MODEL.longTaskMs / (tk.end - tk.start)) * 100}%` }} />}
                        </span>
                      );
                    })}
                  </span>
                </li>
              </ol>
            </div>
          </div>
          <p className="mt-1 text-[11px] text-fg-muted">
            Grey = connection setup and waiting for the first byte. Red on the main thread = the part of a task over 50 ms (counts towards TBT after first paint).
            {sim.deferred.length > 0 && ` Not in the load: ${sim.deferred.length} request${sim.deferred.length === 1 ? "" : "s"} deferred until scroll or interaction.`}
          </p>

          {/* details */}
          {sel && selT && (
            <dl className="mt-3 grid grid-cols-2 gap-x-4 gap-y-1 rounded-md border border-border bg-bg-elev p-3 font-mono text-xs sm:grid-cols-4" aria-live="polite">
              <div className="col-span-2 sm:col-span-4 break-all text-fg">{sel.url}</div>
              <Detail k="type / mode" v={`${sel.type} / ${sel.mode}`} />
              <Detail k="size" v={`${sel.kb} KB`} />
              <Detail k="priority weight" v={String(priority(sel))} />
              <Detail k="found by" v={sel.discover.by === "html" ? `HTML (${Math.round(sel.discover.pos * 100)}% in)` : sel.discover.by === "nav" ? "navigation" : `${sel.discover.by}: ${sel.discover.id}`} />
              <Detail k="discovered" v={fmtMs(selT.discoveredAt)} />
              <Detail k="first byte" v={fmtMs(selT.firstByteAt)} />
              <Detail k="finished" v={fmtMs(selT.endAt)} />
              <Detail k="note" v={sel.note ?? (selT.connectStart !== null ? "opened a new connection" : "—")} />
            </dl>
          )}
        </section>
      </div>

      {/* CLS breakdown + model */}
      <div className="grid gap-5 md:grid-cols-2">
        <section aria-label="Layout shift breakdown">
          <h3 className="font-mono text-sm font-semibold">CLS breakdown (fixed estimates)</h3>
          <ul className="mt-2 space-y-1 text-sm">
            {m.clsParts.map((p) => (
              <li key={p.label} className="flex justify-between gap-3 border-b border-border py-1">
                <span>{p.label}</span>
                <span className="font-mono">{p.value.toFixed(3)}</span>
              </li>
            ))}
          </ul>
        </section>
        <section aria-label="Model">
          <button type="button" onClick={() => setShowModel((v) => !v)} aria-expanded={showModel} className="font-mono text-sm font-semibold hover:text-accent">
            {showModel ? "▾" : "▸"} How the model works
          </button>
          {showModel && <ModelExplainer />}
        </section>
      </div>
    </div>
  );
}

function Metric({ label, value, before, r }: { label: string; value: string; before: string; r?: "good" | "ni" | "poor" }) {
  return (
    <div className="rounded-md border border-border bg-bg-elev p-2.5">
      <p className="font-mono text-[11px] text-fg-muted">{label}</p>
      <p className={`text-xl font-semibold ${r ? RATING_CLASS[r] : ""}`}>{value}</p>
      <p className="text-[11px] text-fg-muted">
        {r && <span className="sr-only">Rating: {RATING_LABEL[r]}. </span>}before: {before}
      </p>
    </div>
  );
}

function Detail({ k, v }: { k: string; v: string }) {
  return (
    <div>
      <dt className="text-fg-muted">{k}</dt>
      <dd>{v}</dd>
    </div>
  );
}

function ModelExplainer() {
  const rows: [string, string][] = [
    ["Round trip (RTT)", `${MODEL.rttMs} ms`],
    ["Bandwidth", `${MODEL.bandwidthKBps} KB/s (≈1.6 Mbps)`],
    ["CPU slowdown", `${MODEL.cpuSlowdown}×`],
    ["New origin", `${MODEL.connectRtts} RTT (DNS + TCP + TLS)`],
    ["Server time, uncached", `${MODEL.serverMsUncached} ms`],
    ["Server time, page cache", `${MODEL.serverMsCached} ms`],
    ["Font block period", `${MODEL.fontBlockMs} ms`],
    ["Long task threshold", `${MODEL.longTaskMs} ms`],
    ["Bandwidth weights", `document ${MODEL.weights.document}, blocking CSS / preloaded ${MODEL.weights.highest}, fonts + sync JS ${MODEL.weights.high}, deferred JS ${MODEL.weights.medium}, images + async ${MODEL.weights.low}`],
  ];
  return (
    <div className="mt-2 space-y-2 text-sm">
      <table className="w-full text-xs">
        <tbody>
          {rows.map(([k, v]) => (
            <tr key={k} className="border-b border-border">
              <th scope="row" className="py-1 pr-2 text-left font-normal text-fg-muted">
                {k}
              </th>
              <td className="py-1 font-mono">{v}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <ul className="list-inside list-disc space-y-1 text-xs text-fg-muted">
        <li>Time advances in {MODEL.stepMs} ms steps. Each step discovers requests, shares bandwidth by weight, and runs at most one main-thread task.</li>
        <li>FCP = HTML parsed + render-blocking CSS loaded + head scripts run (+ up to 3 s of hidden text for blocking web fonts) + 20 ms.</li>
        <li>LCP = when the hero image has loaded (or been injected by the slider), never before FCP.</li>
        <li>TBT = the time over 50 ms of every task after FCP. Deferring scripts can raise TBT, because work moves after the first paint.</li>
        <li>CLS uses fixed numbers per cause, listed on the left. A real page needs a real measurement.</li>
      </ul>
    </div>
  );
}