Vercel Görsel Optimizasyon Limitleri ve Kullanımı Düşürmek
Liste sayfasında on yazı duran bir blog ağır bir site gibi görünmez. Buna rağmen Vercel'in Hobby planında ilk tükenen sayaç genelde görsel optimizasyonu olur — bant genişliğinden de, fonksiyon çağrılarından da önce. Hobby ayda 5.000 görsel dönüşümü içeriyor ve küçük kapak görselleriyle dolu tek bir liste sayfası, ekranda hiçbir şey ters görünmeden yüzlerce olası önbellek girdisi açabiliyor.
Hobby limitlerinin kendisi Hobby planı yazısında duruyor. Bu yazı mekanizmayla ilgili: Vercel neyi sayıyor, tek bir görsel neden on beşe dönüşüyor ve next.config.ts içindeki hangi değerler bu sayıyı gerçekten düşürüyor. Aşağıdaki bütün varsayılanlar hafızadan değil, kurulu next@16.2.12 paketinin içinden okundu.
Tek sayaç değil, üç sayaç
Görsel optimizasyonu üç ayrı sayaç üzerinden faturalanıyor ve üçü farklı davranıyor:
| Sayaç | Hobby'de dahil | Neyi tetikliyor |
|---|---|---|
| Görsel dönüşümü | 5.000 / ay | Her önbellek MISS'i ve her STALE yenilemesi |
| Önbellek okuması | 300.000 / ay | Küresel önbellekten bayt çekmek, 8 KB'lık birimlerle |
| Önbellek yazması | 100.000 / ay | Dönüştürülmüş baytı küresel önbelleğe yazmak, 8 KB'lık birimlerle |
Sayılardan daha önemli iki ayrıntı var. Dönüşüm, kaynak görsel başına değil, önbellek ıskası başına faturalanıyor. Ve önbellek okuması her HIT'te faturalanmıyor: bir görsel aynı bölgede yakın zamanda istenmişse o bölgeden servis ediliyor ve bu sayaca hiç dokunmuyor.
Bunun pratik bir sonucu var. Kötü şekil, yazmanın okumaya yakın olduğu şekildir. Bir kez yazılıp bir kez okunan dönüşümler üretiyorsanız, kimsenin tekrar kullanmadığı varyantları üretmek için para ödüyorsunuz.
Bütçe, önbellek anahtarında
Vercel optimizasyon API'sinin önbellek anahtarını belgeliyor ve buradaki en işe yarar bilgi bu. Yerel bir görsel için anahtar şunlardan oluşuyor:
- proje kimliği
q— istenen kalitew— istenen piksel genişliğiurl— yerel görsellerde dosyanın içerik özeti, uzak görsellerde tam URL- normalize edilmiş
Acceptistek başlığı
Yani dönüşüm bütçeniz yayına aldığınız görsel sayısı değil. Tarayıcıların fiilen istediği farklı (görsel, genişlik, kalite, format) kombinasyonlarının sayısı. Vercel kendi rehberinde bunu açıkça örnekliyor: 2 format, 2 kalite ve 8 genişlikte istenen tek bir hero görseli 32 önbellek girdisi demek.
Bu listede sayfa sayısı yok. Dolayısıyla çözüm hiçbir zaman "daha az görsel kullan" değil — "daha küçük bir varyant uzayı üret".
Tek görsel aslında kaç varyant istiyor
Genişlik uzayını sizin yerinize next/image belirliyor, ürettiği srcset üzerinden. Bu uzayın ne kadar geniş olduğunu tahmin etmeniz gerekmiyor, çünkü getImageProps herkese açık bir API ve çıktısını yazdırabiliyorsunuz. Aşağıdakini proje kökünde next@16.2.12 üzerinde çalıştırdım:
import { getImageProps } from 'next/image.js'
function say(etiket, opts) {
const { props } = getImageProps(opts)
const urls = props.srcSet.split(',').map((s) => s.trim())
console.log(etiket, '->', urls.length, 'girdi')
console.log(urls.map((u) => u.match(/[?&]w=(\d+)/)[1]).join(', '))
}
say('kapak, sizes 100vw sonra 768px', {
src: '/assets/cover.png', alt: '', width: 1200, height: 630,
sizes: '(max-width: 768px) 100vw, 768px',
})
say('kapak, sizes yok', {
src: '/assets/cover.png', alt: '', width: 1200, height: 630,
})
say('128px küçük görsel', {
src: '/assets/cover.png', alt: '', fill: true, sizes: '128px',
})Import'taki .js uzantısı yalnızca bu betik paketleyicinin dışında, düz bir Node ESM betiği olarak çalıştığı için gerekiyor. Çıktı:
kapak, sizes 100vw sonra 768px -> 8 girdi
640, 750, 828, 1080, 1200, 1920, 2048, 3840
kapak, sizes yok -> 2 girdi
1200, 3840
128px küçük görsel -> 15 girdi
32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840Üçüncü satırı bir daha okuyun. Hiçbir zaman 128 CSS pikselinden geniş gösterilmeyen bir küçük görsel, tarayıcıya 3840'a kadar uzanan on beş genişlik sunuyor. Bu on beş, iki varsayılan dizinin toplamının tam olarak kendisi: imageSizes 7, deviceSizes 8 girdi taşıyor.
Birinci durum da daha sessiz biçimde neredeyse aynı derecede savurgan. Görsel kendi sizes değeriyle 768 pikselde sınırlanmış, ama 1920, 2048 ve 3840 hâlâ menüde duruyor.
Maliyet konusunda net olalım: on beş URL için fatura kesilmiyor. Tarayıcı bir tanesini seçiyor. Ama o liste kaç farklı w değerinin erişilebilir olduğunu tanımlıyor ve yeterince farklı cihaz, piksel oranı ve tarayıcı botu geçtiğinde bunların iyi bir kısmına gerçekten erişiliyor — her biri ayrı bir önbellek anahtarı, her birinin ilk isteği bir dönüşüm.
deviceSizes ve imageSizes'ı yerleşimine göre kırp
16.2.12'deki varsayılanlar şunlar:
// next.config.ts — varsayılanlar, referans olsun diye
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [32, 48, 64, 96, 128, 256, 384],
}deviceSizes, görüntü alanının bir oranını kaplayan görseller için kullanılıyor. imageSizes ise yalnızca sizes özelliği verilmiş görseller için devreye giriyor ve içindeki her değer deviceSizes'ın en küçük değerinden küçük olmalı.
Okuma kolonu 768 piksel civarında olan bir içerik sitesinin hiçbir şeyin 3840 piksellik sürümüne ihtiyacı yok; 128 piksellik bir avatarın da yedi aday genişliğe. Dizileri yerleşiminizin gerçekten üretebileceği genişliklere göre ayarlayın, 2x payını da hesaba katarak:
images: {
deviceSizes: [640, 828, 1080, 1536],
imageSizes: [128, 256],
}Bu, 15 yerine 6 erişilebilir genişlik demek. Tasarruf bu orandan geliyor ve yalnız dönüşümlere değil önbellek yazmalarına da yansıyor.
Bir uyarı: bu dizileri fazla kısarsanız bir cihaz kendi alanından dar bir görsel alır ve onu büyütür. En büyük değeri, en geniş gösterdiğiniz görselin kabaca iki katında tutun.
Tek formatta kal
Varsayılan tek format:
images: { formats: ['image/webp'] }AVIF eklemek saf kazanç gibi duruyor — Next.js onu WebP'ye göre yaklaşık %20 daha küçük diye belgeliyor — ama kodlaması %50 daha uzun sürüyor ve burada daha önemlisi, Next.js her formatı ayrı önbellekliyor. İki format, iki farklı normalize Accept değeri, yani genişlik uzayının ikiyle çarpılması demek. 5.000 dönüşümlü bir planda, baytlardan %20 indirim için kardinaliteyi ikiye katlamak çoğunlukla yanlış takas. WebP'de bırakın.
minimumCacheTTL'i yükselt, varsayılana güvenme
Dokümantasyonun kendisiyle çeliştiği yer burası, o yüzden varsaymak yerine kontrol edin. Vercel'in görsel optimizasyon sayfası uzak görseller için varsayılan TTL'i 3600 saniye diyor. Kurulu paketteki varsayılan bunun dört katı:
// node_modules/next/dist/shared/lib/image-config.js
minimumCacheTTL: 14400,Her iki durumda da bir blog kapağı dört saatte bir değişmiyor. Görselleriniz fiilen değişmiyorsa bunu söyleyin:
images: { minimumCacheTTL: 2678400 } // 31 günDaha az sona erme, daha az STALE yenilemesi; önlenen her yenileme harcamadığınız bir dönüşüm. Geçerli TTL, bu değer ile kaynak görselin Cache-Control max-age'i arasında büyük olanı oluyor. Statik import'lar daha da iyisini yapıyor: dosya içeriğini özetleyip değişmez olarak önbellekleniyorlar.
Ama bedeli gerçek ve Next.js bunu doğrudan yazıyor: bu önbelleği geçersiz kılmanın bir yolu yok. Yeniden dağıtım temizlemiyor. Vercel'de CDN önbelleğini elle ya da programatik olarak purge edebiliyorsunuz; kendi sunucunuzda ya src'yi değiştiriyor ya da <distDir>/cache/images dizinini siliyorsunuz. Yani 31 günlük TTL içerik görselleri için doğru, kullanıcının yerinde değiştirebildiği hiçbir şey için doğru değil.
qualities listesini kilitle
Next.js 16'da qualities zorunlu bir izin listesine dönüştü ve dokümanda verilen gerekçe tam olarak faturayı ilgilendiren gerekçe: liste olmadan herkes rastgele kalite değerleri isteyip sizin hiç niyet etmediğiniz dönüşümleri üretebiliyor. Varsayılan tek değer:
images: { qualities: [75] }Bir ya da iki değerde tutun. quality özelliği listede olmayan bir istek en yakın izinli değere yuvarlanıyor; optimizasyon uç noktasına izinli olmayan bir kaliteyle doğrudan gidilirse 400 dönüyor. Tek izinli kalite, kardinalite tablosunda yüz satır yerine bir satır demek.
Pattern'leri kapat, search dahil
remotePatterns ve localPatterns genelde erişim denetimi sayılıyor, ama aynı zamanda harcama denetimi — hangi URL'lerin dönüşüme dönüşmeye hakkı olduğuna onlar karar veriyor. Tuzak search alanında:
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.example.com',
pathname: '/account123/**',
search: '',
},
],
}search'ü yazmazsanız her sorgu dizesi geçerli olur; yani aynı görsel yüz farklı önbellek kırıcı sonekle yüz farklı önbellek anahtarı demektir. search: '' hiç sorgu dizesi olmamasını şart koşuyor. Next.js ayrıca hostname sizin değilse hesap ya da bucket bölümünü pathname içine koymanızı öneriyor — böylece paylaşımlı bir host üzerinden sizin projenize dönüşüm faturalanamıyor.
Hiçbir şey kazandırmayan yerde optimizasyonu atla
Bazı görseller bu hattan hiçbir şey kazanmıyor: SVG'ler, hareketli GIF'ler ve 10 KB'ın altındaki her şey. Bunları optimize etmek, anlamlı biçimde küçülmeyen bir dosya üretmek için bir dönüşüm, bir önbellek yazması ve bir önbellek okuması harcıyor.
<Image src="/assets/logo.svg" alt="" width={120} height={32} unoptimized />Bunu görsel bazında kullanın, global olarak değil. Yapılandırmada unoptimized: true demek tüm uygulamayı orijinalleri servis etmeye çeviriyor; dönüşüm faturanızı Fast Data Transfer faturasıyla takas ediyorsunuz ve Hobby'de o tavan 100 GB.
Gerçekten tükendiğinde ne oluyor
Panikle ayar yapmaya başlamadan önce bilmeye değer: dahil edilen görsel optimizasyon kullanımını aşmak dağıtımınızı durdurmuyor. Vercel yalnızca yeni kaynak görseller için optimizasyonu durduruyor. Önbellekte olan görseller servis edilmeye devam ediyor, diğer trafik etkilenmiyor. Yeni kaynak görseller optimizasyon uç noktasından 402 alıyor; bu, tanımladıysanız onError geri çağrısını tetikliyor ve görselin yerine alt metnini gösteriyor.
Bu, gerçek alt metni yazmak için iyi bir gerekçe — ve tanınması gereken arıza biçimi de bu: bazı görseller yerinde, yeni eklenenlerin yerinde yazı.
Kendi sunucunda aritmetik değişiyor
Vercel dışında bu sayaçların hiçbiri yok — bunun yerine CPU ve diskle ödüyorsunuz, ve disk tarafının kendi ayarı var:
images: { maximumDiskCacheSize: 500_000_000 } // 500 MBDeğer verilmezse Next.js açılışta bir kez boş disk alanını ölçüp yarısını kullanıyor. Önbellek sınırı geçtiğinde en az kullanılan girdiler siliniyor. Vercel'den çıkınca değişen diğer her şey kendi sunucunda çalıştırma yazısında — görsel optimizasyonunun kendisi yalnızca sharp paketini istiyor.
Özet
Bütçe kardinalite, görsel sayısı değil. Etki sırasına göre:
deviceSizesveimageSizes'ı yerleşiminizin gerçekten üretebileceği genişliklere kırpın. Büyük kazanç burada — varsayılan yapılandırma 128 piksellik bir küçük görseli 15 genişlikte erişilebilir yapıyor.formats'ı tek girdide tutun. İki format her şeyi ikiye katlıyor.minimumCacheTTL'i açıkça yazın. Belgelenen varsayılan ile paketteki varsayılan uyuşmuyor.qualities'i tek değerde tutun.- Pattern'lerde
search: ''verin, sorgu dizeleri önbellek anahtarlarınızı çoğaltamasın. - SVG, GIF ve çok küçük görselleri görsel bazında
unoptimizedişaretleyin.
Sonra toplama değil şekle bakın: önbellek okumaları yazmalardan hızlı büyümeli. Yazmalar okumalara ayak uyduruyorsa hâlâ kimsenin iki kez istemediği varyantlar üretiyorsunuz. Bu arada sizes üzerinde oynuyorsanız neyi daha etkilediğini bilmek işe yarar — eksik ya da yanlış sizes, aynı zamanda yerleşim kaymasının yaygın sebeplerinden biri.