| 1 | # Image Optimization |
| 2 | |
| 3 | Use `next/image` for automatic image optimization. |
| 4 | |
| 5 | ## Always Use next/image |
| 6 | |
| 7 | ```tsx |
| 8 | // Bad: Avoid native img |
| 9 | <img src="/hero.png |
| 10 | |
| 11 | // Good: Use next/image |
| 12 | import Image from "next/image |
| 13 | <Image src=" |
| 14 | ``` |
| 15 | |
| 16 | ## Required Props |
| 17 | |
| 18 | Images need explicit dimensions to prevent layout shift: |
| 19 | |
| 20 | ```tsx |
| 21 | // Local images - dimensions inferred automatically |
| 22 | import heroImage from './hero.png |
| 23 | <Image src={ |
| 24 | |
| 25 | // Remote images - must specify width/height |
| 26 | <Image src=" |
| 27 | |
| 28 | // Or use fill for parent-relative sizing |
| 29 | <div style={{ |
| 30 | <Image src= |
| 31 | </div> |
| 32 | ``` |
| 33 | |
| 34 | ## Remote Images Configuration |
| 35 | |
| 36 | Remote domains must be configured in `next.config.js`: |
| 37 | |
| 38 | ```js |
| 39 | // next.config.js |
| 40 | module.exports = { |
| 41 | images: { |
| 42 | remotePatterns: [ |
| 43 | { |
| 44 | protocol: "https", |
| 45 | hostname: "example.com", |
| 46 | pathname: "/images/**", |
| 47 | }, |
| 48 | { |
| 49 | protocol: "https", |
| 50 | hostname: "*.cdn.com", // |
| 51 | }, |
| 52 | ], |
| 53 | }, |
| 54 | }; |
| 55 | ``` |
| 56 | |
| 57 | ## Responsive Images |
| 58 | |
| 59 | Use `sizes` to tell the browser which size to download: |
| 60 | |
| 61 | ```tsx |
| 62 | // Full-width hero |
| 63 | <Image |
| 64 | src="/hero.png" |
| 65 | alt="Hero" |
| 66 | fill |
| 67 | sizes="100vw" |
| 68 | /> |
| 69 | |
| 70 | // Responsive grid (3 columns on desktop, 1 on mobile) |
| 71 | <Image |
| 72 | src="/card.png" |
| 73 | alt="Card" |
| 74 | fill |
| 75 | sizes="(max-width: 768px) 100vw, 33vw" |
| 76 | /> |
| 77 | |
| 78 | // Fixed sidebar image |
| 79 | <Image |
| 80 | src="/avatar.png" |
| 81 | alt="Avatar" |
| 82 | width={200} |
| 83 | height={200} |
| 84 | sizes="200px" |
| 85 | /> |
| 86 | ``` |
| 87 | |
| 88 | ## Blur Placeholder |
| 89 | |
| 90 | Prevent layout shift with placeholders: |
| 91 | |
| 92 | ```tsx |
| 93 | // Local images - automatic blur hash |
| 94 | import heroImage from './hero.png |
| 95 | <Image src={ |
| 96 | |
| 97 | // Remote images - provide blurDataURL |
| 98 | <Image |
| 99 | src="https://example.com/image.jpg" |
| 100 | alt="Hero" |
| 101 | width={800} |
| 102 | height={400} |
| 103 | placeholder="blur" |
| 104 | blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." |
| 105 | /> |
| 106 | |
| 107 | // Or use color placeholder |
| 108 | <Image |
| 109 | src="https://example.com/image.jpg" |
| 110 | alt="Hero" |
| 111 | width={800} |
| 112 | height={400} |
| 113 | placeholder="empty" |
| 114 | style={{ backgroundColor: |
| 115 | /> |
| 116 | ``` |
| 117 | |
| 118 | ## Priority Loading |
| 119 | |
| 120 | Use `priority` for above-the-fold images (LCP): |
| 121 | |
| 122 | ```tsx |
| 123 | // Hero image - loads immediately |
| 124 | <Image src=" |
| 125 | |
| 126 | // Below-fold images - lazy loaded by default (no priority needed) |
| 127 | <Image src=" |
| 128 | ``` |
| 129 | |
| 130 | ## Common Mistakes |
| 131 | |
| 132 | ```tsx |
| 133 | // Bad: Missing sizes with fill - downloads largest image |
| 134 | <Image src=" |
| 135 | |
| 136 | // Good: Add sizes for proper responsive behavior |
| 137 | <Image src=" |
| 138 | |
| 139 | // Bad: Using width/height for aspect ratio only |
| 140 | <Image src=" |
| 141 | |
| 142 | // Good: Use actual display dimensions or fill with sizes |
| 143 | <Image src=" |
| 144 | |
| 145 | // Bad: Remote image without config |
| 146 | <Image src=" |
| 147 | // Error: Invalid src prop, hostname not configured |
| 148 | |
| 149 | // Good: Add hostname to next.config.js remotePatterns |
| 150 | ``` |
| 151 | |
| 152 | ## Static Export |
| 153 | |
| 154 | When using `output: 'export'`, use `unoptimized` or custom loader: |
| 155 | |
| 156 | ```tsx |
| 157 | // Option 1: Disable optimization |
| 158 | <Image src=" |
| 159 | |
| 160 | // Option 2: Global config |
| 161 | // next.config.js |
| 162 | module.exports = { |
| 163 | output: "export", |
| 164 | images: { unoptimized: true |
| 165 | }; |
| 166 | |
| 167 | // Option 3: Custom loader (Cloudinary, Imgix, etc.) |
| 168 | const cloudinaryLoader = ( |
| 169 | return `https://res.cloudinary.com/demo/image/upload/w_${width |
| 170 | }; |
| 171 | |
| 172 | <Image loader={ |
| 173 | ``` |