Modern Web Performance Optimization - A Complete Guide
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}> {children}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});
}
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! ⚡