Back to path
IntermediatePulse · Project 7 of 12 ~7h· 6 milestones

Hold it to a performance budget on a real phone

Continues from the last build: a searchable app with no performance guardrails

Last rung you shipped a fast, accessible search over the event stream, and on your laptop, on your office wifi, it feels instant.

Performance budgets as pass/fail gatesLighthouse CI on a throttled profileCode splitting with React.lazy and SuspenseDiagnosing and fixing cumulative layout shiftTanStack Query caching and refetch tuningDependency auditing and trimmingReading a bundle analyzerWeb Vitals field measurement

What you'll build

A committed performance budget file, a Lighthouse CI config that runs on a throttled mobile profile and fails on regression, a lazily-loaded chart chunk that stays out of the initial bundle, a below-the-fold section that defers its own render until it is near the viewport, reserved layout space so nothing shifts when async content arrives, and a TanStack Query cache tuned to stop redundant refetches.

See how we teach, before you sign up

You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:

lighthouserc.jsonjson
{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "settings": {
        "preset": "mobile",
        "throttlingMethod": "simulate",
        "throttling": {
          "cpuSlowdownMultiplier": 4,
          "rttMs": 150,
          "throughputKbps": 1638.4
        }
      }
    },
    "assert": {
      "assertions": {
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "interactive": ["error", { "maxNumericValue": 4000 }],
        "resource-summary:script:size": ["error", { "maxNumericValue": 180000 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Reading this file

  • "cpuSlowdownMultiplier": 4,Simulates a mid-tier Android CPU, not the M-series chip on your laptop. This is the setting that stops you from measuring on fast wifi only.
  • "throughputKbps": 1638.4Caps the simulated network around a slow 4G connection instead of your fast office wifi.
  • "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }]The LCP budget as a hard error, Lighthouse CI fails the run instead of just reporting a number.
  • "resource-summary:script:size": ["error", { "maxNumericValue": 180000 }]The initial JS ceiling, this is the number that forces the chart out of the main bundle in milestone 3.
  • "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]The CLS budget, tied directly to the reserved-space work you do in milestone 4.

Runs Lighthouse three times on a simulated mid-tier Android profile and fails the run if any metric or the initial JS weight crosses the line.

That's 1 of 8 explained code blocks in this single project.

The build, milestone by milestone

  1. 1

    Write the budget as a pass or fail contract

    4 guided steps

    A budget that lives only in your head gets renegotiated the moment a deadline is tight. A budget that is a typed constant and a CI assertion cannot be quietly ignored, it either passes or the build fails.

  2. 2

    Measure an honest baseline on a throttled profile

    4 guided steps

    You cannot fix what you have not measured honestly. Fast wifi and a laptop CPU hide exactly the problems a real user hits, the baseline has to reproduce the pain before you can prove you removed it.

  3. 3

    Code-split the heavy chart out of the initial bundle

    4 guided steps

    A pass/fail JS ceiling only matters if something is actually forcing weight out of the critical path. The chart is almost always the biggest single offender in an analytics console, splitting it is usually the single highest-leverage fix.

  4. 4

    Kill the layout shift when charts and late data mount

    4 guided steps

    A user does not experience 'the bundle is smaller', they experience 'the button I was about to tap just moved'. CLS is the metric that most directly measures that feeling, and it is the easiest one to accidentally make worse while fixing JS weight.

  5. 5

    Cache honestly and trim dependency weight

    4 guided steps

    Every unnecessary refetch and every kilobyte of an unused dependency chips away at the same JS and interaction budget you just fought to hit. Caching and trimming are the parts of the fix that keep paying off after this rung ends.

  6. 6

    Wire the budget into a CI gate that fails the build

    4 guided steps

    A budget that a human has to remember to check gets skipped the first time a release is late. A budget wired into CI either blocks the merge or it does not exist.

What's inside when you start

3 starter files, ready to clone
6 guided milestones
5 full reference solutions
8 code blocks explained line-by-line
6 "is it working?" checks
4 interview questions it prepares you for

You'll walk away with

A typed performanceBudget.ts committing to LCP, INP, CLS, and initial JS ceilings
A lighthouserc.json that runs three throttled mobile-profile runs and fails on regression
The chart panel code-split into its own chunk via React.lazy and Suspense, out of the initial bundle
A DeferredSection component and reserved-space skeletons that bring Cumulative Layout Shift under 0.1
A tuned TanStack Query client with staleTime and refetchOnWindowFocus set, plus at least one heavy dependency replaced with a native API
A CI-wired bundle-size script that exits non-zero the moment the initial JS ceiling is crossed

This is portfolio-grade. Build it free.

Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.

Start building