"No less than 85 on mobile and 95 on desktop" appears in briefs more and more often. The problem is that by the time anyone remembers it, the architecture is already chosen, and pure optimisation will not close the gap.
Below are the decisions that produce almost all of the result, in order of impact.
1. Fonts are the most expensive thing that is easy to fix
Loading through an @import to an external font service means an extra DNS
lookup, a TLS handshake, a CSS download, and only then the font file itself —
all before the first text is painted.
The right answer is self-hosting with preload. In Next.js that is next/font:
the files sit next to the application, preload with the page, and no external
request remains.
A note on Cyrillic: if the site is in Russian but only the Latin subset is
configured, the browser will fetch a second file for the first letter of the
heading. Check that cyrillic is in the subset list.
2. Server rendering instead of client rendering
A site that ships an empty <div id="root"> and assembles content in the
browser pays twice: the user waits for JavaScript, and the crawler receives an
empty page it must render itself.
Server rendering makes the first screen immediate and the markup indexable straight away.
3. Less JavaScript in the browser
The usual set — animation library, carousel, icon pack, class utility — easily reaches 150–200 KB compressed, before any of your own code.
What we do instead:
- entrance animations on CSS transitions and
IntersectionObserverrather than an animation library; - the carousel on native
scroll-snap, which swipes on a phone out of the box; - the accordion on native
<details>, which is also correct for accessibility and leaves the answers in the markup for crawlers; - icons as inline SVG instead of a package containing a thousand glyphs.
Individually each looks like a detail. Together they are tens of points on a weak phone.
4. Reserve space for anything that loads later
Layout shift (CLS) is penalised separately and is obvious to the user: they aim at a button and it moves. Images need dimensions, embedded widgets need a fixed container height, and fonts need a correct fallback so the swap does not move lines.
5. Defer everything not needed for the first screen
Analytics counters, maps and support chats all load after the page becomes
interactive. The map on a contacts page should not load until the user reaches
it: loading="lazy" on the iframe saves more than any minification.
What not to do
Do not chase a hundred at the cost of the product. Removing every image and animation is a way to get a fast site nobody needs. The goal is not the score; it is that a user on a weak phone underground reaches the form and submits it.
The metric is an indicator that the engineering decisions were right — not the objective itself.
- #performance
- #seo
- #core-web-vitals