Images represent the largest content type on web pages. According to Web Almanac 2024 data, the median desktop page loads 1,054 KB of images, more than any other resource type.
Missing dimensions cause layout shifts that spike Cumulative Layout Shift above 0.25, and mobile users on slower connections wait through multi-megabyte downloads that tank your Core Web Vitals.
The Next.js Image component intercepts remote image requests, resizes and compresses them on demand, converts to WebP or AVIF based on browser support, and serves device-appropriate variants automatically.
This guide covers Next.js configuration for remote CMS images, Strapi API integration, responsive delivery patterns, and optimization verification.
In brief:
- Configure remotePatterns in next.config.js to whitelist your Strapi domain—localhost for development, wildcard patterns for Strapi Cloud, environment variables for production.
- Strapi's Media Library generates thumbnail (156px), small (500px), medium (750px), and large (1000px) variants automatically on upload, providing ready-made responsive breakpoints without build-time processing.
- The next/image component requires four fields from your CMS API: url, width, height, and alternativeText. Map these once with a utility function, then spread the result into Image props across your application.
- Optimization happens on the first request and caches at the edge. Lighthouse reports confirm 60-80% file size reductions, LCP under 2.5 seconds, and zero layout shift—without manual image processing or external services.
What Is the Next.js Image Component?
The Next.js Image component (imported as next/image) is a React component built into Next.js that automatically optimizes images through on-demand resizing, compression, and format conversion.
You import it from next/image, provide a source URL and dimensions, and Next.js handles the entire optimization pipeline—generating multiple sizes, converting to WebP or AVIF, and caching results at the edge.
Images need resizing for different devices, compression to reduce file weight, format conversion to modern formats, lazy loading to defer offscreen content, and explicit dimensions to prevent layout shifts.
The Next.js Image component runs all five optimizations automatically when you fetch images from Strapi, no build scripts, no external services, no manual processing.
The Next.js Image Component vs Standard HTML Images
Standard <img> tags display whatever file you provide. A 2MB JPEG downloads to mobile devices at full resolution.
No format conversion happens, so browsers receive JPEG even when they support lighter WebP or AVIF. Images load immediately whether visible or not, and without explicit width and height attributes, content jumps when files arrive.
The next/image component intercepts each request. Next.js fetches the remote file from Strapi, generates multiple sizes, converts to modern formats based on browser support, caches the result, and serves the smallest variant that maintains visual quality.
You provide width and height once; the browser reserves space before the image loads, eliminating layout shift.
1<!-- Traditional approach -->
2<img
3 src="https://my-strapi-instance.com/uploads/foo.jpg"
4 width="800"
5 height="600"
6 alt="Landscape"
7/>1// Next.js approach
2import Image from 'next/image';
3
4<Image
5 src="https://my-strapi-instance.com/uploads/foo.jpg"
6 alt="Landscape"
7 width={800}
8 height={600}
9 placeholder="blur"
10 quality={80}
11/>| Capability | <img> | next/image |
|---|---|---|
| Automatic resizing | ✗ | ✓ |
| Format conversion (WebP/AVIF) | ✗ | ✓ |
| Native lazy loading | Manual | Default |
| CLS prevention | Manual | Enforced |
| Remote asset optimization | ✗ | ✓ |
| Developer effort | High | Low |
Next.js runs images through Sharp compression, reducing file size by 40-70% on average. Format conversion to WebP or AVIF saves another 25-35% for compatible browsers. Combined, these optimizations cut payloads by 60-80% compared to the original upload.
Optimization runs on demand rather than at build time. The first visitor triggers processing; subsequent requests serve from cache. Content editors upload images through Strapi's Media Library, and Next.js handles optimization automatically without build steps or version control bloat.
Built-in Optimization Features of the Next.js Image Component
Next.js fetches remote files at runtime, processes them through Sharp compression, and stores multiple variants by width and quality. Compression reduces file size by 40-70%. Format conversion to WebP or AVIF saves another 25-35% for compatible browsers.
The component inspects Accept headers automatically. Chrome supports WebP, Safari supports JPEG, and browsers supporting AVIF receive that format first, all from a single <Image> tag in your code. This negotiation happens transparently without conditional logic.
Lazy loading runs by default. Images below the fold wait until they approach the viewport, cutting initial page weight. For hero banners above the fold, add the priority prop to load immediately and improve Largest Contentful Paint.
1<Image
2 src="https://my-strapi-instance.com/uploads/foo.jpg"
3 alt="Hero shot"
4 width={1600}
5 height={900}
6 quality={85}
7 priority
8 placeholder="blur"
9/>The workflow stays identical whether files live in cloud storage, self-hosted servers, or http://localhost:1337 during development.
Whitelist your domain in next.config.js once, then reference any image from that source. Next.js generates srcSet entries for 1x, 2x, and 3x device-pixel ratios automatically, serving appropriately sized files to Retina displays without over-delivering bytes to standard screens.
Performance Benefits of the Next.js Image Component
Images remain the heaviest resource on most pages, so optimizing them has an outsized impact on Core Web Vitals. Search engines use these metrics to rank pages, making optimization a direct SEO factor.
- Largest Contentful Paint (LCP): Smaller files plus the
priorityprop mean faster hero loading. A 2MB unoptimized hero image might take 4.2 seconds to load on a mobile connection, failing Google's 2.5-second threshold. The same image optimized through the Next.js Image component drops to 180KB and loads in 1.8 seconds—comfortably passing Core Web Vitals requirements. - Cumulative Layout Shift (CLS): Reserved dimensions—or
filllayout inside a styled container—eliminate jarring reflows as assets arrive. When browsers know image dimensions before files load, they reserve the correct space. Content doesn't jump as images appear, keeping CLS scores near zero and preventing users from clicking wrong buttons when layouts shift unexpectedly. - Total page weight: Fewer kilobytes mean faster load times for users on slow networks and reduced bandwidth costs. A blog post with ten 1.5MB images delivers 15MB without optimization. With the Next.js image component, the same page serves 2-3MB—an 80% reduction that matters significantly on mobile connections.
Next.js caches the first optimization. A photography portfolio with 500 gallery images triggers compression once per variant, every subsequent visitor receives cached results from the edge.
Upload a new hero image at noon, and Next.js processes it on the first request, then serves the optimized version to all future traffic without re-compression.
How to Optimize Images in Next.js?
The following steps connect the Next.js Image component to Strapi's remote assets while maintaining optimal Core Web Vitals scores.
Configure Next.js for Remote Images
Next.js blocks external images by default. Without explicit configuration, the Image Optimization API could be abused to process arbitrary images from untrusted sources. You must whitelist every domain where Strapi serves assets.
Use the remotePatterns configuration:
1// next.config.js
2/** @type {import('next').NextConfig} */
3module.exports = {
4 images: {
5 remotePatterns: [
6 // Local development
7 {
8 protocol: 'http',
9 hostname: 'localhost',
10 port: '1337',
11 pathname: '/uploads/**',
12 },
13 // Strapi Cloud
14 {
15 protocol: 'https',
16 hostname: '**.strapiapp.com',
17 pathname: '/uploads/**',
18 },
19 // Production CDN
20 {
21 protocol: process.env.STRAPI_ASSET_PROTOCOL || 'https',
22 hostname: process.env.STRAPI_ASSET_HOSTNAME,
23 pathname: '/uploads/**',
24 },
25 ],
26 formats: ['image/avif', 'image/webp'],
27 },
28};Each pattern requires four fields. protocol accepts http or https—use http only for local development to avoid mixed-content warnings. hostname takes exact domains like cdn.example.com or wildcard patterns like **.strapiapp.com that match any subdomain.
The double asterisk syntax matches multiple subdomain levels. Omit port for standard ports (80/443), but specify it for local development where Strapi runs on 1337. pathname restricts optimization to specific directories—/uploads/** limits processing to Strapi's media library.
The formats array controls output priority. Browsers supporting AVIF receive it first since it compresses 20-30% better than WebP. Browsers without AVIF support fall back to WebP, then JPEG or PNG. Next.js negotiates format through Accept headers automatically.
Next.js evaluates patterns sequentially. Multiple patterns coexist without conflicts, so development, staging, and production domains all work simultaneously. Use environment variables for production hostnames to keep configuration flexible across deployments.
Three configuration mistakes cause failures. First, forgetting the port field for local development—http://localhost/uploads/** won't match http://localhost:1337/uploads/image.jpg without port: '1337'.
Second, mixing the deprecated images. domains array with remotePatterns creates conflicts—use only remotePatterns. Third, configuration changes require a dev server restart to take effect.
Verify the configuration by restarting your development server, loading a page with remote images, and opening DevTools → Network. Optimized images appear as /_next/image?url=...&w=...&q=... requests, not direct Strapi URLs.
A 200 response confirms optimization is active. Check the Content-Type header—it should show image/webp or image/avif, not the original format. If you see 403 errors on /_next/image requests, patterns are too restrictive. If images load directly from Strapi without /_next/image requests, patterns aren't matching.
Fetch Image Data from Your Strapi CMS API
The component needs four data points: url, width, height, and alternativeText. Strapi exposes all through its REST API with populate=*:
1curl "http://localhost:1337/api/photos?populate=*"The response structure:
1{
2 "data": [{
3 "attributes": {
4 "photo": {
5 "data": {
6 "attributes": {
7 "formats": {
8 "thumbnail": { "url": "/uploads/sunrise_thumb.jpg", "width": 150, "height": 100 },
9 "small": { "url": "/uploads/sunrise_small.jpg", "width": 500, "height": 333 },
10 "medium": { "url": "/uploads/sunrise_medium.jpg", "width": 750, "height": 500 },
11 "large": { "url": "/uploads/sunrise_large.jpg", "width": 1000, "height": 667 }
12 },
13 "url": "/uploads/sunrise.jpg",
14 "alternativeText": "Sunrise over the mountains",
15 "width": 2000,
16 "height": 1333
17 }
18 }
19 }
20 }
21 }]
22}Each field serves a specific purpose. The URL points to the original uploaded file. Width and height are mandatory for the Image component, they prevent Cumulative Layout Shift by letting the browser reserve space before the file loads. alternativeText provides screen readers with descriptive text and gives search engines indexable content.
The formats object contains Strapi's auto-generated variants. When content editors upload a high-resolution image through the Media Library, Strapi automatically creates thumbnail (156px), small (500px), medium (750px), and large (1000px) versions.
This happens immediately on upload without build steps or manual intervention. You can reference these variants for responsive delivery—hero banners pull large, article thumbnails pull small, and galleries pull medium.
Transform this nested structure with a utility function:
1// lib/mapStrapiImage.js
2const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';
3
4export function mapStrapiImage(image, preferredFormat = 'large') {
5 if (!image?.data) return null;
6
7 const { url, width, height, alternativeText, formats } = image.data.attributes;
8 const format = formats?.[preferredFormat] || {};
9
10 return {
11 src: `${STRAPI_URL}${format.url || url}`,
12 alt: alternativeText || '',
13 width: format.width || width,
14 height: format.height || height,
15 formats: formats,
16 };
17}The utility flattens Strapi's nested response into props the Image component expects. The preferredFormat parameter lets you choose which variant to use based on context, pass 'small' for thumbnails or 'large' for featured images.
The function handles missing formats gracefully by falling back to the original dimensions when a specific size doesn't exist.
Use the helper in your data-fetching logic:
1// pages/index.js
2import { mapStrapiImage } from '@/lib/mapStrapiImage';
3
4export async function getStaticProps() {
5 const res = await fetch('http://localhost:1337/api/photos?populate=*');
6 const json = await res.json();
7
8 const photos = json.data.map((item) => ({
9 id: item.id,
10 image: mapStrapiImage(item.attributes.photo, 'medium'),
11 }));
12
13 return { props: { photos } };
14}Always use absolute URLs. Strapi returns relative paths like /uploads/image.jpg, which work in the admin panel but fail in Next.js without the domain prepended. Set NEXT_PUBLIC_STRAPI_URL to your production domain or CDN for deployed environments.
Make alternativeText mandatory in your Strapi content model. Screen readers depend on descriptive alt text, and search engines use it to understand image content. Empty alt text hurts accessibility scores and SEO performance—require editors to fill this field during upload.
Implement the Next.js Image Component
With data properly mapped, render optimized images throughout your application:
1import Image from 'next/image';
2
3export function HeroImage({ image }) {
4 return (
5 <Image
6 src={image.src}
7 alt={image.alt}
8 width={image.width}
9 height={image.height}
10 quality={90}
11 priority
12 placeholder="blur"
13 blurDataURL={`${image.src}?w=16&blur=20`}
14 />
15 );
16}The priority prop loads the image immediately instead of lazy loading. Use it exclusively for above-the-fold content that contributes to Largest Contentful Paint—typically one hero image per page. Marking multiple images as priority defeats lazy loading and slows initial page load.
The quality prop controls compression. Higher values preserve detail but increase file size. Use 85-90 for hero images where visual impact matters. Use 70-75 for thumbnails and grids where slight softness is acceptable. The default value of 75 works well for most content images.
The placeholder="blur" prop shows a low-resolution preview while the full image loads. The blurDataURL accepts a base64 string or a query-parameter-modified URL that returns a tiny version. This improves perceived performance, users see something immediately rather than empty space.
For galleries and secondary content below the fold:
1<Image
2 src={thumb.url}
3 alt={image.alt}
4 width={thumb.width}
5 height={thumb.height}
6 quality={70}
7/>Omitting the priority prop enables lazy loading by default—images load only when they approach the viewport.
Numeric width and height prevent layout shifts. The browser reserves space before the image loads, eliminating content jumps that hurt Cumulative Layout Shift scores. Always provide these values when you know dimensions in advance.
For fluid layouts where dimensions aren't known, use the fill property with a positioned container:
1<div className="relative h-64">
2 <Image
3 src={image.src}
4 alt={image.alt}
5 fill
6 sizes="(max-width: 768px) 100vw, 50vw"
7 className="object-cover"
8 />
9</div>The fill property makes the image fill its parent container. The parent must have a position: relative or position: absolute and defined dimensions.
The sizes prop tells the browser how much viewport width the image occupies at different breakpoints—this helps Next.js choose the right variant from its generated srcSet. The example above means "on mobile (≤768px), the image is 100% of viewport width; on desktop, it's 50% of viewport width."
Use className="object-cover" to maintain aspect ratio while filling the container, or object-contain to fit the entire image within the container without cropping.
Build Responsive Images with Next.js
Strapi generates thumbnail (156px), small (500px), medium (750px), and large (1000px) variants automatically. Use these to build srcSet attributes that serve the right size to each device.
1export function ResponsiveImage({ image }) {
2 const { variants, src, alt, width, height } = image;
3
4 const srcSet = variants
5 .map((v) => `${v.url} ${v.width}w`)
6 .concat([`${src} ${width}w`])
7 .join(', ');
8
9 const sizes = '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw';
10
11 return (
12 <Image
13 src={src}
14 alt={alt}
15 width={width}
16 height={height}
17 srcSet={srcSet}
18 sizes={sizes}
19 loading="lazy"
20 />
21 );
22}The browser combines srcSet and sizes to determine which file to download. A mobile device with a 375px viewport and (max-width: 640px) 100vw requests the 500px variant. A 1920px desktop evaluates the sizes value, falls back to 33vw (633px), and requests the 750px variant.
Match the sizes to your actual layout. If your content column measures 800px on desktop, specify 800px. Accurate values prevent mobile devices from requesting unnecessarily large files.
Customize deviceSizes to match your breakpoints:
1// next.config.js
2module.exports = {
3 images: {
4 deviceSizes: [640, 750, 828, 1080, 1200],
5 remotePatterns: [/* ... */],
6 },
7};Align these with your Tailwind breakpoints or CSS media queries so Next.js generates the exact sizes your layout needs.
Troubleshoot Next.js Image Optimization Issues
Configuration errors break the optimization pipeline. Enable debug mode to see detailed pattern matching:
1export NEXT_DEBUG_IMAGES=trueThe terminal output shows which patterns Next.js evaluates for each image request. Look for lines indicating pattern matches or rejections—this reveals whether your hostname, protocol, or pathname is causing failures.
Common issues:
| Symptom | Cause | Fix |
|---|---|---|
| Broken image icon | Hostname mismatch in remotePatterns | Verify hostname matches exactly—cdn.example.com won't match www.cdn.example.com |
403 Forbidden on /_next/image | Pattern too restrictive | Check pathname matches actual upload directory—/uploads/** vs /media/** |
| "Module not found" error | Static import on remote URL | Remove import statement, fetch URL from API instead |
| 404 on first request | Protocol mismatch | Strapi serves HTTPS, but the pattern specifies HTTP—align both |
Images disappear after the next export | Static export bypasses optimizer | Use next start for SSR or configure a custom loader |
Protocol mismatches cause silent failures. If Strapi serves images over HTTPS but your remotePatterns entry specifies HTTP, browsers show broken image icons without error messages in the console. The Network panel shows the original Strapi URL attempted directly, not proxied through /_next/image.
Verify Your Image Optimization
In DevTools → Performance, record a page load and locate the LCP marker. It should point to /_next/image?url=... rather than direct Strapi URLs like https://your-strapi-instance.com/uploads/image.jpg—if it shows the Strapi domain directly, optimization isn't engaging.
Check the Network tab during page load:
- Compression verification: Compare "Transferred" vs "Size" columns. A 2MB image with 400KB transferred indicates 80% compression. If both values match, Next.js isn't processing the file.
- Format verification: Click any image request and check Response Headers.
Content-type: image/webporimage/avifconfirms modern format delivery.Content-type: image/jpegmeans the browser doesn't support modern formats, or optimization failed. - Lazy loading verification: Scroll slowly with the Network panel open. Images should appear in the request list only when they are near the viewport. If all images load immediately on page load, the
priorityprop might be overused, or lazy loading isn't active. - Optimization parameters: Right-click optimized image URLs. They should include
w(width) andq(quality) query parameters like/_next/image?url=...&w=750&q=75. Missing parameters indicate the optimizer isn't engaged.
Strapi Cloud users see CF-Cache-Status or similar CDN headers in responses. Self-hosted instances gain performance by adding CloudFront or Cloudflare in front of /uploads for edge caching. This creates two optimization layers—Strapi's CDN handles distribution, Next.js handles format conversion and compression.
Ship Fast-Loading Pages Without Manual Image Processing with Strapi
Strapi generates thumbnail, small, medium, and large variants automatically on upload. Next.js intercepts these URLs, compresses files by 40-70%, converts to WebP or AVIF, and caches results at the edge. C
onfigure remotePatterns once in next.config.js, and every subsequent image flows through the optimization pipeline automatically. This workflow stays identical whether you self-host Strapi, use Strapi Cloud, or run localhost during development.
Lighthouse reports confirm the impact: 60-80% file size reductions, LCP under 2.5 seconds, and zero layout shift. For self-hosted instances, add CloudFront or Cloudflare in front of /uploads to compound these caching benefits. Strapi delivers content, Next.js optimizes delivery. Your Core Web Vitals stay green as your media library grows.
<cta title="Try the Live Demo" text=" Strapi Launchpad demo comes with Strapi 5 in the back, Next.js 14, TailwindCSS, and Aceternity UI in the front." buttontext=" Start your demo" buttonlink="https://strapi.io/demo"></cta>