Performance

How to talk about performance

The five layers, the instrument for each, and how to structure an answer so an interviewer hears judgement rather than a list of tricks.

This page is mostly a lookup table, but it is the one to internalise: in an interview you will be given a symptom, and you get credit for reaching for the right instrument before the right fix.

Know

LayerSymptom the user reportsInstrumentTypical levers
Build / dev"nuxt dev takes forever", "HMR reloads the whole page"hyperfine 'pnpm nuxt build', DEBUG=nuxt:*, node --cpu-prof node_modules/nuxt/bin/nuxt.mjs build, nuxt analyzecheap module setup, cache derived work under buildDir, vite.optimizeDeps, fewer global components and auto-imports
Server (TTFB)"the first byte is slow", "it is fine locally"curl -s -o /dev/null -w '%{time_starttransfer}\n', autocannon, node --cpu-prof, a Server-Timing headerparallel data fetching, lazy, route rules (swr, isr, prerender), cached handlers, smaller payload
Network"it is slow on 4G", "huge HTML"Network panel, curl -so /dev/null -w '%{size_download}', DevTools → Payloadshrink runtimeConfig.public, pick/transform, compression, image and font modules, fewer third-party scripts
Client"slow to become interactive", "layout jumps"Lighthouse, CrUX, Performance panel, web-vitals in a plugincode splitting, Lazy* with hydration strategies, server components, shallowRef/markRaw, deferred third-party scripts
Cost / scale"the bill", "pods restart under load"autocannon throughput, cache hit ratio, process.memoryUsage()caching layer, edge rendering, prerendering, fixing the leak first (memory)
  • Quote numbers, not adjectives. "TTFB p95 was 900 ms, three sequential fetches accounted for 640 ms of it, parallelising took it to 310 ms" is the sentence that ends the question.
  • Say what you would not do. Refusing to cache a personalised route, or refusing to add a lever you cannot measure, reads as seniority.
  • Distinguish p50 from p95. Averages hide the cache misses and the cold starts, which is exactly where the complaints come from.
  • Know the cheap wins that are not free: prerendering breaks personalisation, SWR serves stale HTML after a deploy, lazy hydration delays interactivity, prefetching spends the user's bandwidth.

How it works

Four commands worth having in muscle memory:

# TTFB on a warm, built server (run it a dozen times, look at the spread, not one number)
curl -s -o /dev/null -w 'ttfb=%{time_starttransfer}s total=%{time_total}s size=%{size_download}B\n' http://localhost:3000/

# Throughput and latency percentiles under load
npx autocannon -c 20 -d 30 http://localhost:3000/

# Build time with and without a change, repeated and averaged
hyperfine --warmup 1 --runs 5 'pnpm nuxt build'

# Where the client bundle went
pnpm nuxt analyze

A Server-Timing header turns the browser's Network panel into a server profiler, which is the fastest way to show a team where the time goes:

layers/base/server/plugins/server-timing.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.startedAt = performance.now()
  })
  nitroApp.hooks.hook('beforeResponse', (event) => {
    const started = event.context.startedAt as number | undefined
    if (!started) return
    setResponseHeader(event, 'Server-Timing', `total;dur=${(performance.now() - started).toFixed(1)}`)
  })
})
Gotcha· One number is not a measurement

Cold caches, JIT warm-up and a laptop throttling under load all produce numbers that look like wins. Warm up, repeat, and compare distributions. hyperfine and autocannon do this for you; a single curl does not.

Exercise

Exercise
  • Write the Server-Timing plugin above into your layer behind an option that defaults to off. Read the timing in DevTools → Network → Timing.
  • Pick three symptoms from the table, and for each write two sentences in your notes: the instrument you would use and the first lever you would test. Say them out loud.

Be able to say

Be able to say· A stakeholder says the app feels slow. What do you ask and what do you do first?

"I ask what they did and where, because 'slow' is four different problems: slow to first byte, slow to load, slow to become interactive, or janky once loaded. Then I measure that specific layer on a built server, not in dev: curl timings and autocannon for the server, Lighthouse and the Performance panel for the client, nuxt analyze for the bundle. Only once I can name the layer do I pick a lever, and I re-run the same measurement afterwards so I can quote the delta rather than claim an improvement."