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.
class="keyword">class="comment">// Optimize LCP with Next.js Image
class="keyword">import Image class="keyword">from class="keyword">class="string">"next/image";
class="keyword">export class="keyword">default class="keyword">class="keyword">function Hero() {
class="keyword">class="keyword">return (
src=class="keyword">class="string">"/hero.jpg"
alt=class="keyword">class="string">"Hero image"
width={1200}
height={600}
priority class="keyword">class="comment">// Preload above-the-fold images
quality={85}
/>
);
}
First Input Delay (FID)
Measures interactivity. Should be less than 100 milliseconds.
class="keyword">class="comment">// Break up long tasks
class="keyword">class="keyword">async class="keyword">class="keyword">function processData(items) {
class="keyword">class="keyword">const chunks = chunkArray(items, 50);
class="keyword">class="keyword">for (class="keyword">class="keyword">const chunk of chunks) {
class="keyword">class="keyword">await class="keyword">new Promise((resolve) => {
requestIdleCallback(() => {
processChunk(chunk);
resolve();
});
});
}
}
Cumulative Layout Shift (CLS)
Measures visual stability. Should be less than 0.1.
class="keyword">class="comment">/* Reserve space class="keyword">class="keyword">for images */
.image-container {
aspect-ratio: 16 / 9;
width: 100%;
}
class="keyword">class="comment">/* Prevent layout shift class="keyword">from web fonts */
@font-face {
font-family: class="keyword">class="string">"MyFont";
src: url(class="keyword">class="string">"/fonts/myfont.woff2") format(class="keyword">class="string">"woff2");
font-display: swap; class="keyword">class="comment">/* Use fallback until loaded */
}
Image Optimization
Images often account for 50%+ of page weight.
Modern Formats
class="keyword">class="comment">// Next.js automatically serves WebP/AVIF
src=class="keyword">class="string">"/photo.jpg"
alt=class="keyword">class="string">"Photo"
width={800}
height={600}
class="keyword">class="comment">// Next.js serves optimal format per browser
/>
Responsive Images
class="keyword">class="comment">// Serve different sizes per viewport
src=class="keyword">class="string">"/banner.jpg"
alt=class="keyword">class="string">"Banner"
fill
sizes=class="keyword">class="string">"(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
Lazy Loading
class="keyword">class="comment">// Lazy load images below the fold
src=class="keyword">class="string">"/product.jpg"
alt=class="keyword">class="string">"Product"
width={400}
height={400}
loading=class="keyword">class="string">"lazy" class="keyword">class="comment">// Native lazy loading
/>
Placeholder Blur
class="keyword">class="comment">// Show blur placeholder class="keyword">class="keyword">while loading
src=class="keyword">class="string">"/hero.jpg"
alt=class="keyword">class="string">"Hero"
width={1200}
height={600}
placeholder=class="keyword">class="string">"blur"
blurDataURL=class="keyword">class="string">"data:image/jpeg;base64,/9j/4AAQSkZJRg..." class="keyword">class="comment">// Generated base64
/>
Code Splitting
Split your JavaScript bundles for faster initial loads.
Dynamic Imports
class="keyword">import dynamic class="keyword">from class="keyword">class="string">"next/dynamic";
class="keyword">class="comment">// Load component only when needed
class="keyword">class="keyword">const HeavyComponent = dynamic(() => class="keyword">import(class="keyword">class="string">"./HeavyComponent"), {
loading: () => ,
ssr: false, class="keyword">class="comment">// Client-side only
});
class="keyword">export class="keyword">default class="keyword">class="keyword">function Page() {
class="keyword">class="keyword">const [show, setShow] = useState(false);
class="keyword">class="keyword">return (
{show && }
);
}
Route-based Splitting
class="keyword">class="comment">// Next.js automatically splits by route
class="keyword">class="comment">// Each page gets its own bundle
class="keyword">class="comment">// app/dashboard/page.tsx
class="keyword">export class="keyword">default class="keyword">class="keyword">function Dashboard() {
class="keyword">class="keyword">return class="keyword">class="comment">// Only loaded on /dashboard
}
class="keyword">class="comment">// app/settings/page.tsx
class="keyword">export class="keyword">default class="keyword">class="keyword">function Settings() {
class="keyword">class="keyword">return class="keyword">class="comment">// Only loaded on /settings
}
Caching Strategies
Leverage caching to minimize network requests.
Static Generation
class="keyword">class="comment">// Pre-render at build time
class="keyword">export class="keyword">class="keyword">async class="keyword">class="keyword">function generateStaticParams() {
class="keyword">class="keyword">const posts = class="keyword">class="keyword">await getPosts();
class="keyword">class="keyword">return posts.map((post) => ({ slug: post.slug }));
}
class="keyword">export class="keyword">default class="keyword">class="keyword">async class="keyword">class="keyword">function Post({ params }) {
class="keyword">class="keyword">const post = class="keyword">class="keyword">await getPost(params.slug);
class="keyword">class="keyword">return ;
}
Incremental Static Regeneration
class="keyword">class="comment">// Revalidate class="keyword">static pages periodically
class="keyword">export class="keyword">class="keyword">const revalidate = 60; class="keyword">class="comment">// Revalidate every 60 seconds
class="keyword">export class="keyword">default class="keyword">class="keyword">async class="keyword">class="keyword">function ProductPage({ params }) {
class="keyword">class="keyword">const product = class="keyword">class="keyword">await fetch(class="keyword">class="string">/api/products/${params.id}, {
next: { revalidate: 60 },
}).then((res) => res.json());
class="keyword">class="keyword">return ;
}
HTTP Caching
class="keyword">class="comment">// Set cache headers
class="keyword">export class="keyword">class="keyword">async class="keyword">class="keyword">function GET() {
class="keyword">class="keyword">const data = class="keyword">class="keyword">await fetchData();
class="keyword">class="keyword">return class="keyword">new Response(JSON.stringify(data), {
headers: {
class="keyword">class="string">"Cache-Control": class="keyword">class="string">"class="keyword">public, max-age=3600, stale-class="keyword">class="keyword">while-revalidate=86400",
class="keyword">class="string">"Content-Type": class="keyword">class="string">"application/json",
},
});
}
Font Optimization
Fonts can significantly impact performance.
Next.js Font Optimization
class="keyword">import { Inter, Roboto_Mono } class="keyword">from class="keyword">class="string">"next/font/google";
class="keyword">class="keyword">const inter = Inter({
subsets: [class="keyword">class="string">"latin"],
display: class="keyword">class="string">"swap",
variable: class="keyword">class="string">"--font-inter",
});
class="keyword">class="keyword">const robotoMono = Roboto_Mono({
subsets: [class="keyword">class="string">"latin"],
display: class="keyword">class="string">"swap",
variable: class="keyword">class="string">"--font-roboto-mono",
});
class="keyword">export class="keyword">default class="keyword">class="keyword">function RootLayout({ children }) {
class="keyword">class="keyword">return (
class="keyword">class="string">${inter.variable} ${robotoMono.variable}}>
{children}
);
}
Subset Fonts
class="keyword">class="comment">// Only include characters you need
class="keyword">class="keyword">const notoSans = Noto_Sans({
subsets: [class="keyword">class="string">"latin"], class="keyword">class="comment">// Only Latin characters
weight: [class="keyword">class="string">"400", class="keyword">class="string">"700"], class="keyword">class="comment">// Only these weights
display: class="keyword">class="string">"swap",
});
Bundle Size Optimization
Keep your JavaScript bundles small.
Analyze Bundle
# Next.js bundle analyzer
npm install @next/bundle-analyzer
# next.config.js
class="keyword">class="keyword">const withBundleAnalyzer = require(class="keyword">class="string">'@next/bundle-analyzer')({
enabled: process.env.ANALYZE === class="keyword">class="string">'true'
})
module.exports = withBundleAnalyzer({
class="keyword">class="comment">// your config
})
# Analyze
ANALYZE=true npm run build
Tree Shaking
class="keyword">class="comment">// Import only what you need
class="keyword">import { debounce } class="keyword">from class="keyword">class="string">"lodash-es"; class="keyword">class="comment">// ✅ Tree-shakeable
class="keyword">class="comment">// vs
class="keyword">import _ class="keyword">from class="keyword">class="string">"lodash"; class="keyword">class="comment">// ❌ Imports entire library
Remove Unused Dependencies
# Find unused dependencies
npx depcheck
# Remove unused packages
npm uninstall unused-package
Compression
Compress assets for faster transfers.
Enable Compression
class="keyword">class="comment">// next.config.js
module.exports = {
compress: true, class="keyword">class="comment">// Enable gzip compression
class="keyword">class="comment">// Or use Brotli(better compression)
class="keyword">class="keyword">async headers() {
class="keyword">class="keyword">return [
{
source: class="keyword">class="string">"/:path*",
headers: [
{
key: class="keyword">class="string">"Content-Encoding",
value: class="keyword">class="string">"br",
},
],
},
];
},
};
Prefetching & Preloading
Load resources proactively.
Link Prefetching
class="keyword">import Link class="keyword">from class="keyword">class="string">"next/link";
class="keyword">class="comment">// Next.js automatically prefetches visible links
class="keyword">class="string">"/about" prefetch={true}>
About
;
Resource Preloading
class="keyword">class="comment">// app/layout.tsx
class="keyword">export class="keyword">default class="keyword">class="keyword">function RootLayout({ children }) {
class="keyword">class="keyword">return (
rel=class="keyword">class="string">"preload"
href=class="keyword">class="string">"/fonts/inter.woff2"
as=class="keyword">class="string">"font"
class="keyword">type=class="keyword">class="string">"font/woff2"
crossOrigin=class="keyword">class="string">"anonymous"
/>
{children}
);
}
Monitoring Performance
Track performance in production.
Web Vitals Tracking
class="keyword">class="comment">// app/layout.tsx
class="keyword">import { Analytics } class="keyword">from class="keyword">class="string">"@vercel/analytics/react";
class="keyword">import { SpeedInsights } class="keyword">from class="keyword">class="string">"@vercel/speed-insights/next";
class="keyword">export class="keyword">default class="keyword">class="keyword">function RootLayout({ children }) {
class="keyword">class="keyword">return (
{children}
);
}
Custom Metrics
class="keyword">class="string">"use client";
class="keyword">import { useReportWebVitals } class="keyword">from class="keyword">class="string">"next/web-vitals";
class="keyword">export class="keyword">class="keyword">function WebVitals() {
useReportWebVitals((metric) => {
class="keyword">class="comment">// Send to analytics
console.log(metric);
class="keyword">class="comment">// Example: Send to Google Analytics
class="keyword">class="keyword">if (window.gtag) {
window.gtag(class="keyword">class="string">"event", metric.name, {
value: Math.round(metric.value),
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
});
}
});
class="keyword">class="keyword">return class="keyword">null;
}
Database Query Optimization
Slow database queries kill performance.
Use Database Indexes
-- Create index class="keyword">class="keyword">for frequently queried columns
class="keyword">CREATE INDEX idx_users_email class="keyword">ON users(email);
class="keyword">CREATE INDEX idx_posts_user_id class="keyword">ON posts(user_id);
Connection Pooling
class="keyword">import { Pool } class="keyword">from class="keyword">class="string">"pg";
class="keyword">class="comment">// Reuse connections
class="keyword">class="keyword">const pool = class="keyword">new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
class="keyword">export class="keyword">class="keyword">async class="keyword">class="keyword">function getUser(id) {
class="keyword">class="keyword">const client = class="keyword">class="keyword">await pool.connect();
class="keyword">try {
class="keyword">class="keyword">const result = class="keyword">class="keyword">await client.query(class="keyword">class="string">"class="keyword">SELECT * class="keyword">FROM users class="keyword">WHERE id = $1", [
id,
]);
class="keyword">class="keyword">return result.rows[0];
} class="keyword">finally {
client.release();
}
}
Query Optimization
class="keyword">class="comment">// ❌ N+1 query problem
class="keyword">class="keyword">const users = class="keyword">class="keyword">await db.user.findMany();
class="keyword">class="keyword">for (class="keyword">class="keyword">const user of users) {
class="keyword">class="keyword">const posts = class="keyword">class="keyword">await db.post.findMany({ where: { userId: user.id } });
}
class="keyword">class="comment">// ✅ Single query with join
class="keyword">class="keyword">const users = class="keyword">class="keyword">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! ⚡