jekcms her yüklemeyi AVIF (kalite 60) ve WebP (kalite 82) formatına dönüştürür ve otomatik olarak yedi boyut varyantı üretir. Gerçek işlem hattı burada — otomatik bir sahipsiz-dosya çöp toplayıcısı gibi yapmadığı şeyler dahil.
Yonetilmeyen Medyanin Sorunu
Medya pipeline'i olmayan erken bir jekcms kurulumunda, uploads klasoru yazi sayisina gore beklenenden cok daha hizli buyuyebilir. Suclu genelde belliydi: yazarlar telefonlarindan doğrudan 4000x3000 JPEG fotograf yukluyordu ve sistem bunlari tam cozunurlukle sakliyordu.
Ayni görsel kenar cubugunda 200x200 thumbnail olarak, ana sayfada 400px kart görseli olarak ve yazi sayfasinda 800px hero olarak sunuluyordu — hepsi ayni 4.8 MB kaynak dosyadan. Her sayfa yüklemesi gerekenden 10-15 kat daha fazla veri aktariyordu.
Medya pipeline'ini o zaman kurduk. JekCMS'e giren her görsel artik kalici depolamaya tek bayt yazilmadan once doğrulama, EXIF temizleme, format donusumu ve çoklu boyut thumbnail oluşturma işlemlerinden geciyor.
Yukleme Akisi: Gonder Tusuna Bastiginda Ne Olur
Yukleme sureci bes farkli asamaya sahiptir. Bunlari anlamak bir sey ters gittiginde hata ayiklamaya yardimci olur.
Asama 1: Dogrulama
Herhangi bir işleme baslamadan once yuklenen dosya doğrulama kontrollerinden gecer:
private function validateUpload(array $file): void
{
// 1. PHP yükleme hatalarini kontrol et
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new MediaException($this->uploadErrorMessage($file['error']));
}
// 2. Dosya boyutunu kontrol et (görseller için max 10MB)
$maxSize = 10 * 1024 * 1024;
if ($file['size'] > $maxSize) {
throw new MediaException('Dosya 10MB sinirini asiyor');
}
// 3. MIME tipini uzantidan degil dosya iceriginden doğrula
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif'];
if (!in_array($mime, $allowed)) {
throw new MediaException('Dosya tipi izin verilmiyor: ' . $mime);
}
// 4. Gecerli bir görsel oldugunu doğrula
$info = getimagesize($file['tmp_name']);
if ($info === false) {
throw new MediaException('Dosya gecerli bir görsel degil');
}
// 5. Gomulu PHP kodu kontrolu (güvenlik)
$content = file_get_contents($file['tmp_name']);
if (preg_match('/
MIME tipi kontrolu gerçek dosya basligini okumak için finfo kullanir. malware.php dosyasini malware.jpg olarak yeniden adlandiran birisi bu kontrolde basarisiz olur cunku dosya basligi hicbir görsel MIME tipiyle eslesmez.
Asama 2: EXIF Temizleme
Telefonlardan gelen JPEG görseller EXIF meta verileri icerir: GPS koordinatlari, cihaz modeli, tarih/saat ve bazen fotografcinin adi. Bu verileri yayinlamak gizlilik riskidir. Ofisinizin bir fotografi, indirip EXIF verisini kontrol eden herkese tam konumunuzu ifsa eder.
private function stripExifData(string $sourcePath, string $mime): string
{
if ($mime !== 'image/jpeg') return $sourcePath;
$image = imagecreatefromjpeg($sourcePath);
if ($image === false) return $sourcePath;
$stripped = tempnam(sys_get_temp_dir(), 'exif_');
imagejpeg($image, $stripped, 95);
imagedestroy($image);
return $stripped;
}
Yaklasim basittir: JPEG'yi GD ile yukle ve yeniden kaydet. GD EXIF verilerini korumaz, dolayisiyla yeniden kaydedilen dosya temizdir.
Asama 3: Format Donusumu
JekCMS tum yuklenen görselleri birincil format olarak AVIF'e, yedek olarak WebP'ye dönüştürur. Basarili donusumden sonra orijinal dosya atilir.
Neden once AVIF? Cunku sikistirma olculebilir sekilde daha iyi. Gerçek JekCMS sitelerinden 500 görsel üzerindeki testlerimizde, jekcms'in kullandigi kalite-60 AVIF ayari, kalite-82 WebP'ye göre gorsel kalitede karsilastirilabilirken belirgin sekilde daha küçük dosyalar uretti.
private function convertToModernFormats(string $sourcePath, string $destBase): array
{
$image = $this->loadImage($sourcePath);
$results = [];
if (function_exists('imageavif')) {
$avifPath = $destBase . '.avif';
imageavif($image, $avifPath, 60);
$results['avif'] = $avifPath;
}
if (function_exists('imagewebp')) {
$webpPath = $destBase . '.webp';
imagewebp($image, $webpPath, 82);
$results['webp'] = $webpPath;
}
imagedestroy($image);
return empty($results) ? ['original' => $sourcePath] : $results;
}
Asama 4: Thumbnail Oluşturma
Yuklenen her görsel dort thumbnail boyutu uretir:
- thumbnail — 400x400, merkez kirpilmis. Admin panellerde, küçük widget'larda kullanilir.
- medium — 800x800, orantili olceklenmis. Yazi kartlari ve listeleme sayfalari icin.
- large — 1600x1600, orantili olceklenmis. Tekil yazi sayfalarinda içerik alani icin.
- pinterest — 1000x1500, 2:3 oraninda merkez kirpilmis. Pinterest paylaşımi için özel — dikey görseller kare olanlardan 2-3 kat daha fazla etkilesim alir.
$sizes = [
'thumbnail' => ['width' => 400, 'height' => 400, 'crop' => true],
'medium' => ['width' => 800, 'height' => 800, 'crop' => false],
'large' => ['width' => 1600, 'height' => 1600, 'crop' => false],
'pinterest' => ['width' => 1000, 'height' => 1500, 'crop' => true],
];
"Kaynak daha küçükse atla" kontrolu kritiktir. 300x200 görsel yuklendiginde 1600x1600 "large" versiyonu oluşturmak onu buyutur ve bulanik, daha büyük bir dosya uretir.
Asama 5: Depolama ve Veritabani Kaydi
Son asama dosyalari diske yazar ve yüklemeyi medya tablosuna kaydeder. Veritabani yalnizca temel yolu saklar — uzanti olmadan: images/2026/03/resim. URL oluşturucu render sirasinda uygun boyut soneki ve formati ekler.
Picture Elementi Ciktisi
Dogru formati dogru tarayıcıya sunmak HTML <picture> elemani gerektirir:
function get_featured_picture(array $post, string $size = 'medium'): string
{
$basePath = $post['featured_image'] ?? '';
if (empty($basePath)) return '';
$baseUrl = UPLOADS_URL . '/' . $basePath;
$suffix = ($size !== 'full') ? '-' . $size : '';
return sprintf(
'<picture>
<source srcset="%s" type="image/avif">
<source srcset="%s" type="image/webp">
<img src="%s" alt="%s" loading="lazy" width="%d" height="%d">
</picture>',
$baseUrl . $suffix . '.avif',
$baseUrl . $suffix . '.webp',
$baseUrl . $suffix . '.webp',
htmlspecialchars($post['title'] ?? ''),
$size === 'thumbnail' ? 400 : ($size === 'medium' ? 800 : 1600),
$size === 'thumbnail' ? 400 : ($size === 'medium' ? 500 : 1000)
);
}
Tarayici destekledigi ilk <source> ogresini secer. Acik width ve height özellikleri Cumulative Layout Shift'i (CLS) onler — bunlar olmadan tarayıcı görsel yuklenene kadar ne kadar alan ayiracagini bilemez.
CDN Entegrasyonu
CDN kullanan siteler için medya URL oluşturma tek bir degisiklik gerektirir:
define('CDN_URL', 'https://cdn.example.com');
$baseUrl = defined('CDN_URL') && CDN_URL
? CDN_URL . '/uploads/' . $basePath
: UPLOADS_URL . '/' . $basePath;
CDN ilk istekte kaynak sunucudan ceker ve yaniti dunya capinda kenar dugumlerde önbellegee alir. Dikkat edilecek sey: CDN önbellek gecersiz kilma. Ayni dosya adiyla görsel yeniden yuklendiginde CDN eski önbellek versiyonunu sunmaya devam edebilir. Bunu önlemek için içerik tabanli dosya adlari kullanin.
Yaygin Yol Tuzaklari
JekCMS kurulumlarinda en sik karsilasilan medya hatasi cift uploads/ yoludur. Bu, veritabanı uploads/images/2026/03/foto gibi bir yol sakladiginda ve URL oluşturucu zaten /uploads ile biten UPLOADS_URL ekledigi zaman olusur. Sonuc: https://site.com/uploads/uploads/images/2026/03/foto.avif — bir 404.
Kural basittir: veritabanı, uploads dizinine goreli yollar saklar, uploads/ onekini asla icermez. Dogru format: images/2026/03/foto.
// Dogru: veritabanında "images/2026/03/foto"
$url = UPLOADS_URL . '/' . $path;
// Sonuc: https://site.com/uploads/images/2026/03/foto.avif
// Yanlis: veritabanında "uploads/images/2026/03/foto"
$url = UPLOADS_URL . '/' . $path;
// Sonuc: https://site.com/uploads/uploads/images/2026/03/foto.avif (404!)
Production'dan Gerçek Rakamlar
- Ortalama görsel boyutu, orijinal JPEG yuklemesinden donusturulmus AVIF/WebP thumbnail'lere gecerken belirgin sekilde kucculur — tam oran kaynak fotografa bagli olsa da tutarli sekilde buyuk bir kat farkidir.
- Ana sayfa agirligi, orijinal cozunurluklu JPEG'ler sunmaya kiyasla tipik bir yazi-karti izgarasi icin cok daha kucuktur.
- Largest Contentful Paint, gorsel-agirlikli sayfalarda AVIF/WebP sunumu devreye girdiginde olculebilir sekilde iyilesir.
Islem yukleme sirasinda gercek zaman alir — sunucu format donusumu ve boyut varyanti uretimi icin gercek is yapar — ama bu maliyet gorsel basina bir kez odenir, bant genisligi tasarrufu ise gorselin sitede kaldigi surece her sayfa yuklemesinde gecerlidir.
Farkli Ne Yapardik
Bu pipeline'i sifirdan bugün kursaydim iki özellik eklerdim. Birincisi, arka plan işleme: donusum ve thumbnail oluşturmayi senkron yapmak yerine bir kuyruga tasiyin. 800ms gecikmesi kabul edilebilir ama galeri sayfasi için 50 görsel toplu yuklerken toplanir.
Ikincisi, responsive srcset oluşturma. Sabit thumbnail boyutlari yerine birden cok genislik (320, 640, 960, 1280, 1600) oluşturun ve tarayıcınin genislik tanimlayicilariyla srcset kullanarak en iyisini secmesine izin verin. Bu, 400 piksel genisligindeki kartin 800 piksel görsele ihtiyac duymadigi mobil cihazlarda daha küçük dosyalar sunacaktir.
Her iki özellik de yol haritamizda. Su an mevcut sabit-boyut yaklasimi cogu kullanim durumunu yeterince iyi karsiliyor.