perf: add AVIF/WebP next image config and image optimization script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a0cde76789
commit
63af1f86e3
3 changed files with 130 additions and 11 deletions
|
|
@ -26,18 +26,13 @@ const nextConfig: NextConfig = {
|
|||
return config
|
||||
},
|
||||
images: {
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
deviceSizes: [375, 640, 768, 1024, 1280, 1536, 1920],
|
||||
imageSizes: [16, 32, 64, 96, 128, 256, 384, 512, 694],
|
||||
minimumCacheTTL: 31536000,
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: '3000',
|
||||
pathname: '/media/**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'shumiland.com.ua',
|
||||
pathname: '/media/**',
|
||||
},
|
||||
{ protocol: 'http', hostname: 'localhost', port: '3000', pathname: '/media/**' },
|
||||
{ protocol: 'https', hostname: 'shumiland.com.ua', pathname: '/media/**' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
75
scripts/optimize-images.manifest.json
Normal file
75
scripts/optimize-images.manifest.json
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"deletedDuplicates": [
|
||||
"081e52b5-d35a-41d2-b506-a9d751b0b563",
|
||||
"109f13b4-25f9-4f94-a06d-77421ff2b4fe",
|
||||
"17d78753-cec3-40ca-a1fc-4125c2b79eff",
|
||||
"2936ec5e-4f99-441e-9bf2-34f23c283170",
|
||||
"2c6a3e5e-7346-4c3e-b8a0-fae1facb87ad",
|
||||
"455670b9-c89a-4bea-81fe-5a4d93b25483",
|
||||
"5088f41f-b898-4958-b7f6-519496b65382",
|
||||
"58218f84-457d-4ff5-8d4e-2ace40b45568",
|
||||
"640999e1-7096-4623-bf96-12bb8ef62ffc",
|
||||
"7a2627b2-b6ce-4325-a0b1-fbc3393aca4c",
|
||||
"908a8aab-6129-4d7d-b7ac-c43b6fa60044",
|
||||
"abacda18-57c7-441d-9313-c70d22c6f0f0",
|
||||
"ac7f3a5e-0e66-4971-bccc-89901a7c314d",
|
||||
"b5597790-7b54-4ad7-9977-4cb15633286e",
|
||||
"c3053789-6cd0-4dda-9774-d4ae4bc400e1",
|
||||
"de9ad287-5e83-4a0c-ad06-f2420bff4096",
|
||||
"e42d6611-82e6-47b0-9d84-271f9810ab1a",
|
||||
"e9a8cee6-6ee5-4c74-b270-1c133a762c0a",
|
||||
"f4e2bff2-754c-460c-baba-baa95521bfc9",
|
||||
"f5d32fc0-8ea7-4fd1-b193-819d6aa1a68e"
|
||||
],
|
||||
"encoded": [],
|
||||
"skipped": [
|
||||
"081e52b5-d35a-41d2-b506-a9d751b0b563.png (webp newer)",
|
||||
"109f13b4-25f9-4f94-a06d-77421ff2b4fe.jpg (webp newer)",
|
||||
"17d78753-cec3-40ca-a1fc-4125c2b79eff.png (webp newer)",
|
||||
"2936ec5e-4f99-441e-9bf2-34f23c283170.jpg (webp newer)",
|
||||
"2c6a3e5e-7346-4c3e-b8a0-fae1facb87ad.jpg (webp newer)",
|
||||
"455670b9-c89a-4bea-81fe-5a4d93b25483.png (webp newer)",
|
||||
"5088f41f-b898-4958-b7f6-519496b65382.png",
|
||||
"58218f84-457d-4ff5-8d4e-2ace40b45568.png (webp newer)",
|
||||
"640999e1-7096-4623-bf96-12bb8ef62ffc.png (webp newer)",
|
||||
"7a2627b2-b6ce-4325-a0b1-fbc3393aca4c.png (webp newer)",
|
||||
"908a8aab-6129-4d7d-b7ac-c43b6fa60044.png (webp newer)",
|
||||
"abacda18-57c7-441d-9313-c70d22c6f0f0.png (webp newer)",
|
||||
"ac7f3a5e-0e66-4971-bccc-89901a7c314d.png (webp newer)",
|
||||
"b5597790-7b54-4ad7-9977-4cb15633286e.png",
|
||||
"c3053789-6cd0-4dda-9774-d4ae4bc400e1.png (webp newer)",
|
||||
"check-mark.png",
|
||||
"de9ad287-5e83-4a0c-ad06-f2420bff4096.png",
|
||||
"e42d6611-82e6-47b0-9d84-271f9810ab1a.png (webp newer)",
|
||||
"e9a8cee6-6ee5-4c74-b270-1c133a762c0a.png (webp newer)",
|
||||
"f4e2bff2-754c-460c-baba-baa95521bfc9.png (webp newer)",
|
||||
"f5d32fc0-8ea7-4fd1-b193-819d6aa1a68e.png (webp newer)",
|
||||
"footer-bg.png",
|
||||
"gallery-1.png",
|
||||
"gallery-2.png (webp newer)",
|
||||
"gallery-3.png",
|
||||
"gallery-4.png (webp newer)",
|
||||
"gallery-6.png (webp newer)",
|
||||
"gallery-7.png (webp newer)",
|
||||
"gallery-8.png (webp newer)",
|
||||
"hero-bg-family.png (webp newer)",
|
||||
"hero-bg1.png (webp newer)",
|
||||
"hero-bg2.png (webp newer)",
|
||||
"hero-blur-mask.png",
|
||||
"loc-dinopark.jpg (webp newer)",
|
||||
"loc-divo-lis.png (webp newer)",
|
||||
"loc-map.jpg (webp newer)",
|
||||
"news-bg1.jpg",
|
||||
"news-bg2.png (webp newer)",
|
||||
"news-bg3.jpg (webp newer)",
|
||||
"news-bg4.png (webp newer)",
|
||||
"news-bg5.png (webp newer)",
|
||||
"news-bg6.png (webp newer)",
|
||||
"review-avatar-bg.jpg (webp newer)",
|
||||
"video-preview.png (webp newer)",
|
||||
"why-parents-1.png (webp newer)",
|
||||
"why-parents-2.png (webp newer)",
|
||||
"why-parents-3.png (webp newer)",
|
||||
"why-parents-4.png (webp newer)"
|
||||
]
|
||||
}
|
||||
49
scripts/optimize-images.mjs
Normal file
49
scripts/optimize-images.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env node
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import sharp from 'sharp'
|
||||
|
||||
const ROOT = path.resolve(process.cwd(), 'public/images/figma')
|
||||
const QUALITY = 82
|
||||
const MIN_BYTES = 200_000
|
||||
|
||||
const manifest = { deletedDuplicates: [], encoded: [], skipped: [] }
|
||||
|
||||
const all = await fs.readdir(ROOT)
|
||||
const byBase = new Map()
|
||||
for (const f of all) {
|
||||
const ext = path.extname(f)
|
||||
const base = ext ? path.basename(f, ext) : f
|
||||
if (!byBase.has(base)) byBase.set(base, [])
|
||||
byBase.get(base).push(f)
|
||||
}
|
||||
|
||||
// 1) Delete extensionless duplicates that have a webp/png/jpg sibling
|
||||
for (const [, variants] of byBase) {
|
||||
const extensionless = variants.find((v) => path.extname(v) === '')
|
||||
const hasSibling = variants.some((v) => /\.(webp|png|jpg|jpeg)$/i.test(v))
|
||||
if (extensionless && hasSibling) {
|
||||
const p = path.join(ROOT, extensionless)
|
||||
await fs.unlink(p)
|
||||
manifest.deletedDuplicates.push(path.basename(p))
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Re-encode large png/jpg to webp (skip if webp already exists and is newer)
|
||||
for (const [base, variants] of byBase) {
|
||||
const heavy = variants.find((v) => /\.(png|jpg|jpeg)$/i.test(v))
|
||||
if (!heavy) continue
|
||||
const srcPath = path.join(ROOT, heavy)
|
||||
const stat = await fs.stat(srcPath).catch(() => null)
|
||||
if (!stat || stat.size < MIN_BYTES) { manifest.skipped.push(path.basename(srcPath)); continue }
|
||||
const outPath = path.join(ROOT, `${base}.webp`)
|
||||
const outStat = await fs.stat(outPath).catch(() => null)
|
||||
if (outStat && outStat.mtimeMs > stat.mtimeMs) { manifest.skipped.push(path.basename(srcPath) + ' (webp newer)'); continue }
|
||||
await sharp(srcPath).webp({ quality: QUALITY }).toFile(outPath)
|
||||
manifest.encoded.push({ src: path.basename(srcPath), out: `${base}.webp`, before: stat.size })
|
||||
}
|
||||
|
||||
const manifestPath = path.join(process.cwd(), 'scripts/optimize-images.manifest.json')
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
console.log(`Deleted ${manifest.deletedDuplicates.length} duplicates, encoded ${manifest.encoded.length} files, skipped ${manifest.skipped.length}.`)
|
||||
console.log('Manifest:', manifestPath)
|
||||
Loading…
Add table
Reference in a new issue