PerformanceHow fast is fast enough

100%

How fast is fast enough.

"It should feel fast" is not a requirement, because nobody can fail it. Human attention gives you better numbers: a reaction inside about a tenth of a second feels caused by the tap, a wait under about a second keeps the train of thought, and past about ten seconds the mind wanders off. Give every interaction one of those limits, and the limit tells you what the screen must show while the work is still going.

Beginner34 minUpdated 2 Oct 2026

The idea.

People do not experience milliseconds. They experience whether a tap answered them, whether a wait broke their thought, and whether they gave up and looked away.

Picture a rider on a commuter train with Cobbleway open on a mid-range phone. She taps Add to basket on a chain. If the basket badge changes before her thumb has left the glass, the shop feels like an object she is handling. If nothing moves for half a second she wonders whether the tap registered, and often taps again. If the category page takes three seconds to show resin pads after she picks the filter, she has already glanced out of the window, and part of her attention has to find its way back.

Those three moments sit on three different limits of human perception, and they have been remarkably stable for half a century. Robert Miller analysed how long people tolerate a terminal's reply in 1968; Card, Robertson and Mackinlay built on perception research for their 1991 information workspace; Jakob Nielsen folded both into three round numbers in 1993: about 0.1 s, about 1 s and about 10 s. Google's RAIL model later restated them for browsers. The numbers are approximate, and they are about people, not networks: a fibre connection does not make a person notice delays any later.

The design consequence is the useful part. Each limit tells you what the interface owes the person while the work is still running. Inside 100 ms the result itself can be the feedback. Up to a second the screen must at least admit the request was heard. Beyond that it must show that progress is happening, and beyond ten seconds it must let the person do something else. So a performance requirement does not say "the product page is fast". It lists the interactions and gives each a band.

The limits, and what each one owes the user

LimitWhat the person experiencesWhat the interface owes themA Cobbleway interaction in this band
one frame: 16.7 ms at 60 Hz, 8.3 ms at 120 HzMotion looks continuous; a missed frame reads as a stutterA new frame every refresh while something movesDragging the price-range slider on a category page
~100 msThe response feels caused by the actionThe result itself or a visible reaction of the controlPressing a button, picking a size, the basket badge after Add to basket
~1 sThe delay is noticed but the thought is not lostAcknowledge at once; keep the old content visible or hold its spaceApplying a filter; opening a product from the list
~10 sAttention stays on the task but patience drainsA looped busy indicator and words for what is happeningPaying for an order
beyond 10 sAttention leaves; people switch tabs or abandonProgress with an estimate, a way to cancel, a way to come back laterUploading a photo of a worn part to the part finder

The limits on one scale

The limits on one scaleFrom 100 ms on, each limit is ten times the one before, so the bands are orders of magnitude apart: missing the 100 ms band puts a tap in the 1 s band, not a little behind.1 ms10 ms100 ms1 s10 sFrame at 120 HzFrame at 60 HzFeels instantFlow unbrokenAttention held8.3 ms16.7 ms100 ms1 s10 sLimitTime (ms)The limits on one scaleFrom 100 ms on, each limit is ten times the one before, so the bands are orders of magnitude apart: missing the 100 ms band puts a tap in the 1 s band, not a little behind.1 ms100 ms10 sFrame at 120 HzFrame at 60 HzFeels instantFlow unbrokenAttention held8.3 ms16.7 ms100 ms1 s10 sLimitTime (ms)
Logarithmic scale. The frame is drawn for a 60 Hz screen; a 120 Hz screen halves it.
Data
Limitms
Frame at 120 Hz8.3
Frame at 60 Hz16.7
Feels instant100
Flow unbroken1,000
Attention held10,000
Was this section helpful?

How it works.

Sort each interaction by what kind of thing it is, look up the limit it lives under, then decide what the screen shows while the real work runs. The last step is where perceived time and measured time part ways.

1

Three kinds of interaction

Interactions are not equal, and the person's expectation depends on what they think they did. Touching a control is direct manipulation: the rider expects the thing under her finger to react as a physical switch would, so it belongs to the 100 ms band, and anything that moves under her finger belongs to the frame. Asking for something new, a page, a filtered list, a search, is a request: she knows the shop has to go and get it, so the 1 s band applies, as long as the screen admits the request at once. Work she knows is heavy, paying, uploading, generating a report, is a job: the 10 s band applies, and past it the interface has to stop holding her hostage.

Most real interactions are a mix. Add to basket is a tap (the button must react in 100 ms) that starts a request (the basket must be right within a second or so). Writing the two halves down separately is what makes the requirement testable: "the button shows it was pressed in the next frame" and "the basket badge shows the new count within 1 s at p75" are two different promises, measured from the same tap.

Kinds of interaction and their limits

KindExamplesLimitMeasured fromMeasured to
Continuous motionScrolling, dragging a slider, swiping a gallery, an animated drawerevery frameeach input or animation tickthe next frame painted
Direct manipulationTap a button, tick a checkbox, pick a size, type in a field~100 msthe input eventthe control visibly reacting
RequestOpen a product, apply a filter, run a search, go to the next page~1 sthe input eventthe useful content painted
JobPlace an order, upload a photo, export order history~10 s and beyondthe input eventthe outcome shown or handed to a notification
2

The frame inside the 100 ms

The screen only changes when the browser paints a frame. At 60 Hz a frame comes every 1000 / 60 = 16.7 ms; on the 120 Hz screens now common on phones it is 1000 / 120 = 8.3 ms. The browser needs part of each frame for style, layout and paint, so MDN and RAIL both suggest your own work per frame stays near 10 ms on a 60 Hz screen. How the event loop picks when to paint is taught in the browser-rendering topic on the frame and the event loop; here it matters for two reasons.

First, the 100 ms limit is about six frames at 60 Hz. RAIL turns that into a rule of thumb: finish handling input within 50 ms, so that, together with whatever task was already running when the tap arrived, the change still reaches the screen inside 100 ms. Second, while something moves, the budget is the frame itself, not 100 ms: a drag that lags by three frames feels like dragging through mud even though 50 ms sounds fast.

How many frames fit inside 100 ms

60 Hz screen
  1. 1, width 17, Frame with nothing new to show
  2. 2, width 17, Frame with nothing new to show
  3. 3, width 17, Frame with nothing new to show
  4. 4, width 17, Frame with nothing new to show
  5. 5, width 16, Frame with nothing new to show
  6. 6, width 16, Frame with nothing new to show
  • tap, pointer at 0
120 Hz screen
  1. width 8, Frame with nothing new to show
  2. width 8, Frame with nothing new to show
  3. width 9, Frame with nothing new to show
  4. width 8, Frame with nothing new to show
  5. width 8, Frame with nothing new to show
  6. width 9, Frame with nothing new to show
  7. width 8, Frame with nothing new to show
  8. width 8, Frame with nothing new to show
  9. width 9, Frame with nothing new to show
  10. width 8, Frame with nothing new to show
  11. width 8, Frame with nothing new to show
  12. width 9, Frame with nothing new to show
  • tap, pointer at 0
  • Frame with nothing new to show
  • Main thread busy
  • Frame that shows the response
Start

As it starts. 3 steps follow.

Each segment is one frame, its width in milliseconds (rounded so each row adds up to the 100 ms window). Step through a tap whose handler runs for 40 ms: at 60 Hz the change lands in the fourth frame, at 120 Hz in the sixth, both inside the limit. An 80 ms handler leaves almost no room for anything else.

The 100 ms limit is not INP's 200 ms

Interaction to Next Paint, the Core Web Vitals responsiveness metric, calls 200 ms or less at the 75th percentile good. That is not a contradiction. The 100 ms limit is what one person perceives on one tap; INP is a field threshold over many visits on real devices, set where most sites can reach it. A requirement can name both: design each tap to react within 100 ms on the reference phone, and accept the page as passing when field INP at p75 is 200 ms or less. The vitals topic owns the metric; this topic owns the design target.

3

What each band owes the user

Expected wait and the feedback that fits it

Expected waitFeedbackWhyAvoid
under ~100 msThe result itself, or the control's pressed stateAnything more is noise; the change is the acknowledgementA spinner that appears for two frames
~100 ms to ~1 sInline acknowledgement: a busy label on the button, the old list dimmed, a chip that appears at onceThe rider needs to know she was heard; she does not need to be told to waitClearing the screen, or an animated indicator that flashes on and off (NN/g advises none under about 1 s)
~1 s to ~10 sA looped indicator with words ("Confirming payment…"), or a skeleton that holds the shape of what is comingThe wait is noticeable; the screen must prove work is happeningA frozen screen; an indicator with no words
over ~10 sDeterminate progress (percent or steps), an estimate, Cancel, and a way to leave and be told laterIn a study NN/g cites, people shown a moving progress bar were more satisfied and waited about three times longer than people shown nothingA spinner that turns for a minute with no end in sight

Do not flash a spinner

The bands describe the expected wait, but any one request lands anywhere in its distribution: Cobbleway's filter request comes back in 250 ms on a good train stop and 2 s in a tunnel. Show a spinner the moment the request starts and the fast case gets a spinner that blinks on and off, which reads as a glitch and makes a quick answer feel slower. The usual fix is two timers. Show the indicator only if the request is still pending after a short delay, and once it is shown keep it up for a minimum time, so it never flickers. Cobbleway picks 300 ms and 500 ms; neither is a standard, both are design choices you would state and tune.

The delay applies to animated indicators only. The tap itself still owes a reaction inside 100 ms: the button's pressed state, a busy label, the filter chip appearing. A delayed spinner with no immediate acknowledgement is the worst of both, because the rider sees nothing for 300 ms and taps again.

A loading indicator that never flashes

A loading indicator that never flashes. 6 states, 8 transitions. The table below lists them.
A loading indicator that never flashes6 states, 8 transitions. The table below lists them.

tap / busy label; start 300 ms timer

answer [before 300 ms] / show result

error [before 300 ms]

300 ms timer / show indicator

answer [shown 500 ms or more]

answer [shown under 500 ms]

500 ms minimum reached / show result

error

Idle

Pending, indicator hidden

Indicator shown

Answer ready, holding indicator

Result shown

Error shown

2 steps. The usual case on a good connection. No spinner ever appears.

One request's indicator. Fast answers settle before the 300 ms delay and never show it; slow ones show it for at least 500 ms. Every state keeps the button's busy label, which appeared on the tap.

Transitions of A loading indicator that never flashes
From → ToEventGuardAction
Idle → Pending, indicator hiddentapbusy label; start 300 ms timer
Pending, indicator hidden → Result shownanswerbefore 300 msshow result
Pending, indicator hidden → Error shownerrorbefore 300 ms
Pending, indicator hidden → Indicator shown300 ms timershow indicator
Indicator shown → Result shownanswershown 500 ms or more
Indicator shown → Answer ready, holding indicatoranswershown under 500 ms
Answer ready, holding indicator → Result shown500 ms minimum reachedshow result
Indicator shown → Error shownerror
Idlestart
Pending, indicator hidden
The control already shows it was pressed
Answer ready, holding indicator
Waits out the minimum so it does not flicker
Result shownend
Error shownerror

The two timers in code

// Show `onShow` only if `work` is still pending after `delayMs`,
// and once shown keep it for at least `minVisibleMs`.
export async function withIndicator<T>(
  work: Promise<T>,
  onShow: () => void,
  onHide: () => void,
  { delayMs = 300, minVisibleMs = 500 } = {},
): Promise<T> {
  let shownAt: number | null = null;
  const timer = setTimeout(() => {
    shownAt = performance.now();
    onShow();
  }, delayMs);
  try {
    return await work;
  } finally {
    clearTimeout(timer);
    if (shownAt !== null) {
      const left = minVisibleMs - (performance.now() - shownAt);
      if (left > 0) await new Promise((r) => setTimeout(r, left));
      onHide();
    }
  }
}

// Usage on the brake-pads page: the chip and the dimming happen
// synchronously in the click handler; only the spinner is delayed.
// const results = await withIndicator(fetchPads(filters), showSpinner, hideSpinner);
4

Perceived time versus measured time

Measured time runs from the tap to the moment the work is truly done. Perceived time runs from the tap to the moment the rider believes it is done, or at least believes it is under way. The two can differ by most of a second, and the gap is a design choice, not an accident. Three levers widen it in your favour.

  • Optimistic update: show the expected result at once and reconcile when the server answers. It fits writes that almost always succeed and are cheap to undo, like a basket line or a like. The snapshot, reconcile and rollback machinery belongs to the data-retrieval topic on mutations; what matters here is that it moves a request-sized wait into the 100 ms band.
  • Placeholders that hold the shape: a skeleton, or the previous content kept on screen but dimmed. The rider sees where the answer will appear and keeps her place. A skeleton must reserve exactly the space of the real content, or the swap becomes a layout shift.
  • Progressive rendering: show the first useful part (the product title, price and photo) before the rest (reviews, related parts). The page enters the 1 s band for what matters even if the whole page takes longer.

None of these make the network faster, and none of them excuse a slow server: an optimistic basket that is wrong one time in twenty feels broken, and a skeleton shown for five seconds is still a five-second wait. They decide which band the rider experiences while the real work catches up.

One Add to basket, five ways it can look

Scenario 1 of 5: As described.

Notes
  • 6 A second add is now in flight; the basket may end up with two chains.
Timeline as a list

One Add to basket, five ways it can look: 4 lanes, from 0 ms to 1,200 ms.

  1. 0–620 ms · What the screen shows · no visible change
  2. 0 ms · Rider · taps Add to basket
  3. 20 ms · Basket code in the browser → Basket API, arriving 150 ms: POST line
  4. 100 ms · all lanes · 100 ms limit (deadline)
  5. 150–490 ms · Basket API · add line
  6. 450 ms · Rider · nothing happened? taps again (error) — A second add is now in flight; the basket may end up with two chains.
  7. 490 ms · Basket API → Basket code in the browser, arriving 620 ms: 200 basket v8
  8. 620 ms · What the screen shows · badge 1 to 2 (ok)
Cobbleway's assumed p75 on the reference phone over 4G: 130 ms to reach the basket API, 340 ms of work there, 130 ms back, plus about 20 ms in the browser before the request leaves. The first tab is the bare design: nothing changes until the answer, so she taps again. Switch tabs to see the same round trip with a busy label, a spinner on a delay, an optimistic badge, and an optimistic badge that has to be taken back.
Was this section helpful?

In practice.

Cobbleway's performance requirement starts as an inventory of interactions, each with a band, a target on the reference phone and the feedback it owes. That inventory is also the shape of a strong interview answer.

List the interactions a rider actually performs on the three pages that matter, home, category and product, plus checkout. For each, write the band, the target and how it is measured (at p75 on the reference device, a mid-range Android phone on 4G, chosen in the device topic), and the feedback the screen shows inside the 100 ms limit whatever the network does. The page-load numbers themselves are Core Web Vitals and live in that topic; this table covers what happens after the page is up.

No.AreaRequirementTarget / measure
01Any tap on a controlThe control shows it was pressed in the next frame, and its result (or an acknowledgement) is painted within 100 ms.
pressed state next framevisible response ≤ 100 msfield INP ≤ 200 ms at p75
Reference phone, 60 Hz. INP is the field check; the 100 ms is the design target per control.
02Add to basket (product page)The basket badge shows the new count at once, optimistically; the server confirms within 1 s at p75; a failed add rolls back with a message.
badge next frameconfirm ≤ 1 s at p75rollback rate ≤ 0.5%
Assumed round trip 620 ms at p75 on 4G. A rollback rate above the limit means the bet is wrong; switch to a busy label.
03Filter change (category page)The chip and the result count placeholder appear at once; the old results stay, dimmed; new results replace them within 1 s at p75.
chip next frameresults ≤ 1 s at p75no layout shift on swap
Assumed p75 800 ms. Indicator only after 300 ms, then for at least 500 ms.
04Open a product from the listTitle, price and main photo are painted within 1 s at p75 for a soft navigation; reviews and related parts may follow.
first useful paint ≤ 1 s at p75skeleton holds layout
The first visit to the product page is a page load, governed by LCP in the vitals topic.
05Place order (checkout)The button turns busy at once and cannot be pressed twice; a worded indicator appears after 1 s; the outcome is shown within 10 s at p95.
busy state next frameindicator after 1 soutcome ≤ 10 s at p95
Assumed p75 3.2 s (payment provider included). Never optimistic.
06Photo part finderUpload shows determinate progress with a Cancel; the rider may leave the page and is told when matches are ready.
progress updates ≥ 1 per secondcancel always availableresult kept for 24 h
Assumed 14 s on 4G for a 2.5 MB photo plus matching; the only interaction expected to pass 10 s.

Add to basket, as the rider sees it

One item already in the basket. The rider has picked 118 links.

Before the tap. Store “Cobbleway”, showing the product view. Cart button “Basket” with badge 1 (its accessible name says “Cart, 1 item”, not the number alone). Product (showing) “Trailhead 11-speed chain” by “Cobbleway Workshop range”: rating 4.6 (“318 reviews”); price £27.50. Gallery: image 1 of 4 (“Trailhead chain, coiled, side view”); thumbnails beside it. Length (tiles, a radio group): 114 links, 118 links (selected), 126 links. Stock: “In stock”. Quantity 1 (stepper “Quantity”). Buttons: “Add to basket”, “Buy now”. Delivery: “Dispatched today if ordered by 3 pm”. Folded sections: Fit and compatibility, Reviews, Delivery and returns.

  1. Basket badge: optimistic, then the server's count
  2. Button: pressed and busy within one frame

A filter change inside the 1 s band

All 64 pad sets, sorted by popularity.

Loaded. Grid, 3 columns: 6 items. Search: “Disc brake pads”. Sort: Sort: Popular. Count: 64 results. Facet Compound (check): Resin (24), Sintered (28), Semi-metallic (12) Facet Price (range): range £8 to £45 1. Trailhawk resin pads · £24.90 [image 1:1] badges: Best seller. 2. Quietstop resin pads · £14.00 [image 1:1] 3. Ridgeline sintered pads · £29.50 [image 1:1] 4. Longhaul sintered pads · £18.50 [image 1:1] 5. Mudlark resin pads · £18.00 [image 1:1] 6. Fellrunner semi-metallic pads · £32.00 [image 1:1] Footer: page links.

  1. Acknowledges the tap within 100 ms
  2. Says the list is updating

Cobbleway's interactions against their limits

  • Measured p75 (assumed)
  • Band limit
Cobbleway's interactions against their limitsAdd to basket and the filter both measure well inside 1 s, but only the optimistic add feels instant; Place order is slow enough to need words, and the part finder is the only one past 10 s.10 ms100 ms1 s10 s100 sTap feedbackAdd to basketFilter changeOpen productPlace orderPart finder40 ms620 ms800 ms900 ms3.2 s14 s100 ms1 s1 s1 s10 s10 sInteractionTime from the tap (ms)Cobbleway's interactions against their limitsAdd to basket and the filter both measure well inside 1 s, but only the optimistic add feels instant; Place order is slow enough to need words, and the part finder is the only one past 10 s.10 ms100 ms1 s10 s100 sTap feedbackAdd to basketFilter changeOpen productPlace orderPart finder40 ms620 ms800 ms900 ms3.2 s14 s100 ms1 s1 s1 s10 s10 sInteractionTime from the tap (ms)
Assumed p75 measured time on the reference phone (blue) against the band's limit (gray). Log scale. The optimistic Add to basket is perceived in one frame even though it measures 620 ms.
Data
InteractionMeasured p75 (assumed) (ms)Band limit (ms)
Tap feedback40100
Add to basket6201,000
Filter change8001,000
Open product9001,000
Place order3,20010,000
Part finder14,00010,000
01
How Place order should feel
Chosen:Pessimistic with instant acknowledgement (busy at once, words after 1 s)
  • Pro:Never shows an order that does not exist
  • Pro:The busy state blocks a second press
  • Pro:Honest words ("Confirming payment…") cover the 1-10 s wait
  • Pro:Pairs naturally with one idempotency key per attempt
Downside we accept:
  • Con:The rider waits the full 3.2 s at p75
  • Con:Needs a clear story for timeouts (did it go through?)
Ruled out:Optimistic (show the confirmation page at once)

A declined card or a sold-out part turns a confirmation into a retraction; Money and stock are not cheap to undo; A rider who closes the tab believes she has ordered

Ruled out:No feedback until the server answers

Nothing happens for seconds; so riders press again; Looks frozen on a slow connection

Saying it in an interview

Q1
RequirementsThe interviewer asks: what are your performance requirements for this shop?
Name the reference device first, then go interaction by interaction: every tap reacts within 100 ms; add to basket is optimistic so the badge updates in the next frame and confirms within a second; filters keep the old results dimmed and swap within a second at p75; place order is never optimistic, busy at once, with words after a second. Page loads go to the Core Web Vitals at p75. Then say how each is measured.
Q2
Deep diveWhy not show a spinner on every request?
Most requests land under a second, and a spinner that blinks on and off reads as a glitch. Acknowledge every tap immediately with the control's own state, show an animated indicator only after a short delay (300 ms here), and keep it up for a minimum (500 ms) once shown.
Q3
Trade-offWhen would you not use an optimistic update?
When the write often fails, or is expensive to undo: payments, stock reservations, anything that sends a message to another person. Also when the server decides the result, like a price after a promo code. There a fast, honest busy state beats a fast guess.
Q4
Long workThe photo part finder takes 14 seconds. What does the UI do?
It is past the 10 s limit, so the rider will look away. Show determinate progress for the upload, then a step label for matching, give her Cancel, and let her keep browsing with a notification when the matches are ready.
Was this section helpful?

Trade-offs.

Every lever that shrinks perceived time borrows against something, usually honesty, layout stability or code. These are the ways it goes wrong, and the choices worth making on purpose.

Where perceived-speed tricks backfire

Optimistic updates that are often wrong
Each rollback is a moment the shop told the rider something untrue. Track the rollback rate per interaction (Cobbleway's ceiling for Add to basket is 0.5%) and fall back to a busy label when it climbs. Undo also costs code: a snapshot, a reconcile path and a message, all in the mutations topic.
Skeletons that move the page
A skeleton card of the wrong height turns the swap into a layout shift, which the rider feels as a jump and CLS records. Size placeholders from the real component, and reserve space for what loads late, as the layout-stability topic shows.
Progress bars that lie
A bar that races to 90% and then sits there teaches people to distrust every bar. Tie determinate progress to real units (bytes uploaded, steps done); when there is no real measure, use a looped indicator and words.
Spinners too early or too late
Too early and fast answers flash a spinner; too late and slow answers look frozen. Acknowledge every tap at once with the control's own state, delay only the animated indicator, and hold it for a minimum once shown.
Clearing what was on screen
Blanking the grid while a filter loads turns a 1 s request into a feeling of starting over. Keep the old content, dimmed and inert, until the new content is ready to replace it in one step.
One number for the whole page
"Under 2 s" for the product page says nothing about the tap that freezes for 400 ms. Requirements per interaction catch what a page-level number averages away, and the vitals cover the page itself.
01
What fills the space while the category grid loads
Chosen:Keep the previous results, dimmed, and swap in one step
  • Pro:The rider keeps her place and context
  • Pro:No layout change until the real content is ready
  • Pro:Works for every filter and sort change after the first view
Downside we accept:
  • Con:Nothing to dim on a first visit
  • Con:Dimmed content must be inert so taps on stale items do not go astray
Ruled out:Skeleton cards

Throw away content the rider was reading; Shift the layout if sized wrong; A skeleton shown for long is just a slower-looking wait

Ruled out:A centred spinner on an empty area

Says nothing about what is coming; Collapses the layout and then expands it; Flashes on fast requests

Writing the requirement, badly and well

WeakWhy it failsStronger
"The app feels fast."Nobody can fail it"Every control shows it was pressed in the next frame and reacts within 100 ms on the reference phone."
"API responses under 300 ms."Measures the server, not what the rider sees, and says nothing about the screen while waiting"Filter results replace the dimmed list within 1 s at p75 on the reference phone over 4G."
"Show a spinner while loading."Flashes on fast requests and hides the band"Acknowledge at once; an indicator only after 300 ms, held for at least 500 ms."
"Checkout under 1 s."Unreachable with a payment provider in the path; invites an optimistic hack"Busy at once, worded progress after 1 s, outcome within 10 s at p95; never optimistic."
This topic
The frame, 100 ms, 1 s and 10 s limits and where they come from
Giving each interaction a band, a target and the feedback it owes
Perceived versus measured time, and when to delay an indicator
Elsewhere
LCP, INP and CLS thresholds and the p75 ruleCore Web Vitals as requirements
Choosing the reference phone and networkDesigning for the device you don't own
Turning targets into limits a build can failPerformance budgets
Snapshot, reconcile and rollback for optimistic writesWriting data (data-retrieval)
Breaking up long tasks so taps stay under the limitLong tasks and responsive input
How the browser schedules framesThe frame and the event loop
Percentiles and tail latencyLatency and percentiles
Was this section helpful?
Builds on this
Core Web Vitals as requirements
Read next