How to Hit a 100 PageSpeed Score on a Real Client Website (Not Just a Demo)

How to Hit a 100 PageSpeed Score on a Real Client Website (Not Just a Demo)

# nextjs# performance# seo# webdev
How to Hit a 100 PageSpeed Score on a Real Client Website (Not Just a Demo)Christopher Bailey

Anyone can score 100 on a blank HTML page. But what about a real business site — with images, custom...

Anyone can score 100 on a blank HTML page. But what about a real business site — with images, custom fonts, animations, third-party scripts, and a client who wants a hero video? That's where most agencies tap out.

At OceanWave Web, we build hand-coded Next.js websites for small businesses, and a perfect (or near-perfect) PageSpeed score isn't a bonus — it's the baseline. Here's exactly how we do it, with the real techniques we use on every project.

Why PageSpeed Actually Matters (Beyond the Bragging Rights)

Before the how, let's quickly address the why:

SEO: Google uses Core Web Vitals as a ranking signal. A slow site is a penalized site.
Conversions: A 1-second delay in load time can reduce conversions by up to 7%.
User trust: A fast site feels professional. A slow one, even a beautiful one, feels broken.

For small businesses competing against big brands, performance is a real differentiator.

The Stack We Use

We build on Next.js (App Router), deployed on Vercel, with hand-written CSS or Tailwind. No page builders, no WordPress, no theme bloat. This matters because half the battle is starting with clean code.

  1. Images: The Biggest Win

Images are almost always the #1 culprit behind slow scores. Here's the full playbook:

Use next/image for Everything

Next.js's component handles lazy loading, responsive sizing, and WebP/AVIF conversion automatically.

jsximport Image from 'next/image'

src="/hero.jpg"
alt="Hero image"
width={1200}
height={630}
priority // Only for above-the-fold images
sizes="(max-width: 768px) 100vw, 1200px"
/>

Use priority only on your LCP (Largest Contentful Paint) image — typically the hero. Adding it everywhere defeats the purpose.

Compress Before You Even Upload

Even WebP has limits. We run all client assets through Squoosh or sharp before they ever hit the repo. Target:

Hero images: under 200KB
Card/thumbnail images: under 80KB
Icons: use SVG, not PNG

Avoid Layout Shift with Explicit Dimensions

Always set width and height on every image. This lets the browser reserve space before the image loads, avoiding Cumulative Layout Shift (CLS).

  1. Fonts: The Silent Killer

Google Fonts are convenient but expensive. Here's how we handle typography without paying the performance tax:

Self-Host Your Fonts

js// app/layout.js
import localFont from 'next/font/local'

const myFont = localFont({
src: './fonts/MyFont.woff2',
display: 'swap',
variable: '--font-my-font',
})

With next/font, fonts are:

Self-hosted at build time (no external request)
Preloaded automatically
Injected with font-display: swap to prevent invisible text

Limit Font Weights

Every font weight is an extra file. If you only need 400 and 700, don't load 300, 500, and 900 too. Audit your design and cut ruthlessly.

  1. JavaScript: Ship Less, Load Smarter

Code-Split with Dynamic Imports

Don't load components the user hasn't seen yet.

jsximport dynamic from 'next/dynamic'

const HeavyModal = dynamic(() => import('@/components/HeavyModal'), {
loading: () =>

Loading...

,
})

This is especially useful for modals, chat widgets, map embeds, and anything "below the fold."

Audit Your Bundle

Run next build and check the output. Better yet, use bundlephobia.com before installing any npm package. A single unoptimized dependency can blow up your score.

Defer Third-Party Scripts

Analytics, chat widgets, and marketing tools are notorious score-killers. Use Next.js's component with the right strategy:</p> <p>jsximport Script from &#39;next/script&#39;</p> <p>// Load after page is interactive<br> <Script src="https://analytics.example.com/script.js" strategy="afterInteractive" /></p> <p>// Load after browser is idle<br> <Script src="https://widget.example.com/widget.js" strategy="lazyOnload" /></p> <p>Never use strategy=&quot;beforeInteractive&quot; unless the script is 100% critical (it almost never is).</p> <ol> <li>CSS: Keep It Lean</li> </ol> <p>Avoid Render-Blocking CSS</p> <p>All your CSS should either be:</p> <p>Inlined (handled automatically by Next.js for critical styles)<br> Loaded asynchronously for non-critical styles</p> <p>Purge Unused Styles</p> <p>If you&#39;re using Tailwind, enable the content purge config properly:</p> <p>js// tailwind.config.js<br> module.exports = {<br> content: [<br> &#39;./app/<strong>/*.{js,ts,jsx,tsx}&#39;,<br> &#39;./components/</strong>/*.{js,ts,jsx,tsx}&#39;,<br> ],<br> }</p> <p>This removes unused utility classes from your production build. A typical Tailwind purge goes from 3.5MB to under 15KB.</p> <ol> <li>Core Web Vitals: The Actual Metrics</li> </ol> <p>PageSpeed is built on three Core Web Vitals. You need all three to be green:</p> <p>MetricWhat It MeasuresGood ThresholdLCP (Largest Contentful Paint)Load time of main content&lt; 2.5sINP (Interaction to Next Paint)Response time to user input&lt; 200msCLS (Cumulative Layout Shift)Visual stability&lt; 0.1</p> <p>Fix LCP</p> <p>Use priority on your hero image<br> Preconnect to critical origins: <link rel="preconnect" href="https://fonts.gstatic.com" /><br> Make your server respond fast (Vercel edge network helps here)</p> <p>Fix INP (replaced FID in 2024)</p> <p>Minimize long JavaScript tasks<br> Break up heavy computations with setTimeout or Web Workers<br> Use React&#39;s useTransition for non-urgent state updates</p> <p>jsxconst [isPending, startTransition] = useTransition()</p> <p>startTransition(() =&gt; {<br> setFilteredResults(heavyFilter(data))<br> })</p> <p>Fix CLS</p> <p>Always set image dimensions<br> Reserve space for ads/embeds with explicit height containers<br> Avoid injecting content above existing content dynamically</p> <ol> <li>Server-Side Rendering + Caching</li> </ol> <p>Next.js App Router gives you powerful caching primitives out of the box.</p> <p>js// Cache a data fetch for 1 hour<br> const data = await fetch(&#39;<a href="https://api.example.com/data">https://api.example.com/data</a>&#39;, {<br> next: { revalidate: 3600 }<br> })</p> <p>For truly static content (like a small business homepage), use export const dynamic = &#39;force-static&#39; to pre-render at build time. Zero server compute, near-instant response.</p> <ol> <li>The Audit Workflow We Use on Every Project</li> </ol> <p>Before launch, we run through this checklist:</p> <p>PageSpeed Insights — pagespeed.web.dev on mobile first (it&#39;s the harder test)<br> WebPageTest — for waterfall analysis and finding hidden render-blocking resources<br> Lighthouse in Chrome DevTools — for local iteration before deploying<br> GTmetrix — cross-check and historical tracking</p> <p>We run audits on both mobile and desktop. Mobile is the stricter standard, and it&#39;s where most real users are.</p> <p>Real Results</p> <p>On a recent project for a local service business, we took their site from a 34 (mobile) on PageSpeed to a 98 mobile / 100 desktop after migration from a WordPress theme to hand-coded Next.js. Load time dropped from 6.8s to under 1.2s.</p> <p>The impact wasn&#39;t just the score — organic traffic increased 40% over the next three months as the improved Core Web Vitals fed into Google&#39;s ranking algorithm.</p> <p>TL;DR Checklist</p> <p>Use next/image with explicit dimensions and priority on LCP only<br> Self-host fonts with next/font<br> Dynamic import heavy components<br> Defer all third-party scripts with strategy=&quot;afterInteractive&quot; or &quot;lazyOnload&quot;<br> Purge unused CSS<br> Preconnect to critical third-party origins<br> Use SSG or ISR for static/semi-static pages<br> Audit on mobile, not just desktop</p> <p>Performance isn&#39;t magic — it&#39;s a series of deliberate choices made at every step of the build. If you&#39;re building sites on Next.js and want to go deeper on any of these techniques, drop a comment below or reach out at oceanwaveweb.com.</p> <p>Built by OceanWave Web — hand-coded Next.js websites for small businesses, starting at $0 down.</p>