A premium website is not the one with the most effects. It is the one that feels instant on a mid-range phone and still looks extraordinary on a studio monitor. My own site runs a GPU fluid simulation, a procedural deep-space nebula and hero smoke on Next.js 16 and React 19, so balancing visual ambition against Core Web Vitals is something I deal with in practice, not in theory.
What "fast" means in 2026
Google's Core Web Vitals are the closest thing the web has to a shared definition of fast. A page passes when at least 75% of real visits meet all three "good" thresholds:
- Largest Contentful Paint (LCP): 2.5 seconds or less. How quickly the main content appears.
- Interaction to Next Paint (INP): 200 milliseconds or less. How quickly the page responds to taps, clicks and key presses. INP replaced First Input Delay as a Core Web Vital in March 2024.
- Cumulative Layout Shift (CLS): 0.1 or less. How much the layout jumps around while people use it.
Two things trip people up. First, these are judged on field data from real Chrome users (the Chrome UX Report), not on a Lighthouse run. Lighthouse is a lab tool: great for debugging, but it cannot measure INP and uses Total Blocking Time as a stand-in. A perfect Lighthouse score with failing field data is still failing. Second, the 75th percentile means your slowest quarter of visitors decides the result.
PageSpeed Insights shows field and lab data side by side, Search Console groups failing URLs, and Next.js's useReportWebVitals hook sends metrics to your own analytics.
Ship less JavaScript with Server Components
In the App Router, every component is a Server Component unless its file starts with 'use client'. Server Components render to HTML and send no component code to the browser. That makes 'use client' the most important performance decision in a Next.js codebase, because everything a client file imports becomes part of the client bundle.
The rules I follow:
- Push the client boundary down to the leaves. A product page should be a Server Component; only the quantity picker and the add-to-cart button need to be client code.
- Pass server-rendered content through as children. A client wrapper such as an animated reveal, a modal or a tab set can receive Server Components as children, and they stay server-rendered.
- Keep heavy work on the server. Markdown parsing, syntax highlighting, date formatting and data shaping belong in Server Components, where their libraries never reach the browser.
// Reveal.tsx: a tiny client wrapper
'use client';
import { motion } from 'framer-motion';
export function Reveal({ children }: { children: React.ReactNode }) {
return (
<motion.div initial={{ y: 24 }} animate={{ y: 0 }}>
{children}
</motion.div>
);
}
// page.tsx: stays a Server Component
import { Reveal } from './Reveal';
import { Pricing } from './Pricing'; // rendered on the server, no client JS
export default function Page() {
return (
<Reveal>
<Pricing />
</Reveal>
);
}
Notice that the reveal animates position, not opacity. That detail matters for LCP.
LCP: get the hero on screen fast
Start by finding the LCP element; the Performance panel in Chrome DevTools labels it.
- Load the hero image eagerly and at high priority.
next/imagelazy-loads by default, which is right for everything except the hero. In Next.js 16 the oldpriorityprop is deprecated, and the docs recommendloading="eager"orfetchPriority="high"for the LCP image, with the newpreloadprop kept for special cases. - Always set
sizeson responsive orfillimages. Without it, the browser assumes the image spans the whole viewport and may download a far larger file than the layout needs. - Do not fade in your LCP element. Chrome ignores elements at zero opacity when choosing LCP candidates, so a headline that starts at opacity 0 only counts once its animation begins, which is often after hydration. Animate transform instead, or start from a visible state.
- Keep the LCP in HTML, not in a canvas. A WebGL canvas is not an LCP candidate and should never delay one. On my site, the smoke behind the hero is loaded separately, after the hero content is already on the page.
- Load fonts with
next/font. It self-hosts the files and generates a size-adjusted fallback so the font swap shifts less. Ship only the weights you use. - Cut server time. A prerendered page served from a CDN beats any amount of front-end tuning. With Cache Components enabled in Next.js 16, a route can serve a static shell immediately and stream its dynamic parts as they resolve, so one personalised widget no longer forces the whole page to render per request.
INP: keep the main thread free
INP looks at interactions throughout the visit and reports roughly the worst one. Poor INP almost always means the main thread was busy when the user acted: hydrating a large bundle, running a heavy event handler or re-rendering a big component tree.
- Hydrate less. Every kilobyte of client JavaScript is downloaded, parsed, executed and hydrated. Moving code to Server Components is the biggest INP fix most sites will ever get.
- Mark non-urgent updates as transitions. Wrapping an expensive state update in
startTransitionlets React keep the keystroke or click that caused it responsive, for example when filtering a long list. - Defer third-party scripts. Load chat widgets, heatmaps and tag managers with
next/scriptand thelazyOnloadstrategy, or only after consent and interaction. - Virtualise long lists, and never read layout values such as
offsetHeightinside scroll handlers. - Let the compiler memoise. Next.js 16 supports the React Compiler, which removes most hand-written
useMemoanduseCallbackbusywork.
To find the culprit, record a slow interaction in the DevTools Performance panel with CPU throttling enabled and look for long tasks. In the field, the Long Animation Frames API can attribute slow frames to specific scripts.
CLS: reserve space for everything
- Give every image, video and embed explicit dimensions or an
aspect-ratio.next/imagerequires width and height, orfillinside a sized container, for exactly this reason. - Show cookie banners and promos as overlays, never by pushing content down.
- Make skeletons the same size as the content they replace.
- Animate with
transform. Transforms never cause layout shift; animatingtop,heightormargincan.
Heavy visuals without the heavy penalty
This is where "premium" sites usually lose. Five rules keep ambition and speed compatible:
- Keep 3D libraries out of the initial bundle. Load WebGL effects with
next/dynamicand server rendering disabled, from a Client Component, so the page renders and becomes interactive first. Every Three.js effect on my site is lazy-loaded this way. - Make the effect decorative. If the canvas fails to start, the content must still work.
- Adapt to the device. My site runs an adaptive quality governor that measures real frame rates and lowers simulation quality only when the page genuinely stalls. On devices that still struggle, it switches effects off one at a time instead of degrading everything at once.
- Respect reduced-motion preferences. Visitors who ask their OS to reduce motion should get a calm version of the page, not just a slower one.
- Clean up. Dispose GPU resources and release WebGL contexts when a canvas unmounts.
A quick audit checklist
- Check field data in PageSpeed Insights or Search Console before touching code.
- Confirm the mobile LCP element is HTML text or an eagerly loaded image.
- Run the bundle analyzer and list every large library in a client chunk.
- Move
'use client'down the tree until only interactive leaves remain. - Defer or remove every third-party script you can.
- Record your most common interactions with CPU throttling and fix the long tasks.
- Reserve space for every image, embed and late-arriving element.
- Re-check field data after four weeks; the Chrome UX Report is a rolling 28-day window.
Key takeaways
- Core Web Vitals are judged on real-user data at the 75th percentile: LCP of 2.5 s or less, INP of 200 ms or less, CLS of 0.1 or less.
- Server Components are the biggest single lever; keep
'use client'at the leaves of the tree. - In Next.js 16, use
loading="eager"orfetchPriority="high"for the hero image instead of the deprecatedpriorityprop. - Never fade in the LCP element, and never make visitors wait on a canvas.
- Rich visuals are compatible with good scores when they are lazy-loaded, decorative and adaptive.
If your Next.js site is failing Core Web Vitals, my Core Web Vitals fixes start from field data rather than guesswork, and the same approach drives my React performance optimization work for dashboards and apps.
Planning a project? Book a free discovery call — I'll tell you honestly what it needs.

