Building for Performance
Best practices for creating lightning-fast web applications without compromising features.
Written byGrzegorz Kaczmarek — Founder, GKD AgencyJump to section
Performance is not a feature you add at the end. By the time a slow application ships, the architectural decisions that caused the slowness are often expensive to reverse. Speed needs to be designed in from the start — but that doesn't mean sacrificing functionality or developer experience to get there.
Here are the practices that consistently produce fast, maintainable web applications.
#Understand What You're Optimising For
Core Web Vitals — Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and Interaction to Next Paint (INP) — are the metrics that matter most for perceived performance and search ranking. Before optimising anything, run Lighthouse or PageSpeed Insights and identify which metric is your biggest problem. Trying to improve everything at once usually improves nothing.
LCP is almost always the highest-impact target. It measures when the largest visible element loads — typically a hero image or above-the-fold heading. Improving LCP often has the biggest effect on how fast a page feels.
#Serve Only What the Page Needs
Modern JavaScript bundlers are powerful, but they're permissive by default. Left unchecked, a single page can ship hundreds of kilobytes of JavaScript that runs before the user can interact with anything.
Code splitting is the most effective remedy. In Next.js, dynamic imports (next/dynamic) let you defer heavy components — modals, rich text editors, data visualisation — until the user actually needs them. The initial load stays lean; the rest loads on demand.
Tree shaking removes unused exports from your bundle, but only works if your dependencies support ES modules and you import selectively. Replace import _ from 'lodash' with import debounce from 'lodash/debounce' and watch bundle size drop.
#Images Are Usually the Biggest Culprit
Unoptimised images account for the majority of excess page weight on most marketing sites. The fix is systematic:
- Use Next.js
<Image>for automatic format conversion (WebP/AVIF), responsive sizing, and lazy loading - Specify
widthandheight(or usefillwith a sized container) to prevent layout shift - Add
priorityto above-the-fold images so they preload rather than lazy-load - Store originals at 2× the maximum display size and let the CDN serve appropriate sizes
A hero image that arrives as a 3 MB PNG and becomes a 120 KB AVIF is worth more performance budget than any amount of JavaScript optimisation.
#Cache Aggressively, Invalidate Precisely
Static assets — fonts, icons, versioned JS and CSS — should carry long-lived Cache-Control headers (max-age=31536000, immutable). Content that changes — API responses, personalised data — needs shorter TTLs or stale-while-revalidate strategies.
In Next.js App Router, fetch calls accept cache and revalidate options that integrate with the built-in data cache. A revalidate: 3600 on a CMS query means the page rebuilds from fresh data every hour without a full deployment.
#Measure, Don't Guess
Every performance claim should be backed by a measurement. Chrome DevTools' Performance panel shows exactly where time is spent. The Network tab shows what's being loaded and in what order. Real User Monitoring tools like Vercel Analytics show what actual users experience — which often differs from lab measurements.
Set a performance budget before you start: for example, LCP under 2.5 s, total JavaScript under 200 KB compressed. Treat a budget breach the same way you'd treat a failing test — fix it before it ships.
#The Mindset Shift
The teams that ship fast applications don't have a performance phase — they have performance awareness embedded in every decision. When a new dependency is added, someone asks how much it weighs. When a new page is built, someone checks its LCP. Small habitual checks prevent the slow accumulation of debt that eventually demands a dedicated performance sprint to repay.