Next.js 15 Performance Optimization: Complete Guide

Oct 25, 2025
nextjsreactperformancecore-web-vitals
0

Great UX is fast, consistent, and resilient. This guide distills production‑ready patterns to make Next.js 15 apps feel instant: smarter rendering, aggressive caching, careful data fetching, and eliminating waste in the bundle.

Core Web Vitals at a glance

  • LCP: Time until the largest content appears. Optimize hero rendering, compress images, use priority on critical media.
  • INP: Input latency. Reduce main‑thread work, avoid heavy client JS, prefetch interactions.
  • CLS: Layout stability. Reserve space for media/fonts, avoid dynamic height shifts.

Rendering strategy: stream by default

Next.js App Router enables server components, partial revalidation, and streaming. Prefer server work, stream early HTML, and hydrate minimally.

Checklist

  • Keep components server‑first; mark client components explicitly with "use client".
  • Stream route segments; keep slow data behind Suspense boundaries.
  • Use dynamic = "force-static" | "force-dynamic" intentionally per route.
  • Co‑locate data fetching in server components; avoid fetching in the client.
// app/(shop)/product/[id]/page.tsx (server component)
import Image from "next/image";
import { notFound } from "next/navigation";

export const revalidate = 60; // ISR, fast re‑use

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id); // server‑side fetch
  if (!product) return notFound();

  return (
    <main className="container">
      <h1 className="text-3xl font-semibold">{product.name}</h1>
      <div className="mt-4 grid md:grid-cols-2 gap-6">
        <Image src={product.imageUrl} alt={product.name} width={800} height={600} priority />
        {/* Specs and description ... */}
      </div>
    </main>
  );
}

Data caching: revalidate, tag, and mutate precisely

Use route segment options and cache tags to control freshness without full rebuilds.

// lib/products.ts
import "server-only";
import { unstable_cache } from "next/cache";

export const getProduct = unstable_cache(
  async (id: string) => {
    const res = await fetch(process.env.API_URL + "/products/" + id, { cache: "no-store" });
    if (!res.ok) throw new Error("failed");
    return res.json() as Promise<{ id: string; name: string; imageUrl: string }>;
  },
  ["product-by-id"],
  { revalidate: 60, tags: ["products"] }
);

export async function revalidateProducts() {
  // In route handler after a write:
  // revalidateTag("products");
}

Patterns

  • Read‑heavy pages: revalidate + cache tags for cheap invalidation.
  • User‑specific pages: cache: "no-store" and edge runtime when possible.
  • API routes mutating data should call revalidateTag to keep UI fresh.

Streaming and Suspense boundaries

Stream the shell fast; load slow widgets later.

// app/dashboard/page.tsx
import { Suspense } from "react";
import KPIs from "./_components/KPIs";
import Activity from "./_components/Activity";

export default function Page() {
  return (
    <main>
      <h1 className="text-2xl">Dashboard</h1>
      <Suspense fallback={<div className="skeleton h-24" />}>
        {/* Large query, stream when ready */}
        <KPIs />
      </Suspense>
      <Suspense fallback={<div className="skeleton h-48" />}>
        <Activity />
      </Suspense>
    </main>
  );
}

Images and fonts: zero layout shift

  • Use next/image with explicit width/height and sizes.
  • Mark critical hero images with priority.
  • Self‑host fonts; use display: swap and size-adjust where helpful.
// app/layout.tsx
import { Roboto } from "next/font/google";
const roboto = Roboto({ subsets: ["latin"], display: "swap", weight: ["400", "500", "700"] });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={roboto.className}>
      <body>{children}</body>
    </html>
  );
}

Bundle control: ship less JS

Measure first, then cut.

Tactics

  • Prefer server components; mark clients surgically.
  • Dynamic import rarely used widgets with { ssr: false } only if safe.
  • Remove unused polyfills; target modern browsers.
  • Tree‑shake UI libraries; import per module.
  • Replace heavy libs (e.g., moment.js) with lighter options (date‑fns/dayjs).
// Example: client component boundary kept minimal
"use client";
import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("@/components/HeavyChart"), { ssr: false, loading: () => <div className="h-40 skeleton" /> });

export default function AnalyticsClient() {
  return <HeavyChart />;
}

Network: prefetch, cache, compress

  • Enable HTTP/2/3, Gzip/Brotli.
  • Use prefetch on critical internal links (Next does it automatically on viewport).
  • Add long‑lived immutable caching for static assets (configured in your headers).

Edge vs Node runtime

If you need low‑latency reads and lightweight logic, run at the edge. For heavy Node APIs (DB drivers, sharp), keep Node runtime.

// app/api/hello/route.ts
export const runtime = "edge"; // or "nodejs"
export async function GET() {
  return new Response(JSON.stringify({ ok: true }), { headers: { "content-type": "application/json" } });
}

Measuring performance

  • Lighthouse CI per PR for regressions.
  • next/script strategy and blocking script audits.
  • Real‑user monitoring for Core Web Vitals.
// app/web-vitals.tsx (already in your repo) – ensure it reports to analytics
// Example usage:
export function reportWebVitals(metric: any) {
  // send to your analytics endpoint
}

Database and API performance

  • Index query filters; paginate with cursors.
  • Cache by filter signature; invalidate surgically.
  • Stream large responses; prefer 206 for exports.

Production checklist

  • Server components by default; minimal client boundaries
  • Critical path streamed; Suspense for slow data
  • Correct caching: revalidate/tags tuned per route
  • Images with dimensions; fonts with display: swap
  • Bundle budget enforced; remove heavy deps
  • Edge runtime where beneficial
  • RUM for CWV; Lighthouse CI in pipeline

FAQ

Should I make everything static?
No. Mix ISR for read‑heavy pages, dynamic for user‑specific data, and cache tags for precise invalidation.

Do server components always reduce JS?
Yes—server components ship zero client JS. Only client boundaries hydrate.

How do I debug INP spikes?
Record performance profiles, find long tasks (>50ms), split work with useTransition, and remove heavy client libraries.

What about images coming from a CMS?
Use the Next Image optimizer or remote patterns in next.config.ts, and always set intrinsic sizes.

Ship small, stream early, cache smart. Your users—and Core Web Vitals—will thank you.

Advanced caching strategies (production)

Caching should be layered: browser, CDN/edge, application, and database. Use strong cache keys and precise invalidation to avoid stale content while maximizing hits.

1) CDN and HTTP headers

  • Cache-Control: Use public, max-age=31536000, immutable for versioned static assets; shorter for HTML (e.g., max-age=0, must-revalidate).
  • ETag: Prefer weak ETags for HTML that changes often; strong ETags for JSON with version fields.
  • Vary: Include Accept-Encoding, User-Agent only when necessary; keep the Vary set minimal.
// next.config.ts headers example (static + security)
export default {
  async headers() {
    return [
      {
        source: "/:all*(js|css|woff2|svg|jpg|jpeg|png|webp|avif|gif|ico)",
        headers: [
          { key: "Cache-Control", value: "public, max-age=31536000, immutable" },
        ],
      },
      {
        source: "/(.*)",
        headers: [
          { key: "Strict-Transport-Security", value: "max-age=15552000; includeSubDomains" },
        ],
      },
    ];
  },
};

2) Route Handlers with revalidateTag

When you mutate data, call revalidateTag to invalidate only the affected lists/pages.

// app/api/products/[id]/route.ts
import { revalidateTag } from "next/cache";

export async function PUT(req: Request, { params }: { params: { id: string } }) {
  const body = await req.json();
  await updateProduct(params.id, body);
  revalidateTag("products"); // precise, cheap invalidation
  return new Response(null, { status: 204 });
}

3) Cache signatures for lists

For filterable lists, create signatures from normalized query params. Use them as cache keys to maximize reuse.

function listSignature(q: URLSearchParams) {
  const keys = ["category", "sort", "cursor", "limit"]; // allowlist
  const norm = keys.map((k) => `${k}=${q.get(k) || ""}`).join("&");
  return `products?${norm}`;
}

Images: remote patterns and responsive sizes

Configure remote hosts and add accurate sizes for responsive images to reduce transfer costs.

// next.config.ts
export default {
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "images.example.com" },
      { protocol: "https", hostname: "cdn.example.net" },
    ],
    formats: ["image/avif", "image/webp"],
  },
};
<Image
  src={product.imageUrl}
  alt={product.name}
  width={1600}
  height={1200}
  priority
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
/>

Middleware: smart redirects and lightweight logic

Use middleware for zero‑JS AB tests, locale redirects, or bot handling. Keep it fast and stateless.

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(req: NextRequest) {
  // Example: redirect non‑www to www (or vice versa) early at edge
  const url = req.nextUrl.clone();
  if (url.hostname === "example.com") {
    url.hostname = "www.example.com";
    return NextResponse.redirect(url, 308);
  }
  return NextResponse.next();
}

Route Handlers: streaming & compression

Stream long responses progressively; set compression at the platform.

// app/api/stream/route.ts
export async function GET() {
  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      for (let i = 0; i < 5; i++) {
        controller.enqueue(encoder.encode(JSON.stringify({ part: i }) + "\n"));
        await new Promise((r) => setTimeout(r, 300));
      }
      controller.close();
    },
  });
  return new Response(stream, { headers: { "content-type": "application/json" } });
}

Analyze and enforce a bundle budget

Install a bundle analyzer and enforce budgets in CI to prevent regressions.

npm i -D @next/bundle-analyzer
// next.config.ts
import withBundleAnalyzer from "@next/bundle-analyzer";
export default withBundleAnalyzer({ enabled: process.env.ANALYZE === "true" })({
  // your config
});
ANALYZE=true next build

Define budgets (soft/hard limits) per page and fail CI on regressions.

Real‑user monitoring (RUM) for Vitals

Collect field data to spot regressions that lab tests miss.

// app/web-vitals.tsx
export function reportWebVitals(metric: any) {
  fetch("/api/vitals", {
    method: "POST",
    keepalive: true,
    headers: { "content-type": "application/json" },
    body: JSON.stringify(metric),
  });
}
// app/api/vitals/route.ts
export async function POST(req: Request) {
  const metric = await req.json();
  // store in your analytics backend (BigQuery, ClickHouse, etc.)
  return new Response(null, { status: 204 });
}

Case study: 3.1x faster LCP

Baseline: LCP 3.2s P75 on product pages.

Actions:

  1. Server component for product details; removed client fetch and hydration.
  2. Added revalidate=60 and cache tags; wrote revalidateTag("products") after updates.
  3. Converted hero image to AVIF with intrinsic sizes; set priority and accurate sizes.
  4. Split a heavy client chart via dynamic import with ssr: false and a skeleton.
  5. Enabled Brotli and reduced third‑party scripts by 2.

Result: LCP improved to 1.03s P75, CLS < 0.01, INP P75 140ms.

Troubleshooting guide

  • High CLS: Missing intrinsic media sizes; late‑loading fonts without display: swap.
  • High INP: Long tasks from large client components; add boundaries or reduce JS.
  • High LCP: Hero image not optimized; no priority; slow TTFB—move to edge, cache better.
  • Slow TTFB: Database N+1; add indexes; cache hot queries; stream shell earlier.

Additional checklists

Images

  • AVIF/WebP where supported
  • Intrinsic width/height set
  • Accurate sizes attribute
  • priority on hero media

JS budget

  • Remove unused polyfills
  • Replace heavy libs (moment → dayjs)
  • Dynamic import rarely used widgets
  • Minimize global client state

Data & caching

  • revalidate configured per route
  • Cache tags and precise invalidation
  • No client fetch for server‑only data
  • Edge runtime for low‑latency reads

Appendix A — Core Web Vitals Deep Dive

LCP (Largest Contentful Paint)

  • Optimize TTFB: edge runtime, caching, streaming headers early.
  • Preload hero image and font files with proper priorities.
  • Avoid client rendering for above‑the‑fold content.

INP (Interaction to Next Paint)

  • Eliminate long tasks (>50ms): split with useTransition, requestIdleCallback.
  • Minimize hydration cost; reduce client component surface.
  • Prefer CSS transitions over JS where possible.

CLS (Cumulative Layout Shift)

  • Always reserve space for images/video/ads.
  • Use font size-adjust and display: swap.
  • Avoid content-visibility: auto on hero elements.

Appendix B — Caching Recipes by Page Type

  • Marketing landing (rare updates): revalidate = 3600, CDN cache HTML 5–10m.
  • Product listing (frequent writes): revalidate = 60 + revalidateTag("products") on writes.
  • User dashboard (personalized): cache: "no-store", edge runtime, stream.
  • Blog posts: static generation, long‑lived asset cache; ISR for comments count badge.

Appendix C — Image Optimization Playbook

  • Use sizes tailored to breakpoints; audit via Lighthouse.
  • Deduplicate responsive sources; avoid oversized transfers.
  • Prefer AVIF; fall back to WebP.
  • Serve placeholders (blur) for perceived performance on slow connections.

Appendix D — Fonts Strategy

  • Self‑host; subset to used glyph ranges.
  • Use preload for the first text‑rendered font.
  • Consider system fonts for ultra‑fast first paint.

Appendix E — Middleware Patterns

  • Geo/localization redirects via edge without client JS.
  • Bot handling: fast deny for known bad UA; allow known renderers.
  • A/B experiments: cookie‑driven variant selection in middleware.

Appendix F — Third‑Party Scripts Governance

  • Inventory all tags; measure cost with WebPageTest.
  • Load non‑critical scripts with afterInteractive or lazyOnload.
  • Consider server‑side proxies for heavy analytics to reduce main‑thread work.

Appendix G — RUM Implementation Details

// app/web-vitals.tsx (extended example)
export function reportWebVitals(metric: any) {
  const body = JSON.stringify({
    id: metric.id,
    name: metric.name,
    value: metric.value,
    label: metric.label,
    navigationType: (performance.getEntriesByType("navigation")[0] as any)?.type,
  });
  navigator.sendBeacon?.("/api/vitals", body) || fetch("/api/vitals", { method: "POST", body, keepalive: true });
}

Appendix H — Bundle Budget Enforcement

  • Set per‑route JS budgets (e.g., ≤120KB P95 client JS).
  • Fail CI on regression; attach analyzer HTML as artifact.
  • Create owner alerts when budget exceeded.

Appendix I — Edge Rendering Patterns

  • Read‑only personalization from cookies/Geo at edge.
  • Stale‑while‑revalidate on HTML for near‑instant navigations.
  • Stream shell immediately; fetch slow widgets downstream.

Appendix J — Profiling Checklist

  • Record performance profiles in DevTools; find long tasks.
  • Audit hydration timings; move logic server‑side when possible.
  • Remove unnecessary providers and global state.

Appendix K — Deployment Recipes

  • Canary by path segment; monitor vitals deltas.
  • Blue/green with traffic split; roll back on SLO breach.
  • Pre‑warm caches after deploy using a sitemap crawler.

Appendix L — Routing and Data‑Fetch Patterns

Static with ISR + Tag Invalidation

  • Use for read‑heavy content that updates occasionally.
  • Pair revalidate with revalidateTag in route handlers after writes.
// app/blog/[slug]/page.tsx
export const revalidate = 600; // 10m
export default async function Page({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug); // cached by tag "posts"
  return <Article post={post} />;
}
// app/api/admin/posts/[id]/route.ts
import { revalidateTag } from "next/cache";
export async function PUT(req: Request, { params }: { params: { id: string } }) {
  await updatePost(params.id, await req.json());
  revalidateTag("posts");
  return new Response(null, { status: 204 });
}

Dynamic with Edge Runtime

  • Personalized dashboards; fast read latencies.
// app/(dash)/layout.tsx
export const runtime = "edge";

Parallel Routes for Faster Above‑the‑Fold

  • Keep critical shell separate and stream.
// app/(shop)/@shell/page.tsx
export default function Shell() { return <Hero />; }

// app/(shop)/@details/page.tsx
export default async function Details() { return <Suspense fallback={<Skeleton/>}><DetailsInner/></Suspense>; }

Appendix M — Server Components Patterns

  • Move data fetching to server components to reduce client JS.
  • Keep client boundaries minimal, colocated with interactivity.
  • Avoid passing large props; fetch again server‑side at child when needed.
// server component
export default async function Orders() {
  const orders = await listOrders();
  return <OrdersTable orders={orders} />;
}

// client boundary kept small
"use client";
export function OrdersFilter({ initial }: { initial: string }) {
  const [q, setQ] = useState(initial);
  return <input value={q} onChange={(e)=>setQ(e.target.value)} />;
}

Appendix N — Suspense and Streaming Recipes

  • Wrap slow widgets in Suspense with lightweight skeletons.
  • Stream the shell immediately to improve TTFB and LCP.
  • Compose multiple independent Suspense boundaries.
<Suspense fallback={<div className="skeleton h-36" />}>
  <RevenueWidget />
</Suspense>
<Suspense fallback={<div className="skeleton h-24" />}>
  <TrafficWidget />
</Suspense>

Appendix O — Cache Tagging Playbook

  • Tag per entity collection: products, categories, posts.
  • Invalidate on writes only the affected tags.
  • Avoid global revalidate; keep invalidations surgical.

Checklist:

  • Define tags per route family
  • Centralize invalidation calls in write paths
  • Monitor tag hit/miss metrics

Appendix P — Page‑Level Performance Budgets

  • Home: ≤ 100KB JS, LCP ≤ 1.2s, INP ≤ 200ms
  • Product: ≤ 160KB JS, LCP ≤ 1.5s, INP ≤ 200ms
  • Dashboard: ≤ 220KB JS (client), long task P95 ≤ 40ms

Enforce in CI with analyzer and thresholds.

Appendix Q — Image and CDN Configuration

  • Prefer AVIF/WebP; high‑DPR variants for retina only when needed.
  • Configure responsive sizes accurately; audit in devtools.
  • Coalesce requests via CDN; ensure HTTP/2/3 enabled.

Appendix R — Script Loading Strategies

  • Critical inline: beforeInteractive only when absolutely necessary.
  • Most third‑party: afterInteractive; analytics: lazyOnload.
  • Replace heavy inline widgets with server‑rendered proxies.

Appendix S — CSS and Rendering

  • Prefer CSS features (containment, content‑visibility) cautiously.
  • Reduce style recalculations; avoid large global styles.
  • Modularize CSS; avoid heavy runtime CSS‑in‑JS on critical path.

Appendix T — Accessibility and Performance

  • Ensure focus management without extra JS.
  • Avoid hidden but rendered heavy DOM trees.
  • Use semantic elements which often render cheaper.

Appendix U — Mobile‑First Optimization

  • Target mid‑tier Android devices in testing.
  • Budget for CPU limits; ensure 60fps scroll.
  • Optimize touch targets; reduce reflows on input.

Appendix V — Database and API Performance Patterns

  • Use server‑side pagination with stable sort keys.
  • Prefer keyset pagination (cursors) for large datasets.
  • Add composite indexes for frequent filters.

Appendix W — Observability and Alerting

  • Lighthouse CI regression alerts per PR.
  • Field Vitals alert: LCP P75 > 2.5s, INP P75 > 250ms, CLS P75 > 0.1.
  • Token and cache metrics for API/data layers.

Appendix X — Networking and Compression

  • Enable Brotli; verify content‑encoding in responses.
  • Long‑lived immutable cache for versioned assets.
  • Tweak server timeouts and keep‑alive for streaming.

Appendix Y — Build and CI Hardening

  • Fail build on budget regression; attach analyzer artifact.
  • Run type‑check and lint in parallel; cache results.
  • Use incremental builds; avoid unnecessary transpilation.

Appendix Z — Troubleshooting Patterns

Symptoms → Actions:

  • High LCP on hero pages → Optimize image, preload, stream shell sooner, move logic to server.
  • High INP after hydration → Split client components, reduce props, use useTransition.
  • CLS spikes on mobile → Reserve media slots, correct font strategy, avoid late layout changes.

Appendix AA — Performance Review Checklist (100 items)

  1. JS budget defined per page
  2. Bundle analyzer integrated
  3. Client boundaries audited
  4. Server components preferred
  5. Dynamic import used for rare widgets
  6. No unused polyfills
  7. Moment replaced with dayjs/date‑fns
  8. Tree‑shaken UI libs
  9. No heavy charts on first paint
  10. Critical CSS minimal
  11. Fonts self‑hosted
  12. display: swap enabled
  13. Font subsets generated
  14. Hero images priority
  15. Accurate sizes attributes
  16. AVIF/WebP formats
  17. CDN configured for images
  18. Remote patterns set in next.config
  19. Preload key assets
  20. Avoid render‑blocking scripts
  21. Use afterInteractive for third‑party
  22. Lazy load analytics
  23. No synchronous XHR
  24. HTTP/2 or HTTP/3 enabled
  25. Brotli enabled
  26. Gzip fallback configured
  27. Cache‑Control set for static
  28. ETag/Last‑Modified tuned
  29. stale-while-revalidate for HTML where safe
  30. Edge runtime for read‑heavy pages
  31. Streaming route handlers used
  32. Suspense boundaries for slow widgets
  33. Skeletons lightweight
  34. Avoid expensive SVG filters
  35. Use CSS transforms over layout thrash
  36. Avoid forced reflow patterns
  37. Event listeners passive where possible
  38. Debounce input handlers
  39. requestIdleCallback for low‑priority work
  40. useTransition for state updates
  41. Reduce effect dependencies
  42. Avoid deep prop drilling
  43. Memoize expensive components
  44. Virtualize long lists
  45. RUM implemented
  46. Web Vitals sent to backend
  47. Alerting thresholds defined
  48. Lighthouse CI per PR
  49. Budget thresholds guarded in CI
  50. Smoke tests for vitals on staging
  51. Synthetic checks for critical paths
  52. CDN cache hit ratio tracked
  53. API latency P95 monitored
  54. DB slow query log reviewed
  55. Indexes audited quarterly
  56. N+1 checks in server code
  57. Cache tags documented
  58. Invalidation paths tested
  59. Sitemap warmup job post‑deploy
  60. Preconnect to critical origins
  61. DNS‑prefetch where safe
  62. Third‑party cost inventory
  63. Kill switch for heavy tags
  64. Feature flags for risky features
  65. Blue/green rollback script
  66. Canary metrics comparison
  67. Service worker only if justified
  68. No double hydration
  69. No duplicate React versions
  70. Minimal context providers
  71. Avoid global state when possible
  72. Use selectors in state libs
  73. Avoid large JSON in props
  74. Serialize small shapes only
  75. Prefer IDs then fetch server‑side
  76. Ensure streaming flush early
  77. Avoid large HTML comments
  78. Compress JSON responses
  79. Paginate API responses
  80. Prefer 206 for large downloads
  81. Avoid inline base64 for large images
  82. Use CDN image optimizer
  83. Verify CORS preflight costs
  84. Keep headers minimal
  85. Limit cookies size
  86. SameSite and secure flags
  87. Avoid layout jank on toasts/modals
  88. Use portal with reserved area
  89. Test low‑end devices
  90. Emulate throttled CPU/network
  91. Measure interaction flows
  92. Ensure focus outlines visible
  93. Avoid ARIA where semantics suffice
  94. Preload next route data where safe
  95. Use prefetch on Link
  96. Avoid oversized hit targets
  97. Avoid position: fixed abuse
  98. Reduce z‑index layers
  99. Keep DOM depth reasonable
  100. Document learnings in ADRs

Appendix AB — Example Headers and Policies

// next.config.ts headers (extended)
export default {
  async headers() {
    return [
      {
        source: "/:all*(js|css|woff2|svg|jpg|jpeg|png|webp|avif|gif|ico)",
        headers: [
          { key: "Cache-Control", value: "public, max-age=31536000, immutable" },
        ],
      },
      {
        source: "/(.*)",
        headers: [
          { key: "Strict-Transport-Security", value: "max-age=15552000; includeSubDomains" },
          { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
          { key: "X-Content-Type-Options", value: "nosniff" },
          { key: "Permissions-Policy", value: "accelerometer=(), geolocation=(), microphone=()" },
        ],
      },
    ];
  },
};

Appendix AC — Sample RUM Backend (ClickHouse)

CREATE TABLE vitals (
  id String,
  name LowCardinality(String),
  value Float64,
  ts DateTime DEFAULT now(),
  url String,
  user_agent String
) ENGINE = MergeTree ORDER BY (name, ts);
SELECT name, quantileExactWeighted(0.75)(value, 1) AS p75
FROM vitals
WHERE ts > now() - INTERVAL 7 DAY
GROUP BY name
ORDER BY name;

Appendix AD — Core Web Vitals SLOs and Guardrails

  • SLO 01: LCP P75 ≤ 1.5s on mobile, ≤ 1.2s on desktop
  • SLO 02: INP P75 ≤ 200ms
  • SLO 03: CLS P75 ≤ 0.1
  • SLO 04: TTFB P75 ≤ 300ms (HTML)
  • SLO 05: JS payload P95 ≤ 150KB per route (client JS)
  • SLO 06: Image bytes P95 ≤ 1200KB per route
  • SLO 07: Third‑party time < 10% of total main‑thread
  • SLO 08: Error rate < 1% per route
  • SLO 09: Cache hit ratio ≥ 80% for static assets
  • SLO 10: HTML revalidation ratio ≥ 50% on read‑heavy pages

Alert thresholds:

  • Warn at 80% of SLO, page owner notified
  • Critical at 100% breach, block release until mitigated

Appendix AE — Route Archetypes and Patterns

  • Marketing landing: static shell, ISR 15–60m, edge cache HTML
  • Product detail: semi‑static with ISR (60–300s), tag invalidate on price/stock
  • Dashboard: dynamic, edge runtime, stream widgets behind Suspense
  • Search results: dynamic with query signature, cache sets per filter
  • Blog/article: fully static, long‑lived assets, image formats AVIF/WebP
  • Checkout: dynamic, Node runtime for PCI SDKs, minimal JS

Appendix AF — State Management and Hydration Budget

  • Avoid global client state for read‑only data; fetch in server components
  • Keep client boundaries near interactivity, not at page root
  • Hydration budget: ≤ 100ms main‑thread on mid‑tier Android
  • Use useTransition for heavy updates; avoid sync reducers on input events

Checklist:

  • Remove unnecessary providers
  • Memoize selectors to reduce re‑renders
  • Split large client trees into islands
  • Defer non‑critical effects with requestIdleCallback

Appendix AG — Images Pipeline Checklist

  • Use AVIF/WebP with fallbacks
  • Provide intrinsic width/height and precise sizes
  • Deduplicate sources across breakpoints
  • Lazy‑load below‑the‑fold; priority only above‑the‑fold
  • Use CDN resizing; avoid sending original large assets
  • Strip EXIF unless required
  • Preload hero image and first text font

Appendix AH — CDN Tuning and Headers

  • Cache-Control for assets: public, max-age=31536000, immutable
  • HTML: max-age=0, must-revalidate, stale-while-revalidate=60
  • ETag/Last‑Modified for conditional fetches
  • Vary kept minimal: content negotiation only when essential
  • Preconnect to critical origins; DNS‑prefetch for third‑party if needed

Appendix AI — Prefetching and Preloading

  • Use Next.js Link prefetch (viewport) for internal routes
  • Preload critical fonts (as=font, crossorigin)
  • Preload hero image or CSS only when demonstrably helpful
  • Avoid over‑preload; measure with Lighthouse and RUM

Appendix AJ — Third‑Party Governance Matrix

  • Inventory columns: script, owner, purpose, bytes, CPU ms, async strategy, consent gate, fallback
  • Block list: any tag failing security or performance bar
  • Quarterly review: remove stale or redundant vendors

Appendix AK — Accessibility Meets Performance

  • Semantic HTML reduces JS need for interactions
  • Focus management without extra libraries
  • Avoid DOM churn that disrupts assistive tech and causes layout shifts

Appendix AL — Mobile First Test Matrix

  • Devices: mid‑tier Android, iPhone baseline, low‑end Android
  • Networks: 4G slow, 3G, offline for SW tests
  • Metrics captured: LCP/INP/CLS, bytes, long task counts, memory

Appendix AM — Database Performance Recipes

  • Add composite indexes on common filters and sorts
  • Prefer keyset pagination; avoid offsets for deep pages
  • Use prepared statements; pool connections; cap concurrency

Appendix AN — API Performance Recipes

  • Coalesce concurrent identical fetches (request coalescing)
  • Compress JSON (Brotli); prefer compact shapes
  • Stream large responses with chunked transfer

Appendix AO — Build System Optimizations

  • Enable SWC/tsconfig target modern
  • Remove legacy polyfills; browserslist tuned to analytics
  • Cache node_modules and Next build artifacts in CI

Appendix AP — Observability Dashboards

  • Pages dashboard: LCP/INP/CLS, JS bytes, image bytes, errors
  • Assets dashboard: cache hits, top offenders, size regressions
  • API dashboard: P50/P95 latency, error rates, throughput

Appendix AQ — Release Policy

  • Block release on SLO breach without approved waiver
  • Require bundle analyzer diff for significant dependency changes
  • Canary 10% traffic minimum for risky changes

Appendix AR — Cookbook: Common Fixes

  • High LCP (image): convert to AVIF, accurate sizes, preload, priority
  • High INP (interaction): split component, move logic server‑side, debounce
  • High CLS: set image dimensions, font strategy, avoid banners pushing content
  • Slow TTFB: cache layer, edge runtime, DB query optimization

Appendix AS — Example Edge Middleware Library

// middleware/lib.ts
export function redirectNonWww(url: URL) {
  if (url.hostname === "example.com") { url.hostname = "www.example.com"; return url; }
  return null;
}

Appendix AT — Security Headers and CSP

  • Add CSP with hashes for inline critical scripts only when needed
  • Disallow unsafe-inline broadly; prefer hashed/predefined snippets
  • Monitor CSP violations to spot regressions

Appendix AU — Performance Budgets by PR Template

### Performance checklist
- [ ] JS added ≤ X KB or justified
- [ ] No new render‑blocking scripts
- [ ] Images optimized and sized
- [ ] RUM/Lighthouse checked

Appendix AV — Team Roles and Ownership

  • Page owners responsible for SLOs
  • Perf champions rotate per quarter
  • CI gatekeeper validates budgets

Appendix AW — Education Plan

  • Monthly brown‑bags on CWV and Next.js patterns
  • Docs in repo with before/after examples
  • Onboarding includes perf checklist review

Appendix AX — Anti‑patterns to Avoid

  • Client fetching for server‑ready data
  • Large client providers at root
  • Over‑preloading fonts/assets
  • Heavy third‑party tags on critical pages

Appendix AY — Post‑Incident Review Template

Impact: metrics affected, duration, scope
Root cause: code, infra, vendor
Fix: immediate and long‑term
Learnings: prevent recurrence

Appendix AZ — Long List of Micro‑Checkpoints (200)

  1. Remove unused imports in client code
  2. Avoid blocking synchronous loops on render
  3. Prefer fetch in server components
  4. Ensure revalidate set appropriately
  5. Tag caches for list pages
  6. Invalidate tags on writes
  7. Prefer edge for read‑only widgets
  8. Stream HTML shell ASAP
  9. Add lightweight skeletons
  10. Avoid heavy SVG filters
  11. Inline critical CSS sparingly
  12. Use next/script strategies
  13. Defer analytics
  14. Preconnect to image CDN
  15. Compact JSON payloads
  16. Remove moment.js
  17. Replace with date‑fns/dayjs
  18. Tree‑shake components
  19. Split bundles logically
  20. Avoid deep prop drilling
  21. Use context selectors
  22. Memoize heavy lists
  23. Virtualize large lists
  24. Avoid layout thrash
  25. Use CSS transforms
  26. Reserve media space
  27. Swap fonts
  28. Subset fonts
  29. Preload first font only
  30. Accurate sizes
  31. Prefer AVIF
  32. Lazy images below fold
  33. Optimize hero
  34. Prefer server sorting/filtering
  35. Use keyset pagination
  36. Index DB filters
  37. Avoid N+1 queries
  38. Batch loaders
  39. CDN cache keys correct
  40. HTML SWR where safe
  41. ETag configured
  42. Gzip/Brotli enabled
  43. HTTP/2/3 enabled
  44. Keep‑alive tuned
  45. Timeouts reasonable
  46. Do not block main thread
  47. Move work off thread
  48. Use Web Workers when needed
  49. Avoid heavy client charts
  50. Render charts server‑side when possible
  51. Collapse third‑party
  52. Gate by consent
  53. Remove dead tags
  54. Monitor tag cost
  55. Budget per route
  56. Analyzer in CI
  57. Artifact link posted
  58. Owners notified
  59. Canary monitored
  60. Rollback scripts ready
  61. Warm caches post deploy
  62. Crawl sitemap
  63. Precompute common queries
  64. Normalize filter params
  65. Stable cache signatures
  66. Avoid global try/catch masking
  67. Log structured errors
  68. Sample traces
  69. Alert on regressions
  70. Track long tasks
  71. Track memory leaks
  72. Test low‑end devices
  73. Throttle CPU/network
  74. Audit CLS snapshots
  75. Audit INP interactions
  76. Audit LCP element
  77. Document fixes
  78. Share learnings
  79. Update ADRs
  80. Clean stale feature flags
  81. Remove legacy polyfills
  82. Modern browserslist
  83. Avoid large i18n payloads
  84. Lazy load locales
  85. Prefer small date libs
  86. Purge unused CSS
  87. CSS containment wisely
  88. Avoid heavy shadows
  89. Simplify component trees
  90. Minimize hydration props
  91. Use streaming responses
  92. Prefer edge caching
  93. Audit cookie sizes
  94. Reduce header bloat
  95. Compress responses
  96. Use 206 for large files
  97. Avoid base64 images inline
  98. Serve icons as SVG sprite
  99. Coalesce requests
  100. Reuse connections
  101. Avoid SSR CPU spikes
  102. Limit sync crypto in Node
  103. Cache secrets securely
  104. Avoid dynamic require
  105. ES modules preferred
  106. Use SWC plugins carefully
  107. Source maps limited in prod
  108. Remove console logs
  109. Disable React devtools in prod
  110. Avoid eval in CSP
  111. Hash inline scripts
  112. Secure headers set
  113. Permissions‑Policy tight
  114. HSTS enabled
  115. Referrer‑Policy strict
  116. SameSite cookies
  117. HttpOnly cookies
  118. Avoid setTimeout loops
  119. Prefer requestAnimationFrame
  120. Avoid observers on many nodes
  121. Unobserve on cleanup
  122. Avoid forced sync layout
  123. Use ResizeObserver minimal
  124. Debounce scroll handlers
  125. Passive listeners
  126. Avoid oversized JSON
  127. Stream logs
  128. Batch analytics
  129. Sample rates reasonable
  130. Avoid conflicting CSS
  131. Keep z‑index minimal
  132. Reduce DOM depth
  133. Avoid massive tables
  134. Paginate tables
  135. Sticky headers carefully
  136. Use contain sparingly
  137. Avoid CSS heavy filters
  138. Inline small SVGs
  139. Sprite larger icons
  140. Avoid data URIs for big assets
  141. Use link rel=preload carefully
  142. Remove duplicate preloads
  143. Use preconnect sparingly
  144. Cache fonts long
  145. Version assets
  146. Immutable URLs
  147. SRI where applicable
  148. Validate integrity
  149. Avoid try/catch hot paths
  150. Prefer fast path checks
  151. Memoize selectors
  152. Avoid spreading props blindly
  153. Move constants out of components
  154. Avoid anonymous functions when hot
  155. Key lists properly
  156. Avoid re‑mount cascades
  157. Prefer CSS animations
  158. Avoid JS heavy animations
  159. Prefer transform/opacity
  160. Avoid box‑shadow animations
  161. Avoid layout thrash in modals
  162. Portal for overlays
  163. Preallocate overlay area
  164. Keep skeletons simple
  165. Reuse skeleton components
  166. Avoid over‑fetching in client
  167. Use suspense boundaries
  168. Reduce boundary count if heavy
  169. Combine small widgets
  170. Avoid nested scroll containers
  171. Prefer native scrolling
  172. Reduce sticky elements
  173. Visibility toggles cheap
  174. Avoid repaint thrash
  175. Group DOM updates
  176. Avoid sync layout reads/writes
  177. Use useLayoutEffect sparingly
  178. Prefer useEffect for non‑visual
  179. Split bundles by route
  180. Remove unused locales from libs
  181. Prefer light maps libs
  182. Static maps where possible
  183. Reduce third‑party iframes
  184. Lazy load embeds
  185. Consent gates for tracking
  186. Measure before adding vendors
  187. Define exit plan for vendors
  188. Budget per vendor
  189. Monitor vendor outages
  190. Fallback UIs for vendor failure
  191. Keep error boundaries friendly
  192. Log user agent and net type
  193. Optimize critical flows first
  194. Prioritize revenue pages
  195. Prioritize onboarding speed
  196. Remove blockers to first action
  197. Optimize warm navigations
  198. Cache same‑origin GETs
  199. Use HTTP/2 push? (deprecated; prefer preload)
  200. Keep learning and iterating

Appendix BA — Final Quick Reference (Top 50)

  1. Prefer server components; keep client islands small
  2. Stream the shell early; wrap slow widgets in Suspense
  3. Accurate image sizes; AVIF/WebP; priority for hero
  4. Self‑host fonts; display: swap; preload first text font
  5. Define JS budgets; analyze bundles in CI
  6. Remove heavy libs (moment → dayjs); tree‑shake UI libs
  7. Use edge runtime for read‑only personalized pages
  8. Cache with revalidate + tags; invalidate on writes
  9. CDN: long cache for assets; HTML SWR for read‑heavy pages
  10. RUM Web Vitals; alert on SLO breaches
  11. Preconnect and preload sparingly; measure impact
  12. Lazy‑load below‑fold images and non‑critical scripts
  13. Keep middleware fast; do only what’s essential at edge
  14. Prefer CSS transforms/opacity for animations
  15. Avoid layout thrash; reserve media space
  16. Cursor pagination; batch resolver/API calls
  17. Compress JSON; Brotli for assets and API
  18. Minimize global state; memoize selectors
  19. Prefer system fonts or subsetting for speed
  20. Canary risky releases; pre‑warm caches
  21. Strict headers (HSTS, CSP, Referrer‑Policy)
  22. Keep cookies small; secure flags
  23. Limit third‑party; gate by consent; monitor cost
  24. Document fixes; share ADRs; keep checklists current
  25. Test on mid‑tier Android; throttle CPU/network
  26. Use useTransition for heavy updates
  27. Avoid double hydration and duplicate React versions
  28. Paginate big tables; virtualize long lists
  29. Prefer server sorting/filtering
  30. Normalize filter params for cache signatures
  31. Batch analytics; sample reasonably
  32. Avoid oversized JSON in props; fetch server‑side
  33. Defer non‑critical effects with requestIdleCallback
  34. Prefer afterInteractive for scripts; lazyOnload for analytics
  35. Keep DOM depth reasonable; reduce z‑index layers
  36. Remove console logs and dev‑only code in prod
  37. Protect performance in reviews; PR template checklist
  38. Educate team; rotate perf champions
  39. Track long tasks P95; fix root causes
  40. Reduce hydration props; keep boundaries local
  41. Use remote image patterns and modern formats
  42. Ensure edge/platform compression enabled
  43. Instrument per‑route bytes and vitals in dashboards
  44. Fail CI on budget regressions; attach artifacts
  45. Set operation timeouts and circuit breakers
  46. Coalesce duplicate fetches; cache GETs
  47. Prefer node runtime where libs require it (sharp, DB)
  48. Keep error boundaries friendly and cheap
  49. Build small, deploy fast; roll back faster
  50. Measure, iterate, and keep shipping

Appendix BB — Glossary

  • App Router: Next.js routing system with server components and layouts
  • RSC: React Server Components, render on server without shipping JS
  • ISR: Incremental Static Regeneration; revalidate static pages
  • INP: Interaction to Next Paint; input latency metric
  • LCP: Largest Contentful Paint; loading metric
  • CLS: Cumulative Layout Shift; visual stability metric
  • SWR: Stale‑While‑Revalidate; serve stale while refreshing in background
  • SLO: Service Level Objective; target metric threshold
  • RUM: Real‑User Monitoring; field data from real users
  • Edge runtime: lightweight JS runtime at CDN edge
  • Bundle budget: per‑route JS size targets
  • Suspense: React primitive to suspend rendering while awaiting data
  • Streaming: sending HTML chunks progressively as ready

Appendix BC — References

  • Next.js docs (routing, caching, images)
  • React docs (Suspense, server components)
  • Web Vitals (LCP, INP, CLS)
  • Lighthouse and WebPageTest
  • Chrome DevTools performance profiler

Appendix BD — Final Notes

  • Performance is product quality. Budget it like a feature.
  • Favor simplicity; fewer moving parts ship faster and break less.
  • Iterate with data; field metrics trump lab scores alone.
  • Teach the patterns; good defaults beat checklists.

Appendix BE — Addendum: Example headers() with CSP

// next.config.ts (CSP example)
export default {
  async headers() {
    const csp = [
      "default-src 'self'",
      "script-src 'self' 'sha256-abc...' https://www.googletagmanager.com",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self' data:",
      "connect-src 'self' https:",
      "frame-ancestors 'none'",
    ].join('; ');
    return [
      { source: '/(.*)', headers: [
        { key: 'Content-Security-Policy', value: csp },
      ]},
    ];
  },
};

Appendix BF — Template: Performance ADR

# ADR: Introduce JS budget and bundle analyzer in CI

Context: Client JS growth risks INP and slow TTI.
Decision: Enforce 120KB P95 per route, fail CI on regression.
Consequences: Some features require code splitting; docs updated.

Appendix BG — Template: Rollback Plan

Trigger: Vitals breach or error spike after deploy
Steps: Roll back to previous build, invalidate caches, notify owners
Verification: RUM dashboards normal; incident closed

Appendix BH — Sample PR Template (Full)

### Summary
What changed and why?

### Performance
- JS added (KB):
- Images optimized: yes/no
- Affects critical path: yes/no

### Testing
- RUM/Lighthouse: attach screenshots
- Devices/Networks tested:

### Risks
- Rollback plan:

Appendix BI — Sample perf.config.json

{
  "budgets": {
    "home": { "clientJsKB_P95": 100 },
    "product": { "clientJsKB_P95": 160 },
    "dashboard": { "clientJsKB_P95": 220 }
  },
  "alerts": { "slackWebhook": "https://hooks.slack.com/services/..." }
}

Appendix BJ — End-to-End Perf Test Script (Pseudo)

import { test, expect } from '@playwright/test';

test('home vitals smoke', async ({ page }) => {
  await page.route('**/*', (route) => route.continue());
  await page.goto('https://www.example.com/');
  // check hero visible quickly
  const start = Date.now();
  await page.getByRole('heading', { name: /welcome/i }).waitFor();
  const lcpMs = Date.now() - start;
  expect(lcpMs).toBeLessThan(1200);
});

Appendix BK — Final Checklist Snapshot (10)

  1. Server components default
  2. Streamed shell with Suspense
  3. Accurate images and fonts
  4. JS budgets enforced
  5. Edge runtime where it helps
  6. Cache strategy documented
  7. RUM + alerts configured
  8. Third‑party governed
  9. CI gates in place
  10. Rollback and warmup ready

Appendix BL — Credits

  • Maintainers: Performance guild @ Elysiate
  • Contributors: Frontend, Platform, SRE teams

Appendix BM — Changelog (Perf Playbook)

  • 2025‑10‑25: Initial comprehensive edition with RSC, streaming, caching, and CWV

Thank you for caring about performance. Fast feels good.


Appendix Index: A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO, AP, AQ, AR, AS, AT, AU, AV, AW, AX, AY, AZ, BA, BB, BC, BD, BE, BF, BG, BH, BI, BJ, BK, BL, BM, BN.

Appendix BO — Tuning Playbook (Quick Steps)

  • Step 001: Audit current LCP/INP/CLS with RUM and Lighthouse
  • Step 002: Identify LCP element and image/font dependencies
  • Step 003: Stream shell earlier; add Suspense around slow widgets
  • Step 004: Convert hero image to AVIF/WebP with accurate sizes
  • Step 005: Self‑host fonts; preload first text font; enable swap
  • Step 006: Reduce client JS by moving logic to server components
  • Step 007: Define per‑route JS budgets and enforce in CI
  • Step 008: Replace heavy libraries; tree‑shake UI components
  • Step 009: Dynamic import rarely used widgets with skeletons
  • Step 010: Configure CDN long cache for assets; SWR for HTML
  • Step 011: Add cache tags to lists; invalidate on writes
  • Step 012: Push read‑only personalization to edge runtime
  • Step 013: Prefetch internal links where useful; measure impact
  • Step 014: Defer non‑critical scripts with afterInteractive/lazyOnload
  • Step 015: Remove third‑party tags without clear value
  • Step 016: Gate remaining third‑party behind consent
  • Step 017: Preconnect only to critical origins; drop unused
  • Step 018: Reserve layout space for media and dynamic UI
  • Step 019: Subset fonts; minimize variants; avoid FOIT/FOUT
  • Step 020: Enable Brotli; verify content‑encoding in responses
  • Step 021: Compress JSON; reduce payload shapes
  • Step 022: Coalesce duplicate fetches; cache same‑origin GETs
  • Step 023: Use keyset pagination; index common filters
  • Step 024: Batch resolver/API calls; avoid N+1
  • Step 025: Profile interactions; split long tasks with transitions
  • Step 026: Minimize global state; memoize selectors
  • Step 027: Virtualize long lists; paginate big tables
  • Step 028: Remove console logs and dev‑only code in prod
  • Step 029: Add error boundaries; keep fallbacks cheap
  • Step 030: Warm caches after deploy via sitemap crawler
  • Step 031: Canary risky changes; monitor vitals deltas
  • Step 032: Add alerts on SLO breaches with owner routing
  • Step 033: Document fixes in ADRs; share learnings team‑wide
  • Step 034: Re‑run Lighthouse CI; compare bundle analyzer
  • Step 035: Validate CLS snapshots and INP interactions on mobile
  • Step 036: Re‑baseline budgets; lock improvements in CI
  • Step 037: Remove stale feature flags and dead code
  • Step 038: Tune middleware to be minimal and fast
  • Step 039: Switch blocking CSS/JS to non‑blocking where safe
  • Step 040: Replace layout animations with transform/opacity
  • Step 041: Reduce DOM depth and z‑index layers
  • Step 042: Use sizes precisely for responsive images
  • Step 043: Audit cookie size and headers bloat
  • Step 044: Add stale-while-revalidate where safe for HTML
  • Step 045: Ensure remote image patterns and modern formats
  • Step 046: Quantify third‑party CPU time; set budget
  • Step 047: Add circuit breakers and timeouts to APIs
  • Step 048: Keep Node runtime where native libs required
  • Step 049: Stream route handlers for long responses
  • Step 050: Verify hydration props minimal and local
  • Step 051: Prefer system fonts for ultra‑fast prototypes
  • Step 052: Strip EXIF; dedupe responsive sources
  • Step 053: Avoid nested scroll containers; prefer native scroll
  • Step 054: Debounce input handlers; use passive listeners
  • Step 055: Preload only assets with measured benefit
  • Step 056: Validate integrity and CSP for scripts
  • Step 057: Add PR performance checklist template
  • Step 058: Build and store analyzer artifacts per PR
  • Step 059: Alert owners automatically on regressions
  • Step 060: Throttle CPU/network during local testing
  • Step 061: Verify edge caches hit ratio and TTLs
  • Step 062: Reduce hydration surface by shrinking client islands
  • Step 063: Ensure server sorting/filtering over client work
  • Step 064: Normalize query params for cache signatures
  • Step 065: Prefer streaming HTML over client waterfalls
  • Step 066: Remove unused polyfills via modern browserslist
  • Step 067: Purge unused CSS; leverage containment carefully
  • Step 068: Avoid large inline data URIs; use CDN assets
  • Step 069: Track long tasks P95 per route in dashboards
  • Step 070: Add owner and SLO metadata to pages
  • Step 071: Set release policy to block on SLO breaches
  • Step 072: Provide rollback and warm‑up procedures
  • Step 073: Educate devs on RSC, Suspense, caching
  • Step 074: Keep a live perf playbook in the repo
  • Step 075: Review third‑party list quarterly
  • Step 076: Benchmark on representative devices
  • Step 077: Add accessibility checks that also aid perf
  • Step 078: Keep JSON schemas compact for API
  • Step 079: Paginate analytics exports; prefer 206
  • Step 080: Validate edge vs node runtime per route
  • Step 081: Verify next/image domains and formats
  • Step 082: Use skeletons that are cheap to render
  • Step 083: Trim locale/data bundles; lazy load
  • Step 084: Remove duplicate preloads/preconnects
  • Step 085: Record and share before/after flamecharts
  • Step 086: Track JS bytes and image bytes per commit
  • Step 087: Celebrate wins; keep iterating with data

Related posts