963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
174 lines
3.8 KiB
Markdown
174 lines
3.8 KiB
Markdown
# Image Optimization
|
|
|
|
Use `next/image` for automatic image optimization.
|
|
|
|
## Always Use next/image
|
|
|
|
```tsx
|
|
// Bad: Avoid native img
|
|
<img src="/hero.png" alt="Hero" />;
|
|
|
|
// Good: Use next/image
|
|
import Image from 'next/image';
|
|
<Image src="/hero.png" alt="Hero" width={800} height={400} />;
|
|
```
|
|
|
|
## Required Props
|
|
|
|
Images need explicit dimensions to prevent layout shift:
|
|
|
|
```tsx
|
|
// Local images - dimensions inferred automatically
|
|
import heroImage from './hero.png'
|
|
<Image src={heroImage} alt="Hero" />
|
|
|
|
// Remote images - must specify width/height
|
|
<Image src="https://example.com/image.jpg" alt="Hero" width={800} height={400} />
|
|
|
|
// Or use fill for parent-relative sizing
|
|
<div style={{ position: 'relative', width: '100%', height: 400 }}>
|
|
<Image src="/hero.png" alt="Hero" fill style={{ objectFit: 'cover' }} />
|
|
</div>
|
|
```
|
|
|
|
## Remote Images Configuration
|
|
|
|
Remote domains must be configured in `next.config.js`:
|
|
|
|
```js
|
|
// next.config.js
|
|
module.exports = {
|
|
images: {
|
|
remotePatterns: [
|
|
{
|
|
protocol: 'https',
|
|
hostname: 'example.com',
|
|
pathname: '/images/**',
|
|
},
|
|
{
|
|
protocol: 'https',
|
|
hostname: '*.cdn.com', // Wildcard subdomain
|
|
},
|
|
],
|
|
},
|
|
};
|
|
```
|
|
|
|
## Responsive Images
|
|
|
|
Use `sizes` to tell the browser which size to download:
|
|
|
|
```tsx
|
|
// Full-width hero
|
|
<Image
|
|
src="/hero.png"
|
|
alt="Hero"
|
|
fill
|
|
sizes="100vw"
|
|
/>
|
|
|
|
// Responsive grid (3 columns on desktop, 1 on mobile)
|
|
<Image
|
|
src="/card.png"
|
|
alt="Card"
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, 33vw"
|
|
/>
|
|
|
|
// Fixed sidebar image
|
|
<Image
|
|
src="/avatar.png"
|
|
alt="Avatar"
|
|
width={200}
|
|
height={200}
|
|
sizes="200px"
|
|
/>
|
|
```
|
|
|
|
## Blur Placeholder
|
|
|
|
Prevent layout shift with placeholders:
|
|
|
|
```tsx
|
|
// Local images - automatic blur hash
|
|
import heroImage from './hero.png'
|
|
<Image src={heroImage} alt="Hero" placeholder="blur" />
|
|
|
|
// Remote images - provide blurDataURL
|
|
<Image
|
|
src="https://example.com/image.jpg"
|
|
alt="Hero"
|
|
width={800}
|
|
height={400}
|
|
placeholder="blur"
|
|
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
|
|
/>
|
|
|
|
// Or use color placeholder
|
|
<Image
|
|
src="https://example.com/image.jpg"
|
|
alt="Hero"
|
|
width={800}
|
|
height={400}
|
|
placeholder="empty"
|
|
style={{ backgroundColor: '#e0e0e0' }}
|
|
/>
|
|
```
|
|
|
|
## Priority Loading
|
|
|
|
Use `priority` for above-the-fold images (LCP):
|
|
|
|
```tsx
|
|
// Hero image - loads immediately
|
|
<Image src="/hero.png" alt="Hero" fill priority />
|
|
|
|
// Below-fold images - lazy loaded by default (no priority needed)
|
|
<Image src="/card.png" alt="Card" width={400} height={300} />
|
|
```
|
|
|
|
## Common Mistakes
|
|
|
|
```tsx
|
|
// Bad: Missing sizes with fill - downloads largest image
|
|
<Image src="/hero.png" alt="Hero" fill />
|
|
|
|
// Good: Add sizes for proper responsive behavior
|
|
<Image src="/hero.png" alt="Hero" fill sizes="100vw" />
|
|
|
|
// Bad: Using width/height for aspect ratio only
|
|
<Image src="/hero.png" alt="Hero" width={16} height={9} />
|
|
|
|
// Good: Use actual display dimensions or fill with sizes
|
|
<Image src="/hero.png" alt="Hero" fill sizes="100vw" style={{ objectFit: 'cover' }} />
|
|
|
|
// Bad: Remote image without config
|
|
<Image src="https://untrusted.com/image.jpg" alt="Image" width={400} height={300} />
|
|
// Error: Invalid src prop, hostname not configured
|
|
|
|
// Good: Add hostname to next.config.js remotePatterns
|
|
```
|
|
|
|
## Static Export
|
|
|
|
When using `output: 'export'`, use `unoptimized` or custom loader:
|
|
|
|
```tsx
|
|
// Option 1: Disable optimization
|
|
<Image src="/hero.png" alt="Hero" width={800} height={400} unoptimized />;
|
|
|
|
// Option 2: Global config
|
|
// next.config.js
|
|
module.exports = {
|
|
output: 'export',
|
|
images: { unoptimized: true },
|
|
};
|
|
|
|
// Option 3: Custom loader (Cloudinary, Imgix, etc.)
|
|
const cloudinaryLoader = ({ src, width, quality }) => {
|
|
return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`;
|
|
};
|
|
|
|
<Image loader={cloudinaryLoader} src="sample.jpg" alt="Sample" width={800} height={400} />;
|
|
```
|