All articles
169 articles · updated weekly See our Tools
All articles
Tutorials

How to reduce a website's initial load time

Practical techniques to cut a website's initial load time, from TTFB to JavaScript, with the real win each one delivers.

How to reduce a website's initial load time
COVER · Tutorials

A visitor clicks your link, stares at a blank screen for two or three seconds, and closes the tab before anything appears. You never knew they were there. That gap between the click and the first useful pixel on screen is where most sites bleed users — and money. It isn't "slow" in some vague sense: it's the initial load, the first technical impression, that decides whether someone stays or vanishes.

The good news is that this gap is measurable and you can attack it in pieces. Each technique below bites off a specific chunk of time, and you can know roughly what each one buys you before spending an afternoon on it. We'll follow the path a page travels: from the server to the moment the browser paints the first thing that matters.

Start with server response time (TTFB)

Before any image or script, the browser asks the server a question and waits for the answer. That delay is TTFB (Time to First Byte). If it's already 800ms, you started the race 800ms behind everyone, and no front-end trick recovers it.

The usual culprits for high TTFB are uncached database queries, PHP/Node work on every request, and a server geographically far from the user. The fixes, in order of impact:

  • Full-page caching. If the page doesn't change per request, serve a pre-built version. In WordPress that's a caching plugin; in a custom app, it's Redis or file cache in front of the render.
  • A CDN. Put the bytes near the user. A server in New York answering someone in Sydney pays ~200ms just for the round trip at the speed of light.
  • HTTP keep-alive and compression. Brotli or gzip on the server shrinks transferred HTML by 60-80%.

Typical win: cutting TTFB from 800ms to 200ms is cheaper than any front-end rewrite and improves every page at once.

Stop blocking the render with CSS and JavaScript

Once the HTML arrives, the browser hits <link> CSS and <script> tags in the <head>. By default, CSS blocks rendering and synchronous scripts block HTML parsing. The browser stops everything and waits to download those files before painting anything.

The fix has three fronts:

<!-- Script that doesn't need to run before the HTML is ready -->
<script src="/app.js" defer></script>

<!-- Critical CSS inline in the head, the rest loaded later -->
<style>/* only the CSS for what appears above the fold */</style>
<link rel="preload" href="/full.css" as="style" onload="this.rel='stylesheet'">

defer lets the script download in parallel and run only after the HTML is fully parsed. "Critical CSS" is the most overlooked trick: you inline into the HTML only the styles for content visible without scrolling, and push the rest of the stylesheet to later. The browser paints the first screen without waiting on 40KB of CSS for components that aren't even visible yet.

Ship less JavaScript — and ship it later

JavaScript is the most expensive resource in a load, because the browser doesn't just download it: it parses, compiles, and executes it, all on the main thread — the same one that paints the screen. 300KB of JS costs far more than 300KB of image.

Three measures that actually pay off:

  • Code splitting. Break the bundle per route. The landing page doesn't need the checkout code. Modern frameworks do this via dynamic import().
  • Tree shaking. Import import { debounce } from 'lodash-es' instead of the whole package. Bundlers strip what isn't used — as long as you import in a way they can statically analyze.
  • Defer third-party scripts. Analytics tags, chat widgets, ad pixels: none of it needs to run in the first second. Load them on first user interaction (scroll, click) or with a few-second timeout as fallback. They compete for the main thread precisely when it's busiest.

That last point connects directly to the metrics Google uses to rank: main-thread blocking time feeds INP, one of the three Core Web Vitals: LCP, INP and CLS. Where this guide focuses on how to cut the load, that sibling post explains which numbers Google measures and the thresholds for each — worth reading next to close the loop.

Optimize images, the heaviest part of most pages

On content sites, images are often 50% or more of the bytes. The classic mistake is serving a 2500px JPG into a 600px slot on screen.

<img
  src="hero-800.webp"
  srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w"
  sizes="(max-width: 600px) 400px, 800px"
  width="800" height="450"
  loading="lazy"
  decoding="async"
  alt="Image description">

What each piece does:

  • WebP/AVIF instead of JPG/PNG: 25-50% smaller at the same quality.
  • srcset + sizes: the browser picks the right resolution for the screen. A phone doesn't download the desktop version.
  • Explicit width and height: reserve the space and avoid the layout "jump" (CLS) when the image loads.
  • loading="lazy": below-the-fold images only download when the user scrolls near them. Caution: do not lazy-load the main above-the-fold image, that delays LCP.

Preconnect and preload what's critical

The browser discovers resources by reading the HTML top to bottom. You can give it early hints:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preload" href="/hero.webp" as="image">
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>

preconnect resolves DNS and opens the TLS connection to external domains before you need them. preload forces the immediate download of a critical resource the browser would otherwise discover late — classic for the LCP image and for fonts, which otherwise aren't requested until the CSS is parsed.

On fonts: use font-display: swap so text shows in a system font while yours loads, instead of leaving invisible text on the page.

Frequently asked questions

What is a good initial load time?

Google's practical benchmark is LCP (largest visible element) under 2.5 seconds for 75% of visits. For TTFB, aim under 800ms, ideally near 200ms. There's no single magic number — what matters is staying within those thresholds on your audience's real connection and device, not on your fiber-connected laptop.

How do I reduce initial server response time?

Attack in this order: full-page caching (removes reprocessing), a CDN (cuts physical distance), Brotli/gzip compression, and database query tuning. On slow shared hosting, sometimes the biggest improvement is simply moving to a better plan or provider — bad TTFB from an oversaturated server can't be fixed in code.

Does lazy loading really speed up a page?

Yes, with a caveat. It defers off-screen images and iframes, cutting the bytes of the initial load. But applying lazy loading to the main above-the-fold image has the opposite effect: it delays LCP, because the browser would deprioritize the very element that defines the metric. Lazy below the fold, eager above it.

Does minifying CSS and JS make a real difference?

It does, but it's the lowest rung. Minifying and compressing saves a few KB; the big win is in sending less code (code splitting, dropping dependencies) and in not blocking the render. Always minify — it's automatic in the build — but don't expect it alone to fix a 2MB-of-JavaScript page.

What to take from this guide

Initial load time isn't a number you improve "overall": it's a chain of stages, and each one has a nameable culprit. Start by measuring TTFB — if it's high, nothing on the front end matters until you fix it. Then unblock the render (critical CSS, defer), ship less JavaScript and defer the third-party stuff, serve images in the right size and format, and preload only what's genuinely critical.

Order matters because the wins are unequal: shaving 600ms off TTFB with caching beats saving 5KB by minifying. Measure before and after each change, on a real mobile connection, and always go after the biggest bottleneck first. That's how a three-second blank screen becomes half a second — and the visitor stays.

RD
Author
Rafael Duarte
Backend developer with a background in fintech and B2B SaaS — worked on teams that scaled APIs from zero to millions of requests. Carries enough production scars to hold strong opinions on tools, patterns and architecture decisions. Not an academic: he read the UUID RFC when he had to choose between v4 and v7 for a write-heavy table.
View profile