Image Optimization
Why Image Optimization Matters
Images are often the largest assets on a page. Without optimization:
- Slow page loads – Users wait for large images
- Poor Core Web Vitals – LCP (Largest Contentful Paint) suffers
- High bandwidth costs – Serving unoptimized images wastes resources
Next.js Image component provides:
- Automatic format conversion (WebP, AVIF)
- Lazy loading by default
- Responsive srcset generation
- Blur placeholder support
Configuring Next.js for WordPress Images
First, allow WordPress domain in next.config.js:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'your-wordpress-site.com',
pathname: '/wp-content/uploads/**',
},
// Add localhost for development
{
protocol: 'http',
hostname: 'localhost',
},
],
},
};
module.exports = nextConfig;your-wordpress-site.com with your actual WordPress domain, or use an environment variable.
Creating a WordPress Image Component
Build a reusable component for WordPress images:
// src/components/WPImage.tsx
import Image from 'next/image';
interface WPImageProps {
src: string;
alt: string;
width?: number;
height?: number;
fill?: boolean;
priority?: boolean;
className?: string;
sizes?: string;
}
export function WPImage({
src,
alt,
width,
height,
fill = false,
priority = false,
className = '',
sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw',
}: WPImageProps) {
// Handle relative URLs from WordPress
const imageSrc = src.startsWith('http')
? src
: `${process.env.NEXT_PUBLIC_WORDPRESS_URL}${src}`;
if (fill) {
return (
<Image
src={imageSrc}
alt={alt}
fill
priority={priority}
className={`object-cover ${className}`}
sizes={sizes}
/>
);
}
return (
<Image
src={imageSrc}
alt={alt}
width={width || 1200}
height={height || 630}
priority={priority}
className={className}
sizes={sizes}
/>
);
}Using in Post Cards
Update PostCard to use optimized images:
// src/components/PostCard.tsx
import { WPImage } from './WPImage';
import { WPPostWithEmbed, getFeaturedImageUrl } from '@/lib/types';
export function PostCard({ post }: { post: WPPostWithEmbed }) {
const imageUrl = getFeaturedImageUrl(post, 'medium_large');
return (
<article className="border rounded-lg overflow-hidden">
{imageUrl && (
<div className="aspect-video relative">
<WPImage
src={imageUrl}
alt={post.title.rendered}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
)}
<div className="p-4">
{/* Content */}
</div>
</article>
);
}Blur Placeholder for Better UX
For a nicer loading experience, add blur placeholders:
// src/components/WPImage.tsx
interface WPImageProps {
// ... existing props
blurDataURL?: string;
}
export function WPImage({
// ... existing props
blurDataURL,
}: WPImageProps) {
// Generate a simple blur placeholder if not provided
const placeholder = blurDataURL ? 'blur' : 'empty';
return (
<Image
src={imageSrc}
alt={alt}
fill={fill}
width={!fill ? width : undefined}
height={!fill ? height : undefined}
priority={priority}
className={className}
sizes={sizes}
placeholder={placeholder}
blurDataURL={blurDataURL}
/>
);
}Generating Blur Data URLs
For static images, you can generate blur placeholders at build time:
// src/lib/images.ts
import { getPlaiceholder } from 'plaiceholder';
export async function getBlurDataUrl(imageUrl: string): Promise<string | null> {
try {
const res = await fetch(imageUrl);
const buffer = await res.arrayBuffer();
const { base64 } = await getPlaiceholder(Buffer.from(buffer));
return base64;
} catch {
return null;
}
}Install plaiceholder:
npm install plaiceholder sharpImages in Post Content
WordPress content may include images. Handle them with custom components:
// src/components/PostContent.tsx
'use client';
import { useEffect, useRef } from 'react';
interface PostContentProps {
content: string;
}
export function PostContent({ content }: PostContentProps) {
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!contentRef.current) return;
// Find all images in content
const images = contentRef.current.querySelectorAll('img');
images.forEach((img) => {
// Add lazy loading
img.loading = 'lazy';
// Add responsive classes
img.classList.add('max-w-full', 'h-auto', 'rounded-lg');
// Wrap in figure if not already
if (img.parentElement?.tagName !== 'FIGURE') {
const figure = document.createElement('figure');
figure.className = 'my-6';
img.parentElement?.insertBefore(figure, img);
figure.appendChild(img);
}
});
}, [content]);
return (
<div
ref={contentRef}
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: content }}
/>
);
}Performance Tips
1. Use Appropriate Sizes
// For hero images (full width)
sizes="100vw"
// For grid of 3 columns
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
// For sidebar images
sizes="(max-width: 768px) 100vw, 300px"2. Priority Loading for Above-the-Fold
// First post in grid - load immediately
<WPImage priority={index === 0} ... />3. Use Next.js Image Optimization API
Images are automatically optimized through /_next/image. Configure quality:
// next.config.js
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60 * 60 * 24, // 24 hours
}Summary
In this lesson, we:
- ✅ Configured Next.js for WordPress images
- ✅ Created a reusable WPImage component
- ✅ Added blur placeholders
- ✅ Handled images in post content
- ✅ Applied performance optimizations
Next up: SEO and metadata optimization.