| name | nextjs-image-usage |
| description | Implement proper Next.js 16 Image component usage with configuration for remote domains, responsive images, and breaking changes from previous versions Use when this capability is needed. |
| metadata | {"author":"darellchua2"} |
What this skill does
- Ensures all images use Next.js 16 Image component (not img tag)
- Configures remote domains in next.config.ts for external images
- Handles responsive images with proper sizing and priority
- Migrates from old Next.js versions to Next.js 16 Image API
- Implements image optimization for performance
- Configures image domains and patterns for security
- Uses proper placeholder and blur strategies
- Handles local and remote image sources correctly
LLM Code Detection and Conversion
When an LLM proposes using <img> tags in Next.js 16 code, automatically convert them to the Next.js <Image /> component equivalent.
Detection Patterns
The LLM may propose <img> tags in these scenarios:
<img src="/image.jpg" alt="Description" />
<img src="/image.jpg" alt="Description" width="800" height="600" />
<img src="/image.jpg" alt="Description" class="rounded-lg shadow-md" />
<img src="https://cdn.example.com/image.jpg" alt="Description" />
<img src={imageUrl} alt={altText} />
Conversion Rules
Always convert <img> to <Image /> following these rules:
<img> Attribute | Next.js 16 <Image /> Equivalent | Notes |
|---|
src="/path.jpg" | src="/path.jpg" | Same (local images in public/) |
src="https://..." | src="https://..." | Must configure remotePatterns |
alt="text" | alt="text" | Required (same) |
width="800" | width={800} | Change to JS number |
height="600" | height={600} | Change to JS number |
class="name" | className="name" | JSX class -> className |
loading="lazy" | loading="lazy" | Same (default) |
style={{...}} | style={{...}} | Same object syntax |
Required Conversions
All <img> tags MUST be converted in Next.js 16:
<img src="/hero.jpg" alt="Hero banner" width="1920" height="1080" />
import Image from 'next/image'
<Image src="/hero.jpg" alt="Hero banner" width={1920} height={1080} />
Conversion Examples
Example 1: Basic img tag
<img src="/logo.png" alt="Company Logo" />
import Image from 'next/image'
<Image src="/logo.png" alt="Company Logo" width={200} height={60} />
Example 2: With styling classes
<img
src="/avatar.jpg"
alt="User Avatar"
class="rounded-full w-12 h-12 shadow-md"
width="48"
height="48"
/>
import Image from 'next/image'
<Image
src="/avatar.jpg"
alt="User Avatar"
className="rounded-full w-12 h-12 shadow-md"
width={48}
height={48}
/>
Example 3: Dynamic source
interface Props {
imageUrl: string
altText: string
}
export function ProductImage({ imageUrl, altText }: Props) {
return <img src={imageUrl} alt={altText} width={400} height={300} />
}
import Image from 'next/image'
interface Props {
imageUrl: string
altText: string
}
export function ProductImage({ imageUrl, altText }: Props) {
return <Image src={imageUrl} alt={altText} width={400} height={300} />
}
Example 4: Remote image
<img
src="https://cdn.example.com/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
import Image from 'next/image'
<Image
src="https://cdn.example.com/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
Example 5: Background image (CSS)
<div className="relative">
<img
src="/bg.jpg"
alt=""
class="absolute inset-0 object-cover"
/>
<div className="relative z-10">Content</div>
</div>
import Image from 'next/image'
<div className="relative">
<Image
src="/bg.jpg"
alt=""
fill
className="object-cover"
/>
<div className="relative z-10">Content</div>
</div>
Example 6: Gallery of images
export function Gallery({ images }: { images: string[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{images.map((src, index) => (
<img
key={index}
src={src}
alt={`Gallery image ${index + 1}`}
width={400}
height={300}
loading="lazy"
/>
))}
</div>
)
}
import Image from 'next/image'
export function Gallery({ images }: { images: string[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{images.map((src, index) => (
<Image
key={index}
src={src}
=` ${ + }`}
=
=
=
/>
))}
)
}
Automatic Conversion Checklist
When LLM proposes <img> tags, verify:
Why Convert img to Image?
Performance Benefits:
- Automatic optimization (WebP, AVIF formats)
- Responsive image generation
- Lazy loading by default
- Priority loading for critical images
- Reduced bandwidth usage
DX Benefits:
- Better TypeScript support
- Consistent API across project
- Built-in error handling
- Automatic blur placeholder support
SEO Benefits:
- Proper alt text enforcement
- Better accessibility scores
- Optimized images for faster LCP
- Core Web Vitals improvement
Common Conversion Mistakes to Avoid
Mistake 1: Keeping img for dynamic images
<Image src={dynamicUrl} alt="Dynamic" />
Mistake 2: Forgetting to import Image
<Image src="/image.jpg" alt="Image" width={800} height={600} />
import Image from 'next/image'
<Image src="/image.jpg" alt="Image" width={800} height={600} />
Mistake 3: String instead of number props
<Image src="/image.jpg" alt="Image" width="800" height="600" />
<Image src="/image.jpg" alt="Image" width={800} height={600} />
Mistake 4: Missing required props
<Image src="/image.jpg" alt="Image" />
<Image src="/image.jpg" alt="Image" width={800} height={600} />
<Image src="/image.jpg" alt="Image" fill />
When to use
Use this when:
- Displaying images in Next.js 16 applications
- LLM proposes
<img> tags that need conversion
- Migrating from Next.js 13/14/15 to Next.js 16
- Images not loading or showing placeholder icons
- Need to configure external image domains
- Implementing responsive image layouts
- Optimizing image loading performance
- Setting up image placeholders or blur effects
Common signals:
- LLM suggests
<img> tags in Next.js code
- Using
<img> tags instead of <Image /> component
- Remote images not displaying
- Console errors about "next/image" configuration
- Images appearing slowly or unoptimized
- Need to load images from external domains
- Placeholder images not working
Next.js 16 Image Component Changes
Breaking Changes from Previous Versions
| Feature | Next.js < 16 | Next.js 16+ |
|---|
| Import Path | next/image (same) | next/image (same) |
unoptimized | Supported | Removed - Use custom loader |
layout prop | 'fill', 'fixed', 'intrinsic', 'responsive' | Removed - Use fill boolean or explicit sizing |
objectFit prop | Supported | Removed - Use style={{ objectFit: ... }} |
objectPosition prop | Supported | Removed - Use style={{ objectPosition: ... }} |
| Default loader | loader() function | Simplified loader interface |
| Remote patterns | remotePatterns in config | Enhanced - More powerful matching |
Key Changes in Next.js 16
- Removed
layout prop: Use explicit width/height or fill prop instead
- Removed
objectFit and objectPosition: Use CSS style object instead
- Removed
unoptimized: Use custom loader for unoptimized images
- Enhanced remote patterns: More flexible domain and pattern matching
- Better TypeScript types: Improved type safety for Image props
Configuration
Remote Domains Configuration
Configure external image domains in next.config.ts:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '**.cloudinary.com',
},
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/static/**',
},
],
},
}
export default nextConfig
Remote Patterns Options
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
hostname: '**.example.com',
port: '',
port: '443',
pathname: '/images/**',
pathname: '/static/**.jpg',
pathname: '/uploads/**/*.png',
},
],
},
}
Domain vs. Patterns
Old Way (deprecated in Next.js 16):
const nextConfig = {
images: {
domains: ['example.com', 'cdn.example.com'],
},
}
New Way (recommended):
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
pathname: '/**',
},
],
},
}
Basic Image Usage
Import the Component
import Image from 'next/image'
Local Images
Import and use local images from public/ directory:
import Image from 'next/image'
import heroImage from '@/public/hero.jpg'
export function HeroSection() {
return (
<div className="relative">
<Image
src={heroImage}
alt="Hero banner showing our product"
width={1920}
height={1080}
priority
/>
</div>
)
}
Remote Images
Use remote URLs with configured domains:
import Image from 'next/image'
export function UserProfile({ userId }: { userId: string }) {
return (
<div className="flex items-center gap-4">
<Image
src={`https://cdn.example.com/avatars/${userId}.jpg`}
alt={`User ${userId} avatar`}
width={64}
height={64}
className="rounded-full"
/>
<div>User {userId}</div>
</div>
)
}
Dynamic Remote Images
import Image from 'next/image'
interface ProductProps {
id: string
name: string
imageUrl: string
}
export function ProductCard({ id, name, imageUrl }: ProductProps) {
return (
<div className="product-card">
<Image
src={imageUrl}
alt={name}
width={400}
height={300}
className="product-image"
loading="lazy"
/>
<h3>{name}</h3>
</div>
)
}
Image Props (Next.js 16)
Required Props
| Prop | Type | Description |
|---|
src | string or StaticImageData | Image source (URL or import) |
alt | string | Alt text for accessibility (required) |
width | number | Width in pixels (required unless fill) |
height | number | Height in pixels (required unless fill) |
Common Props
| Prop | Type | Default | Description |
|---|
fill | boolean | false | Fill parent container |
priority | boolean | false | Load image with high priority |
loading | 'lazy' | 'eager' | 'lazy' | Loading strategy |
quality | number | 75 | Image quality (1-100) |
placeholder | 'blur' | 'empty' | undefined | Placeholder type |
blurDataURL | string | - | Base64 blur data URL |
sizes | string | - | Responsive sizes string |
className | string | - | CSS class names |
style | CSSProperties | - | Inline styles |
Responsive Images
Using Sizes Prop
import Image from 'next/image'
export function ResponsiveBanner() {
return (
<Image
src="/banner.jpg"
alt="Promotional banner"
width={1920}
height={600}
// Load different sizes based on viewport
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="w-full"
/>
)
}
Sizes String Format
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
sizes="50vw"
sizes="100vw"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
Responsive Width/Height
export function ResponsiveImage({ src }: { src: string }) {
return (
<div className="relative w-full aspect-video">
<Image
src={src}
alt="Responsive image"
fill
className="object-cover"
sizes="100vw"
/>
</div>
)
}
Placeholder Images
Blur Placeholder
import Image from 'next/image'
export function BlurredImage({
src,
blurData
}: {
src: string
blurData: string
}) {
return (
<Image
src={src}
alt="Image with blur placeholder"
placeholder="blur"
blurDataURL={blurData}
width={800}
height={600}
/>
)
}
Generate Blur Data
import { getImageProps } from 'next/image'
export function getBlurData(src: string) {
const props = getImageProps(src)
return props.blurDataURL || ''
}
Empty Placeholder
export function EmptyPlaceholderImage({ src }: { src: string }) {
return (
<Image
src={src}
alt="Image with empty placeholder"
placeholder="empty"
width={800}
height={600}
/>
)
}
Fill Images
Background Image
export function BackgroundImage({ src }: { src: string }) {
return (
<div className="absolute inset-0 -z-10">
<Image
src={src}
alt="Background"
fill
className="object-cover"
priority
/>
</div>
)
}
Card Cover Image
export function ImageCard({ imageSrc, title }: { imageSrc: string; title: string }) {
return (
<div className="relative h-64 w-full overflow-hidden rounded-lg">
<Image
src={imageSrc}
alt={title}
fill
className="object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-4">
<h3 className="text-white text-lg font-semibold">{title}</h3>
</div>
</div>
)
}
Custom Loaders
Unoptimized Images
import Image, { ImageLoader } from 'next/image'
const unoptimizedLoader: ImageLoader = ({ src, width, quality }) => {
return `${src}?w=${width}&q=${quality || 75}`
}
export function DynamicImage({ src }: { src: string }) {
return (
<Image
src={src}
alt="Dynamic image"
loader={unoptimizedLoader}
width={800}
height={600}
/>
)
}
CDN Loader
import Image, { ImageLoader } from 'next/image'
const cdnLoader: ImageLoader = ({ src, width, quality }) => {
return `https://cdn.example.com/images/${src}?w=${width}&q=${quality || 75}&fmt=webp`
}
export function CDNImage({ imagePath }: { imagePath: string }) {
return (
<Image
src={imagePath}
alt="CDN image"
loader={cdnLoader}
width={600}
height={400}
/>
)
}
Common Patterns
Avatar Component
import Image from 'next/image'
export function Avatar({
src,
size = 48,
alt = 'User avatar'
}: {
src: string
size?: number
alt?: string
}) {
return (
<div className="relative overflow-hidden rounded-full">
<Image
src={src}
alt={alt}
width={size}
height={size}
className="rounded-full"
/>
</div>
)
}
Logo Component
import Image from 'next/image'
export function Logo({
src,
height = 40,
alt = 'Company logo'
}: {
src: string
height?: number
alt?: string
}) {
return (
<div className="relative h-[40px]">
<Image
src={src}
alt={alt}
height={height}
width={height * 2} // Assuming 2:1 aspect ratio
className="object-contain"
/>
</div>
)
}
Gallery Image
import Image from 'next/image'
import { useState } from 'react'
export function GalleryImage({
src,
caption
}: {
src: string
caption: string
}) {
const [isLoaded, setIsLoaded] = useState(false)
return (
<figure className="relative">
<Image
src={src}
alt={caption}
width={800}
height={600}
className={`transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setIsLoaded(true)}
loading="lazy"
/>
{!isLoaded && (
<div className="absolute inset-0 bg-gray-200 animate-pulse" />
)}
{caption}
)
}
Migration Guide
From Next.js 15 to 16
Remove Layout Prop
<Image
src="/image.jpg"
alt="Image"
layout="responsive"
width={800}
height={600}
/>
<Image
src="/image.jpg"
alt="Image"
width={800}
height={600}
/>
Remove objectFit/ObjectPosition Props
<Image
src="/image.jpg"
alt="Image"
objectFit="cover"
objectPosition="center"
fill
/>
<Image
src="/image.jpg"
alt="Image"
fill
style={{
objectFit: 'cover',
objectPosition: 'center'
}}
/>
Replace Unoptimized Prop
<Image
src="/image.jpg"
alt="Image"
unoptimized
/>
const unoptimizedLoader: ImageLoader = ({ src }) => src
<Image
src="/image.jpg"
alt="Image"
loader={unoptimizedLoader}
width={800}
height={600}
/>
Update Domain Configuration
const nextConfig = {
images: {
domains: ['example.com', 'cdn.example.com'],
},
}
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/**',
},
],
},
}
Best Practices
Accessibility
- Always provide alt text: Describe the image for screen readers
- Alt text should be descriptive: Not just "image" or "photo"
- Decorative images: Use empty alt text
alt=""
- Avoid "Image of": Alt text should start with the subject
<Image src="/dog.jpg" alt="Golden retriever playing in park" />
<Image src="/dog.jpg" alt="Image of a dog" />
Performance
- Use
priority for above-fold images: Load critical images first
- Use
loading="lazy" for below-fold: Defer loading off-screen images
- Provide proper sizes: Help browser select correct image size
- Optimize image quality: Use quality prop (75-85 recommended)
- Use blur placeholders: Improve perceived performance
<Image src="/hero.jpg" alt="Hero" priority />
<Image src="/gallery.jpg" alt="Gallery" loading="lazy" />
Security
- Configure remote patterns: Only allow trusted domains
- Use specific paths: Don't allow wildcard
/** for all paths
- HTTPS only: Always use HTTPS for remote patterns
- Validate image URLs: Sanitize user-provided image URLs
{
protocol: 'https',
hostname: 'cdn.trusted.com',
pathname: '/images/**',
}
{
protocol: 'https',
hostname: '**',
pathname: '/**',
}
Responsive Design
- Use sizes prop: Ensure correct image size loads
- Test on multiple viewports: Verify images load properly
- Use fill for flexible layouts: Match parent container
- Maintain aspect ratio: Avoid layout shifts
Common Issues and Solutions
Remote Images Not Loading
Issue: External images showing placeholder or error
Solution: Add domain to remotePatterns in next.config.ts
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'your-domain.com',
pathname: '/**',
},
],
},
}
Type Errors on width/height
Issue: TypeScript error when width/height is unknown
Solution: Use fill prop or define explicit dimensions
<Image src="/image.jpg" alt="Image" />
<Image src="/image.jpg" alt="Image" width={800} height={600} />
<Image src="/image.jpg" alt="Image" fill />
Layout Shift Issues
Issue: Page content jumps when images load
Solution: Reserve space for images with proper dimensions or fill
<Image src="/image.jpg" alt="Image" width={800} height={600} />
<div className="relative aspect-video">
<Image src="/image.jpg" alt="Image" fill />
</div>
Slow Image Loading
Issue: Images taking too long to load
Solution: Use priority, quality, and blur placeholders
<Image
src="/critical.jpg"
alt="Critical image"
priority
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
width={800}
height={600}
/>
Blur Data Not Working
Issue: Blur placeholder not showing
Solution: Ensure blurDataURL is valid base64 string
import { getPlaiceholder } from 'plaiceholder'
const { base64 } = await getPlaiceholder(800, 600)
<Image
src="/image.jpg"
alt="Image"
width={800}
height={600}
placeholder="blur"
blurDataURL={base64}
/>
Images Not Optimizing
Issue: Images not being optimized by Next.js
Solution: Ensure images are in public/ or properly configured
import image from '@/public/image.jpg'
const nextConfig = {
images: {
remotePatterns: [{ protocol: 'https', hostname: 'example.com', pathname: '/**' }],
},
}
Verification Checklist
After implementation, verify:
Configuration
Image Usage
Performance
Accessibility
TypeScript
Converted and distributed by TomeVault — claim your Tome and manage your conversions.