Back to blog

Modern Web Performance Optimization - A Complete Guide

7 min readBy Mustafa Akkaya
#Performance#Web Vitals#Optimization#Next.js

In today's web landscape, performance isn't just a nice-to-have—it's essential. Users expect instant loading, smooth interactions, and Google rewards fast sites with better rankings. Let's dive into practical techniques to optimize your web applications.

Why Performance Matters

The impact of web performance is measurable:

- 1 second delay = 7% reduction in conversions

- 53% of users abandon sites that take longer than 3 seconds

- Google Search uses Core Web Vitals as a ranking factor

- Better UX = Higher engagement and retention

- Lower costs = Reduced server and bandwidth expenses

Core Web Vitals

Google's Core Web Vitals measure real-world user experience:

Largest Contentful Paint (LCP)

Measures loading performance. Should occur within 2.5 seconds.

javascript

// Optimize LCP with Next.js Image

import Image from "next/image";

export default function Hero() {

return (

src="/hero.jpg"

alt="Hero image"

width={1200}

height={600}

priority // Preload above-the-fold images

quality={85}

/>

);

}

First Input Delay (FID)

Measures interactivity. Should be less than 100 milliseconds.

javascript

// Break up long tasks

async function processData(items) {

const chunks = chunkArray(items, 50);

for (const chunk of chunks) {

await new Promise((resolve) => {

requestIdleCallback(() => {

processChunk(chunk);

resolve();

});

});

}

}

Cumulative Layout Shift (CLS)

Measures visual stability. Should be less than 0.1.

css

/* Reserve space for images */

.image-container {

aspect-ratio: 16 / 9;

width: 100%;

}

/* Prevent layout shift from web fonts */

@font-face {

font-family: "MyFont";

src: url("/fonts/myfont.woff2") format("woff2");

font-display: swap; /* Use fallback until loaded */

}

Image Optimization

Images often account for 50%+ of page weight.

Modern Formats

javascript

// Next.js automatically serves WebP/AVIF

src="/photo.jpg"

alt="Photo"

width={800}

height={600}

// Next.js serves optimal format per browser

/>

Responsive Images

javascript

// Serve different sizes per viewport

src="/banner.jpg"

alt="Banner"

fill

sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"

/>

Lazy Loading

javascript

// Lazy load images below the fold

src="/product.jpg"

alt="Product"

width={400}

height={400}

loading="lazy" // Native lazy loading

/>

Placeholder Blur

javascript

// Show blur placeholder while loading

src="/hero.jpg"

alt="Hero"

width={1200}

height={600}

placeholder="blur"

blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." // Generated base64

/>

Code Splitting

Split your JavaScript bundles for faster initial loads.

Dynamic Imports

javascript

import dynamic from "next/dynamic";

// Load component only when needed

const HeavyComponent = dynamic(() => import("./HeavyComponent"), {

loading: () => ,

ssr: false, // Client-side only

});

export default function Page() {

const [show, setShow] = useState(false);

return (

{show && }

);

}

Route-based Splitting

javascript

// Next.js automatically splits by route

// Each page gets its own bundle

// app/dashboard/page.tsx

export default function Dashboard() {

return // Only loaded on /dashboard

}

// app/settings/page.tsx

export default function Settings() {

return // Only loaded on /settings

}

Caching Strategies

Leverage caching to minimize network requests.

Static Generation

javascript

// Pre-render at build time

export async function generateStaticParams() {

const posts = await getPosts();

return posts.map((post) => ({ slug: post.slug }));

}

export default async function Post({ params }) {

const post = await getPost(params.slug);

return

;

}

Incremental Static Regeneration

javascript

// Revalidate static pages periodically

export const revalidate = 60; // Revalidate every 60 seconds

export default async function ProductPage({ params }) {

const product = await fetch(/api/products/${params.id}, {

next: { revalidate: 60 },

}).then((res) => res.json());

return ;

}

HTTP Caching

javascript

// Set cache headers

export async function GET() {

const data = await fetchData();

return new Response(JSON.stringify(data), {

headers: {

"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",

"Content-Type": "application/json",

},

});

}

Font Optimization

Fonts can significantly impact performance.

Next.js Font Optimization

javascript

import { Inter, Roboto_Mono } from "next/font/google";

const inter = Inter({

subsets: ["latin"],

display: "swap",

variable: "--font-inter",

});

const robotoMono = Roboto_Mono({

subsets: ["latin"],

display: "swap",

variable: "--font-roboto-mono",

});

export default function RootLayout({ children }) {

return (

${inter.variable} ${robotoMono.variable}
}> {children}

);

}

Subset Fonts

javascript

// Only include characters you need

const notoSans = Noto_Sans({

subsets: ["latin"], // Only Latin characters

weight: ["400", "700"], // Only these weights

display: "swap",

});

Bundle Size Optimization

Keep your JavaScript bundles small.

Analyze Bundle

bash

# Next.js bundle analyzer

npm install @next/bundle-analyzer

# next.config.js

const withBundleAnalyzer = require('@next/bundle-analyzer')({

enabled: process.env.ANALYZE === 'true'

})

module.exports = withBundleAnalyzer({

// your config

})

# Analyze

ANALYZE=true npm run build

Tree Shaking

javascript

// Import only what you need

import { debounce } from "lodash-es"; // ✅ Tree-shakeable

// vs

import _ from "lodash"; // ❌ Imports entire library

Remove Unused Dependencies

bash

# Find unused dependencies

npx depcheck

# Remove unused packages

npm uninstall unused-package

Compression

Compress assets for faster transfers.

Enable Compression

javascript

// next.config.js

module.exports = {

compress: true, // Enable gzip compression

// Or use Brotli (better compression)

async headers() {

return [

{

source: "/:path*",

headers: [

{

key: "Content-Encoding",

value: "br",

},

],

},

];

},

};

Prefetching & Preloading

Load resources proactively.

Link Prefetching

javascript

import Link from "next/link";

// Next.js automatically prefetches visible links

About

;

Resource Preloading

javascript

// app/layout.tsx

export default function RootLayout({ children }) {

return (

rel="preload"

href="/fonts/inter.woff2"

as="font"

type="font/woff2"

crossOrigin="anonymous"

/>

{children}

);

}

Monitoring Performance

Track performance in production.

Web Vitals Tracking

javascript

// app/layout.tsx

import { Analytics } from "@vercel/analytics/react";

import { SpeedInsights } from "@vercel/speed-insights/next";

export default function RootLayout({ children }) {

return (

{children}

);

}

Custom Metrics

javascript

"use client";

import { useReportWebVitals } from "next/web-vitals";

export function WebVitals() {

useReportWebVitals((metric) => {

// Send to analytics

console.log(metric);

// Example: Send to Google Analytics

if (window.gtag) {

window.gtag("event", metric.name, {

value: Math.round(metric.value),

metric_id: metric.id,

metric_value: metric.value,

metric_delta: metric.delta,

});

}

});

return null;

}

Database Query Optimization

Slow database queries kill performance.

Use Database Indexes

sql

-- Create index for frequently queried columns

CREATE INDEX idx_users_email ON users(email);

CREATE INDEX idx_posts_user_id ON posts(user_id);

Connection Pooling

javascript

import { Pool } from "pg";

// Reuse connections

const pool = new Pool({

max: 20,

idleTimeoutMillis: 30000,

connectionTimeoutMillis: 2000,

});

export async function getUser(id) {

const client = await pool.connect();

try {

const result = await client.query("SELECT * FROM users WHERE id = $1", [

id,

]);

return result.rows[0];

} finally {

client.release();

}

}

Query Optimization

javascript

// ❌ N+1 query problem

const users = await db.user.findMany();

for (const user of users) {

const posts = await db.post.findMany({ where: { userId: user.id } });

}

// ✅ Single query with join

const users = await db.user.findMany({

include: {

posts: true,

},

});

Performance Checklist

Before deploying:

- ✅ Optimize images (WebP/AVIF, lazy loading)

- ✅ Enable code splitting and dynamic imports

- ✅ Use static generation where possible

- ✅ Implement caching strategies

- ✅ Optimize fonts (subset, preload)

- ✅ Minimize bundle size (tree shaking)

- ✅ Enable compression (Brotli/gzip)

- ✅ Prefetch critical resources

- ✅ Monitor Core Web Vitals

- ✅ Optimize database queries

- ✅ Use CDN for static assets

- ✅ Minify CSS/JS in production

Conclusion

Web performance optimization is an ongoing process. Start with Core Web Vitals, optimize images and fonts, implement smart caching, and continuously monitor your metrics.

Remember: fast websites win. They rank better, convert more, and provide superior user experiences. Every millisecond counts.

Happy optimizing! ⚡