Merhaba arkadaşlar, bugün başımı çok ağrıtan bir konudan bahsedeceğim. Sitemin performansını artırmak için cache'leme yapıyordum ama statik asset'lerim (CSS, JS, resimler) her seferinde sunucudan tam boyutuyla indiriliyordu. "Bu dosyalar değişmediyse neden tekrar yükleniyor?" diye düşünürken, koşullu istekler (conditional requests) ve 304 Not Modified durum kodunun gücünü yeniden keşfettim.
Sorunun Özü: Gereksiz Veri Transferi
Bir kullanıcı sitemizi ziyaret ettiğinde, tarayıcı tüm statik dosyaları önbelleğine (cache) alır. Ancak, kullanıcı sayfayı yenilediğinde veya bir sonraki girişinde, tarayıcı sunucuya "Bu dosya hala geçerli mi?" diye sormak yerine, genellikle "Bana bu dosyanın tamamını tekrar ver" şeklinde bir istek (GET) atıyordu. Bu da hem sunucu band genişliğini hem de kullanıcının sayfa yükleme süresini olumsuz etkiliyordu. İşte tam burada ETag ve Last-Modified header'ları devreye giriyor.
Çözüm: Sunucu Tarafında Doğru Header'ları Göndermek
Amacım basitti: Sunucumdan bir dosya istendiğinde, yanıtla birlikte "Bu dosyanın kimliği şu" (ETag) ve "Bu dosya en son şu tarihte değiştirildi" (Last-Modified) bilgilerini gönderecektim. Tarayıcı da bir sonraki isteğinde, önbelleğindeki bu bilgileri If-None-Match (ETag için) ve If-Modified-Since (Last-Modified için) header'ları ile sunucuya gönderecekti. Eğer dosya değişmemişse, sunucu sadece 304 Not Modified durum kodu dönecek ve dosyanın gövdesi (body) boş olacaktı. Böylece sadece birkaç byte'lık header trafiği oluşacak, dosyanın kendisi tekrar indirilmeyecekti.
Node.js (Express) backend'imde bunu nasıl yaptığıma bir bakalım. Express, static dosya servis ederken zaten bu header'ları otomatik ekliyor ama ben biraz daha özelleştirmek istedim.
Yukarıdaki kodda, /assets yoluna gelen her istek için önce bir middleware çalışıyor. Bu middleware, dosyanın hash'inden bir ETag ve son değiştirilme tarihinden de Last-Modified header'ını oluşturup yanıta ekliyor. Ardından, gelen istekte bu değerlerin olup olmadığını kontrol ediyor. Eşleşme varsa, hemen 304 Not Modified döndürüyor. Bu sayede, express.static middleware'ine gidip dosyayı tekrar okuma ve gönderme işi yapılmıyor.
Sonuç ve Performans Kazanımı
Bu yapıyı kurduktan sonra, Chrome DevTools'un "Network" sekmesinde inanılmaz bir iyileşme gördüm. Örneğin, 150KB'lık bir `main.css` dosyam vardı. İlk yüklemede durum kodu 200 OK ve transfer edilen veri ~150KB idi. Sayfayı yenilediğimde ise durum kodu 304 Not Modified ve transfer edilen veri sadece 300 byte civarındaydı (sadece request/response header'ları). Bu, tek bir dosya için bile muazzam bir tasarruf. Onlarca dosya ve binlerce kullanıcı düşünüldüğünde, sunucu yükünde ve kullanıcı deneyiminde ciddi bir fark yaratıyor.
Siz backend taraflı (Laravel, Django, ASP.NET gibi) benzer bir cache stratejisi uyguladınız mı? Ya da CDN kullanıyorsanız, CDN provider'ınızın ETag ve Last-Modified header'larını yönetme şekli nasıl? Deneyimlerinizi yorumlarda paylaşırsanız çok sevinirim!
Bir kullanıcı sitemizi ziyaret ettiğinde, tarayıcı tüm statik dosyaları önbelleğine (cache) alır. Ancak, kullanıcı sayfayı yenilediğinde veya bir sonraki girişinde, tarayıcı sunucuya "Bu dosya hala geçerli mi?" diye sormak yerine, genellikle "Bana bu dosyanın tamamını tekrar ver" şeklinde bir istek (GET) atıyordu. Bu da hem sunucu band genişliğini hem de kullanıcının sayfa yükleme süresini olumsuz etkiliyordu. İşte tam burada ETag ve Last-Modified header'ları devreye giriyor.
Amacım basitti: Sunucumdan bir dosya istendiğinde, yanıtla birlikte "Bu dosyanın kimliği şu" (ETag) ve "Bu dosya en son şu tarihte değiştirildi" (Last-Modified) bilgilerini gönderecektim. Tarayıcı da bir sonraki isteğinde, önbelleğindeki bu bilgileri If-None-Match (ETag için) ve If-Modified-Since (Last-Modified için) header'ları ile sunucuya gönderecekti. Eğer dosya değişmemişse, sunucu sadece 304 Not Modified durum kodu dönecek ve dosyanın gövdesi (body) boş olacaktı. Böylece sadece birkaç byte'lık header trafiği oluşacak, dosyanın kendisi tekrar indirilmeyecekti.
Node.js (Express) backend'imde bunu nasıl yaptığıma bir bakalım. Express, static dosya servis ederken zaten bu header'ları otomatik ekliyor ama ben biraz daha özelleştirmek istedim.
JavaScript:
const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const crypto = require('crypto');
const app = express();
// Özel bir middleware ile ETag oluşturuyoruz
app.use('/assets', async (req, res, next) => {
const filePath = path.join(__dirname, 'public', req.path);
try {
const stats = await fs.stat(filePath);
const fileContent = await fs.readFile(filePath);
// Dosya içeriğinden hash bazlı bir ETag oluştur
const etag = crypto.createHash('md5').update(fileContent).digest('hex');
// Last-Modified header'ını UTC formatında ayarla
const lastModified = stats.mtime.toUTCString();
// Header'ları yanıta ekle
res.set('ETag', etag);
res.set('Last-Modified', lastModified);
// Tarayıcıdan gelen koşullu istek header'larını kontrol et
const clientETag = req.headers['if-none-match'];
const clientLastModified = req.headers['if-modified-since'];
if (clientETag === etag || clientLastModified === lastModified) {
// Dosya değişmemiş, 304 döndür
return res.status(304).end();
}
// Dosya değişmişse veya ilk defa isteniyorsa, normal akışa devam et
next();
} catch (err) {
// Dosya bulunamadı vs.
next(err);
}
});
// Static dosyaları servis et (Artık middleware'imiz header'ları kontrol ediyor)
app.use('/assets', express.static(path.join(__dirname, 'public'), {
// Express static'in kendi ETag'ini devre dışı bırak, bizim middleware'imiz işlesin
etag: false,
lastModified: false
}));
app.listen(3000, () => console.log('Sunucu 3000 portunda çalışıyor...'));
Yukarıdaki kodda, /assets yoluna gelen her istek için önce bir middleware çalışıyor. Bu middleware, dosyanın hash'inden bir ETag ve son değiştirilme tarihinden de Last-Modified header'ını oluşturup yanıta ekliyor. Ardından, gelen istekte bu değerlerin olup olmadığını kontrol ediyor. Eşleşme varsa, hemen 304 Not Modified döndürüyor. Bu sayede, express.static middleware'ine gidip dosyayı tekrar okuma ve gönderme işi yapılmıyor.
Bu yapıyı kurduktan sonra, Chrome DevTools'un "Network" sekmesinde inanılmaz bir iyileşme gördüm. Örneğin, 150KB'lık bir `main.css` dosyam vardı. İlk yüklemede durum kodu 200 OK ve transfer edilen veri ~150KB idi. Sayfayı yenilediğimde ise durum kodu 304 Not Modified ve transfer edilen veri sadece 300 byte civarındaydı (sadece request/response header'ları). Bu, tek bir dosya için bile muazzam bir tasarruf. Onlarca dosya ve binlerce kullanıcı düşünüldüğünde, sunucu yükünde ve kullanıcı deneyiminde ciddi bir fark yaratıyor.
Siz backend taraflı (Laravel, Django, ASP.NET gibi) benzer bir cache stratejisi uyguladınız mı? Ya da CDN kullanıyorsanız, CDN provider'ınızın ETag ve Last-Modified header'larını yönetme şekli nasıl? Deneyimlerinizi yorumlarda paylaşırsanız çok sevinirim!