Performance2025-01-0614 min read

Website Speed Optimization Guide: From Slow to Fast

A comprehensive guide to speeding up your website. Learn actionable techniques to improve load times and Core Web Vitals.

Introduction

Website speed directly impacts user experience, conversion rates, and search rankings. This guide provides actionable techniques to optimize every aspect of your website's performance.

Before You Start: Measure Your Baseline

Before optimizing, measure your current performance:

  1. Run PageSpeed Insights: https://pagespeed.web.dev/
  2. Test WebPageTest: https://www.webpagetest.org/
  3. Check Lighthouse: In Chrome DevTools
  4. Record Your Metrics:
    • LCP (Largest Contentful Paint)
    • FID (First Input Delay)
    • CLS (Cumulative Layout Shift)
    • Total page load time
    • Page size

1. Image Optimization

Images are typically the largest resources on a page.

Choose Modern Formats

<!-- WebP with JPEG fallback -->
<picture>
  <source srcset="image.webp" type="image/webp" />
  <source srcset="image.jpg" type="image/jpeg" />
  <img src="image.jpg" alt="Description" />
</picture>

Format Comparison: | Format | Size vs JPEG | Browser Support | |--------|--------------|-----------------| | JPEG | Baseline | Universal | | PNG | Larger/smaller* | Universal | | WebP | 25-35% smaller | 95%+ | | AVIF | 50% smaller | 70%+ |

*PNG is better for graphics/transparent images

Implement Responsive Images

<img
  src="image-800w.jpg"
  srcset="
    image-400w.jpg   400w,
    image-800w.jpg   800w,
    image-1200w.jpg 1200w,
    image-1600w.jpg 1600w
  "
  sizes="(max-width: 600px) 400px,
         (max-width: 1200px) 800px,
         1200px"
  loading="lazy"
  width="1600"
  height="1200"
  alt="Descriptive alt text"
/>

Compression Settings

Image Compression Tools:

  • Online: TinyPNG, Squoosh, ImageOptim
  • CLI: imagemagick, sharp, libvips
  • Build: next/image, Astro image, Gatsby image

Recommended Compression:

  • JPEG: Quality 80-85
  • WebP: Quality 75-80
  • PNG: Optimize with pngquant or oxipng

Lazy Loading

<!-- Native lazy loading (supported in 95%+ browsers) -->
<img src="image.jpg" loading="lazy" alt="Description" />

<!-- Eager loading for above-fold images -->
<img src="hero.jpg" loading="eager" alt="Hero" />

2. CSS Optimization

Minify CSS

# CLI tools
npm install -g csso-cli
csso input.css -o output.min.css

Build tools:

  • Vite: Built-in minification
  • Webpack: css-minimizer-plugin
  • Next.js: Automatic CSS optimization

Critical CSS Inlining

<head>
  <style>
    /* Inline critical above-fold CSS */
    .hero {
      max-width: 1200px;
      margin: 0 auto;
    }
    .btn {
      padding: 12px 24px;
    }
  </style>
  <link
    rel="preload"
    href="styles.css"
    as="style"
    onload="this.onload=null;this.rel='stylesheet'"
  />
  <noscript><link rel="stylesheet" href="styles.css" /></noscript>
</head>

Remove Unused CSS

// PurgeCSS configuration
module.exports = {
  content: ["./src/**/*.html", "./src/**/*.js"],
  defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
  safelist: {
    standard: [/^hover:/, /^focus:/],
  },
};

CSS Delivery Best Practices

  • [ ] Inline critical CSS (above the fold)
  • [ ] Defer non-critical CSS
  • [ ] Remove unused CSS classes
  • [ ] Avoid @import (use link instead)
  • [ ] Use CSS containment for isolation

3. JavaScript Optimization

Code Splitting

// Route-based splitting (React Router)
const Home = lazy(() => import("./routes/Home"));
const About = lazy(() => import("./routes/About"));

// Component-based splitting
const HeavyChart = lazy(() => import("./components/HeavyChart"));

Defer Non-Critical JavaScript

<!-- Deferred execution -->
<script defer src="analytics.js"></script>

<!-- Async for independent scripts -->
<script async src="widget.js"></script>

<!-- Module scripts -->
<script type="module" src="app.js"></script>

Tree Shaking

// Good: Import only what you need
import { debounce } from "lodash-es";

// Bad: Import entire library
import _ from "lodash";

Use ES modules for better tree shaking:

  • Use lodash-es instead of lodash
  • Import specific functions
  • Use sideEffects: false in package.json

Reduce JavaScript Execution Time

// Web Workers for heavy computation
const worker = new Worker("heavy-worker.js");
worker.postMessage({ data: largeDataSet });
worker.onmessage = (e) => console.log(e.result);

// requestIdleCallback for non-urgent work
requestIdleCallback(() => {
  // Analytics, logging, etc.
});

4. Font Optimization

Use Font Display Swap

@font-face {
  font-family: "Inter";
  src: url("inter.woff2") format("woff2");
  font-weight: 400;
  font-display: swap; /* Key for performance */
}

Preload Critical Fonts

<link
  rel="preload"
  as="font"
  href="critical.woff2"
  type="font/woff2"
  crossorigin
/>

Font Subsetting

# Create subset with only used characters
pyftsubset NotoSans-Regular.ttf \
  --text-file=used-chars.txt \
  --output-file=NotoSans-Subset.woff2

Font Loading Strategies

| Strategy | Description | Use Case | | -------- | --------------------------------------------- | -------------- | | swap | Shows text immediately, swaps when font loads | Body text | | optional | Short timeout, then falls back | Optional fonts | | fallback | Brief invisible period, then fallback | Headings | | optional | No flash if font loads quickly | Non-critical |

5. Server-Side Optimization

Enable Compression

# nginx.conf
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
gzip_comp_level 6;

# Brotli (better compression)
brotli on;
brotli_types text/plain text/css application/json application/javascript;
# .htaccess
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>

HTTP/2 and HTTP/3

# Enable HTTP/2
listen 443 ssl http2;

# Enable HTTP/3 (QUIC)
listen 443 quic;
add_header Alt-Svc 'h3=":443"; ma=86400';

Browser Caching

# Static assets - long cache
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

# HTML - short cache
location ~* \.html$ {
  expires 1h;
  add_header Cache-Control "public";
}

Server Push / Early Hints

# Early hints (HTTP/2)
103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script

6. Caching Strategies

CDN Implementation

Use a CDN for static assets:

  • Cloudflare (free tier available)
  • AWS CloudFront
  • Fastly
  • BunnyCDN
  • Cloudflare R2 (for object storage)

Service Worker Caching

// Install event - cache assets
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("v1").then((cache) => {
      return cache.addAll(["/", "/styles.css", "/script.js", "/logo.png"]);
    }),
  );
});

// Fetch event - serve from cache
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    }),
  );
});

Cache Headers

# Static assets (immutable)
Cache-Control: public, max-age=31536000, immutable

# HTML pages
Cache-Control: public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400

# API responses
Cache-Control: public, max-age=300, s-maxage=600

7. Reducing Third-Party Impact

Audit Third-Party Scripts

// List all third-party scripts
console.log(
  performance
    .getEntriesByType("resource")
    .filter((r) => !r.name.match(location.hostname)),
);

Third-Party Optimization

  1. Load asynchronously where possible
  2. Defer non-critical scripts
  3. Use facade patterns for widgets
  4. Consider self-hosting for critical libraries
  5. Implement timeout fallbacks
// Facade pattern example
function loadChatWidget() {
  const script = document.createElement("script");
  script.src = "chat-widget.js";
  script.async = true;
  document.head.appendChild(script);
}

// Load only when user interacts
document
  .querySelector("#chat-button")
  .addEventListener("click", loadChatWidget);

8. Mobile-Specific Optimizations

Responsive Images

Already covered in section 1, but critical for mobile.

Touch-Friendly Targets

/* Minimum touch target size: 44x44px */
.button {
  min-height: 44px;
  min-width: 44px;
  padding: 12px 24px;
}

Viewport Meta Tag

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

Reduce Mobile Payloads

// Detect mobile and serve lighter content
if (window.innerWidth < 768) {
  // Load lower-quality images
  // Skip non-critical features
  // Reduce animation complexity
}

9. Monitoring and Maintenance

Performance Budgets

// webpack.config.js
module.exports = {
  performance: {
    maxEntrypointSize: 200000, // 200KB
    maxAssetSize: 100000, // 100KB
  },
};

Real User Monitoring (RUM)

// Core Web Vitals monitoring
function logCoreWebVitals() {
  // LCP
  new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const lastEntry = entries[entries.length - 1];
    console.log("LCP:", lastEntry.startTime);
  }).observe({ type: "largest-contentful-paint", buffered: true });

  // FID
  new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      console.log("FID:", entry.processingStart - entry.startTime);
    }
  }).observe({ type: "first-input", buffered: true });

  // CLS
  let clsValue = 0;
  new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (!entry.hadRecentInput) {
        clsValue += entry.value;
        console.log("CLS:", clsValue);
      }
    }
  }).observe({ type: "layout-shift", buffered: true });
}

Regular Auditing

Schedule performance audits:

  • Weekly: Monitor Core Web Vitals
  • Monthly: Full performance audit
  • Quarterly: Performance budget review
  • Annually: Infrastructure review

10. Framework-Specific Tips

Next.js

// next.config.js
module.exports = {
  images: {
    formats: ["image/webp", "image/avif"],
    deviceSizes: [640, 750, 828, 1080, 1200],
  },
  compress: true,
  swcMinify: true,
};

React

// Code splitting with React.lazy
const HeavyComponent = React.lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyComponent />
    </Suspense>
  );
}

Vue

// Async components
const AsyncComponent = defineComponent({
  components: {
    Heavy: defineAsyncComponent(() => import("./Heavy.vue")),
  },
});

Quick Wins Checklist

Start with these high-impact, low-effort optimizations:

  • [ ] Enable gzip/brotli compression
  • [ ] Add width/height to all images
  • [ ] Implement lazy loading for images
  • [ ] Minify CSS and JavaScript
  • [ ] Defer non-critical CSS/JS
  • [ ] Add browser cache headers
  • [ ] Use WebP format for images
  • [ ] Remove unused CSS
  • [ ] Use a CDN for static assets
  • [ ] Preload critical resources

Optimization Priority Matrix

| Impact | Effort | Technique | | ------ | ------ | ------------------------------------------------------- | | High | Low | Enable compression, add image dimensions, cache headers | | High | Medium | Image optimization, code splitting, critical CSS | | High | High | Service Worker, architecture redesign | | Low | Low | Remove unused libraries, minify code | | Low | Medium | Font optimization, reduce third-party impact | | Low | High | Custom CDN configuration, edge computing |

Conclusion

Website speed optimization is an iterative process. Start with high-impact quick wins, then progressively tackle more complex optimizations. Regular measurement and maintenance are key to maintaining fast performance.

Scan your website to get a detailed performance analysis with specific, prioritized recommendations for your site.

Scan Your Website Now

Get comprehensive insights into your website's technology, security, SEO, and performance.

You Might Also Like