Fix Next.js Image Optimization with Strapi CMS: Configuration and Common Errors
Quick Answer
- Conclusion: Use Next.js
next/imagecomponent with Strapi CMS to automatically optimize images (WebP/AVIF conversion, responsive srcSet, lazy loading) and improve LCP/CLS Core Web Vitals without manual optimization scripts. - First checks: Ensure
next.config.jshasimages.remotePatternsconfigured with your Strapi hostname, and that every<Image>component includes requiredsrc,alt,width, andheightprops. - Minimal fix for remote images: Add your Strapi domain to
remotePatternsinnext.config.jsand restart the dev server. Example:{ protocol: 'https', hostname: 'your-strapi-instance.com', pathname: '/uploads/**' }. - Version boundary: Works with Next.js 13+ (App Router) and Next.js 12+ (Pages Router). Requires
next buildbeforenext startin production.
What Problem It Solves
When using Strapi CMS, images uploaded by editors are often large, unoptimized files (JPEG/PNG at full resolution). Serving these directly causes:
- High Largest Contentful Paint (LCP) – large image files take too long to download
- Cumulative Layout Shift (CLS) – images push content down as they load
- Wasted bandwidth – mobile users download desktop-sized images
Next.js next/image solves all three by automatically converting formats, resizing to device-specific dimensions, lazy-loading offscreen images, and reserving space via explicit width/height props.
Minimal Working Configuration
1. Configure next.config.js
JS// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'your-strapi-instance.com', port: '', pathname: '/uploads/**', }, ], }, }; module.exports = nextConfig;
Replace your-strapi-instance.com with your actual Strapi domain. For local development with Strapi on localhost:1337, use:
JS{ protocol: 'http', hostname: 'localhost', port: '1337', pathname: '/uploads/**', }
2. Use the Component
JSXimport Image from 'next/image'; export default function BlogPost({ post }) { return ( <Image src={post.coverImage.url} // e.g. "https://your-strapi-instance.com/uploads/hero-123.jpg" alt={post.coverImage.alternativeText || 'Blog cover'} width={1200} height={630} priority // Add for above-the-fold images placeholder="blur" // Optional: requires blurDataURL for remote images quality={85} // Optional: default is 75 /> ); }
Parameters and Environment Variables
| Parameter | Required | Default | Description |
|---|---|---|---|
src | Yes | – | Image source URL. For remote images, must be absolute (http/https). For local images in public/, use relative path. |
alt | Yes | – | Alternative text for accessibility and SEO. |
width | Yes | – | Explicit width in pixels. Prevents layout shift (CLS). |
height | Yes | – | Explicit height in pixels. Prevents layout shift (CLS). |
placeholder | No | 'empty' | 'blur' shows a blurred placeholder until the image loads. Requires blurDataURL for remote images. |
quality | No | 75 | Image compression quality (1–100). Lower values reduce file size. |
priority | No | false | When true, the image is preloaded (no lazy loading). Essential for LCP images. |
Root Cause Analysis
The most common failure point is the remotePatterns configuration. Next.js blocks optimization for any external image host not explicitly whitelisted. This is a security measure to prevent SSRF attacks where an attacker could trick your server into processing arbitrary external images.
When remotePatterns is missing or incorrect, Next.js throws:
Error: Invalid src prop (https://example.com/image.jpg) on `next/image`,
hostname "example.com" is not configured under images in your `next.config.js`
The second major issue is missing width and height props. Unlike a standard <img> tag, next/image requires explicit dimensions to calculate aspect ratio and prevent CLS. If you omit them, the component will warn or error.
Common Errors and Fixes
Error: Hostname not configured
Error message: Invalid src prop ... hostname "..." is not configured under images in your next.config.js
Fix: Add the exact hostname to images.remotePatterns in next.config.js. Include the correct protocol, hostname, and optionally pathname and port.
JS// Correct remotePatterns: [{ protocol: 'https', hostname: 'cdn.strapi.io', pathname: '/uploads/**' }] // Wrong – too broad, security risk remotePatterns: [{ protocol: 'https', hostname: '**' }]
Error: Image Optimization API not available
Error message: Image Optimization API is not available in next start mode
Fix: Run next build before next start. The image optimization pipeline requires a production build. If you're using a custom server, ensure it calls next({ dev: false }).
Warning: Width or height modified but not the other
Warning: Image with src "..." has either width or height modified, but not the other
Fix: When applying CSS size changes, always set both width and height, or use object-fit. Alternatively, use the fill prop with a positioned parent container:
JSX<div style={{ position: 'relative', width: '100%', height: '400px' }}> <Image src={url} alt="Hero" fill style={{ objectFit: 'cover' }} /> </div>
Error: Invalid URL
Error message: The URL must be absolute and start with http:// or https://
Fix: Ensure src is a full absolute URL for remote images. For local images in public/, use a relative path like /images/logo.png (no leading http).
Production Notes and Security Checks
1. Lock down remotePatterns
Never use wildcard patterns like hostname: '**' in production. Always specify the exact hostname and path prefix. This prevents attackers from making your server optimize arbitrary external images (a form of SSRF).
2. Mitigate CPU spikes
Image optimization uses Sharp, which is CPU-intensive. On first request (cache miss), the server processes the image. To reduce impact:
- Set
images.minimumCacheTTLto a reasonable value (e.g.,3600seconds) - Consider using an external image CDN (Cloudflare Images, Imgix) for high-traffic sites
- Set CPU limits in containerized deployments (Kubernetes/Docker)
3. Cache concurrency
The default image cache is filesystem-based (.next/cache/images). Under high concurrency, multiple requests for the same uncached image may cause file locking. For serverful deployments, use a shared cache (Redis, S3) or a custom loader.
4. Access control
next/image does not enforce authentication. If images require access control (e.g., paid content), implement a proxy API route that validates permissions before serving the image.
5. Always use HTTPS in production
Set protocol: 'https' in remotePatterns. Using http in production triggers mixed-content warnings and exposes traffic to MITM attacks.
FAQ
Q: My LCP didn't improve after switching to next/image. What's wrong?
A: Check these in order:
- Did you add
priorityto the hero image? Without it, Next.js lazy-loads by default. - Is the Strapi hostname correctly listed in
remotePatterns? If not, Next.js serves the original unoptimized image. - Are
widthandheightset to the actual display size? Setting them too large generates oversized images. - Is the image cache warm? First visit is slower; subsequent visits use cached optimized versions.
Q: How do I add blur placeholder for remote Strapi images?
A: Remote images require a manual blurDataURL. Generate it server-side:
JSX// In getServerSideProps or API route import sharp from 'sharp'; const response = await fetch(imageUrl); const buffer = await response.buffer(); const { data } = await sharp(buffer) .resize(10) .jpeg({ quality: 30 }) .toBuffer({ resolveWithObject: true }); const blurDataURL = `data:image/jpeg;base64,${data.toString('base64')}`; // In component <Image src={imageUrl} placeholder="blur" blurDataURL={blurDataURL} alt="..." />
This adds server load, so use it only for critical above-the-fold images.
Q: My Strapi images are on AWS S3. How do I configure next/image?
A: Add the S3 bucket domain to remotePatterns:
JSremotePatterns: [ { protocol: 'https', hostname: 'your-bucket.s3.us-east-1.amazonaws.com', pathname: '/uploads/**', }, ],
For better performance, use a custom loader that points to a CDN (CloudFront + Lambda@Edge) or a third-party image service:
JS// next.config.js images: { loader: 'custom', loaderFile: './lib/imageLoader.js', } // ./lib/imageLoader.js export default function myImageLoader({ src, width, quality }) { return `https://your-cdn-domain.com/${src}?w=${width}&q=${quality || 75}`; }
Ensure the S3 bucket policy allows read access from your Next.js server or CDN.