CloudOwl
HomeAbout
Services
How We WorkBlogContact
Schedule a callGet in touch
All posts
Technology

June 30, 2025

12 min read

Web Performance Optimization: The Changes That Actually Matter

Not all performance optimizations are equal. Some save milliseconds. These ones save seconds.


Performance optimization can be a rabbit hole. You can spend weeks shaving milliseconds off already-fast operations while users are waiting 4 seconds for your page to render because of an uncompressed hero image. The trick is knowing which optimizations actually move the needle. Here are the high-impact ones, in roughly the order you should do them.

Measure first

Before you optimize anything, measure. Run Lighthouse in Chrome DevTools. Check your Core Web Vitals in Google Search Console. Look at real user data in your analytics tool. Optimization without measurement is guessing.

The three Core Web Vitals that Google uses for ranking: Largest Contentful Paint (LCP) - how fast the main content appears, should be under 2.5 seconds. Interaction to Next Paint (INP) - how responsive the page is to user input, should be under 200ms. Cumulative Layout Shift (CLS) - how much the layout jumps around as the page loads, should be under 0.1.

Images (the biggest win, every time)

Unoptimized images are the number one cause of slow websites. A single 3MB JPEG hero image costs more than your entire JavaScript bundle.

  • Convert to WebP or AVIF. WebP is 25-35% smaller than JPEG at the same quality. AVIF is even smaller
  • Serve responsive sizes with srcset and the sizes attribute. Don't send a 2400px image to a phone with a 390px screen
  • Lazy-load images below the fold with loading="lazy". Don't lazy-load the hero image
  • Set explicit width and height attributes to prevent layout shift as images load
  • Use Next.js Image component or a similar optimization layer that handles this automatically
  • Use a CDN. Serving images from S3 in us-east-1 to a user in Tokyo adds hundreds of milliseconds

Reduce JavaScript bundle size

After images, JavaScript is usually the second-biggest performance problem. Every kilobyte of JS has to be downloaded, parsed, and executed before the page is interactive.

  • Code split by route - users shouldn't download the admin panel JS on the landing page
  • Lazy load heavy components (charts, maps, rich text editors) until they're needed
  • Audit your dependencies - run npx bundlephobia or check bundle size in your build output. One bloated library (like moment.js at 300KB) can add more than all your application code
  • Use tree-shaking-friendly imports. import { debounce } from 'lodash-es' instead of import _ from 'lodash'
  • Check for duplicate packages in your bundle. Two versions of the same library is more common than you'd think

Server-side rendering and static generation

For content-heavy pages, server-side rendering (SSR) or static generation (SSG) gives users content immediately instead of showing a blank page while JavaScript loads. The difference between a 3-second blank page and instant content is the difference between a bounce and a conversion.

Next.js makes this straightforward. Use static generation for pages that don't change often (marketing pages, blog posts). Use server-side rendering for pages with dynamic, user-specific content. Use streaming SSR to show content progressively as data loads.

Fonts

Custom fonts are a common source of layout shift and render blocking. Use font-display: swap so text is visible immediately (in a fallback font) while the custom font loads. Self-host fonts instead of loading from Google Fonts - it eliminates a DNS lookup and connection to an external server. Subset your fonts to only include the characters you need.

Caching

Set proper cache headers for static assets. Use content-hashed filenames (main.a3f8b2.js) with long cache durations (1 year). The browser will cache the file, and when you deploy a new version, the filename changes so users get the new file automatically. Use a CDN for global distribution. Cache API responses where freshness isn't critical.

Database queries

If your server response time is slow, the database is usually the bottleneck. Add indexes on columns you filter and sort by. Use EXPLAIN to check query plans for slow queries. Avoid N+1 queries (fetching a list, then making a separate query for each item). Use connection pooling so you're not opening a new database connection for every request.

The diminishing returns point

Once your LCP is under 2 seconds and your page weight is under 500KB, you're in good shape. Further optimization has diminishing returns unless you have specific performance requirements (real-time apps, high-traffic pages, mobile-first markets with slow connections). Focus on features, not on shaving another 50ms off an already-fast page.


Ben Arledge
Ben Arledge
CEO & CTO, CloudOwl

Need help building this?

No sales pitch, just an honest conversation about what you're building.

See our AI capabilities, React/Next.js work, or full service list.

Start a conversationSee how we work

More from the blog

Leadership
Why Your Business Needs a Fractional CTO in 2026
7 min read
Technology
Next.js vs. Traditional CMS: Which Is Right for Your Business Website?
9 min read
Strategy
How to Scope a Software Project Without Getting Burned
8 min read
CloudOwlCode you own. Team that stays.
Company
AboutServicesHow We WorkBlogFAQContact
Industries
SaaS & Software ProductsE-Commerce & RetailFood & TravelEnergy, Construction & Field ServicesFinance & FintechGaming & EntertainmentEducation & eLearning
Technologies
AI & Machine LearningReact & Next.jsNode.jsReact NativeAWS
Cities we serve
EdmontonCalgaryRed DeerLethbridgeSaskatoonReginaWinnipegKelownaVancouverTorontoDetroitKansas CityNashvilleCharlotteIndianapolisMiamiPittsburghDes MoinesBoise

© 2026 CloudOwl. All rights reserved.
Schedule a callhello@cloudowl.com587-872-5683