Skip to content

perf: drop 566 KB and 26 requests from the homepage (−36%) - #74

Merged
nikolas-sapa merged 1 commit into
mainfrom
perf/weight-and-ttfb
Oct 13, 2026
Merged

nikolas-sapa merged 1 commit into
mainfrom
perf/weight-and-ttfb

Conversation

@nikolas-sapa

Copy link
Copy Markdown
Owner

Four changes, no new dependencies, zero client-JS delta — these are request and payload wins, which is exactly what the perf-budget gate's own docblock says it structurally cannot see.

Measured locally (next start, same harness both sides, CDP encodedDataLength, 16s settle)

page before after delta
/ 1592 KB / 64 req 1026 KB / 38 req −566 KB (−36%), −26 req
/components/accordion-latch 698 KB / 85 req 519 KB / 57 req −179 KB (−26%)
/components/hero-particles-webgl 963 KB / 85 req 784 KB / 58 req −179 KB (−19%)
/install 629 KB / 59 req 456 KB / 33 req −173 KB (−28%)
/preview/<slug>/embed 372 KB / 21 req 372 KB / 21 req 0 — shell never renders there

LCP 524 → 200ms, domInteractive 599 → 280ms, load 754 → 447ms on /. The request-count deltas are the solid numbers; local LCP and TTFB are noisy.

What changed

The brand link was prefetching the homepage on every page — 77 KB of RSC payload, 80.5 KB on / itself. Now prefetch={false} with a hand-warm on pointer-enter and touch-start, so aimed navigation stays instant. The bottom bar's 13 utility links got the same treatment: that was 28 RSC requests and 148 KB on / alone.

The search corpus (/search-index.json, 146 KB on the wire) was prefetched unconditionally on idle. It now warms on intent. onFocus was already wired so keyboard users were never affected; onPointerEnter covers the mouse path.

The data-cache cliff is closed. lib/status-checks.ts made two fetches over Next's 2 MB ceiling, so every build logged Failed to set Next.js data cache for a 3.7 MB registry.json and a 16.8 MB MCP snapshot — and neither was ever cached. Now cache: "no-store", with the three derived reads in app/status/page.tsx wrapped in unstable_cache at the same hourly revalidate. 383 bytes of cache now hold what was costing a 1,078,509-byte entry plus two refused ones. Both warnings gone.

Production projection, stated honestly

The removed resources measure 146 KB, 77 KB and ~67 KB on production (brotli), so / projects 1296 → ~1012 KB and every other page drops ~144 KB.

That after-column is each removed resource's own measured prod size, not a second production run — this branch isn't deployed.

Deliberately NOT touched: the 985 KB chunk

It's three + @react-three/fiber, and it is already correctly split:

  • Referenced by zero prerendered HTML files; reachable only as an async import() target from the 55 KB registry lazy map
  • A full 14-screen catalog scroll on production (54 iframes, 1200 requests) pulls it 0 times
  • The three hero-particles-webgl links on / render as a FeaturedCard with <video> + poster, never an iframe — the homepage has no path to it at all

The obvious lever is dead too: narrowing that component's import * as THREE buys zero bytes, because r3f calls extend(THREE) on the whole namespace to build its JSX catalogue. Getting it down means hand-driving WebGL inside a registry component, for one component, with real behaviour risk.

The perf budget is not near-exhausted

scripts/test-perf-budget.ts sets TOLERANCE = 1.1, so CI fails above 6979 KB total and 1084 KB largest — 656 KB and 99 KB of real headroom, not the 23 KB and 1 KB the raw budget numbers imply. Worth knowing before anyone shaves something unrelated to "make room".

Verification

  • npx tsc --noEmit — exit 0
  • npm run test:source-invariants — 0 problems
  • npm run test:perf-budget — 6323 KB / 985 KB, byte-identical before and after

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ns-ui Ready Ready Preview Oct 13, 2026 12:53pm UTC

Four changes, no new dependencies, zero client-JS delta -- these are
request and payload wins, which is what the perf-budget gate's own
docblock says it structurally cannot see.

The brand `<Link href="/">` in the shell was prefetching the homepage on
EVERY page: 77 KB of RSC payload, 80.5 KB on `/` itself. `prefetch={false}`
plus a hand-warm on pointer-enter and touch-start keeps the instant
navigation for anyone who actually aims at it. The bottom bar's 13
utility links got the same treatment, which was 28 RSC requests and
148 KB on `/` alone.

The search corpus (`/search-index.json`, 146 KB on the wire) was
prefetched unconditionally on idle. It now warms on intent instead.
`onFocus` was already wired, so keyboard users were never affected;
`onPointerEnter` covers the mouse path, so both reach the field warm.

`lib/status-checks.ts` made two fetches that exceeded Next's 2 MB data
cache ceiling, so every build logged `Failed to set Next.js data cache`
for a 3.7 MB registry.json and a 16.8 MB MCP snapshot, and neither was
ever cached. They are now `cache: "no-store"`, with the three derived
reads in `app/status/page.tsx` wrapped in `unstable_cache` at the same
hourly revalidate. 383 bytes of cache now hold what was costing a
1,078,509-byte entry plus two refused ones. Both warnings are gone.

Measured locally against `next start`, same harness both sides, CDP
`encodedDataLength`, 16s settle so media completes:

  /                              1592 KB / 64 req -> 1026 KB / 38 req
  /components/accordion-latch     698 KB / 85 req ->  519 KB / 57 req
  /components/hero-particles-webgl 963 KB / 85 req ->  784 KB / 58 req
  /install                        629 KB / 59 req ->  456 KB / 33 req
  /preview/<slug>/embed           372 KB / 21 req ->  372 KB (shell absent)

LCP 524 -> 200ms, domInteractive 599 -> 280ms, load 754 -> 447ms on `/`.
Request-count deltas are the solid numbers; local LCP and TTFB are noisy.

On production (brotli) the removed resources measure 146 KB, 77 KB and
~67 KB, so `/` projects 1296 -> ~1012 KB and every other page drops
~144 KB. That after-column is each removed resource's own measured prod
size, NOT a second production run -- this branch is not deployed.

Deliberately NOT touched: the 985 KB largest chunk. It is `three` plus
@react-three/fiber, and it is already correctly split -- referenced by
zero prerendered HTML, reachable only as an async import() target from
the 55 KB registry lazy map. A full 14-screen catalog scroll on
production (54 iframes, 1200 requests) pulls it 0 times, and the three
`hero-particles-webgl` links on `/` render as a FeaturedCard with a video
and poster, never an iframe, so the homepage has no path to it at all.
The obvious lever is also dead: narrowing that component's
`import * as THREE` buys zero bytes, because r3f calls `extend(THREE)` on
the whole namespace to build its JSX catalogue. Dropping it down means
hand-driving WebGL in a registry component, for one component, with real
behaviour risk.

Also worth recording: the perf budget is not near-exhausted.
`scripts/test-perf-budget.ts` sets `TOLERANCE = 1.1`, so CI fails above
6979 KB total and 1084 KB largest -- 656 KB and 99 KB of real headroom,
not the 23 KB and 1 KB the raw budget numbers suggest.

Signed-off-by: Nik <84yk8btb9f@privaterelay.appleid.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nikolas-sapa
nikolas-sapa merged commit 0bdd9f7 into main Oct 13, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 67e18b99 Deployed Oct 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant