HTTP Caching — Cache-Control, ETag, CDN vodic

Kako ubrzati sajt 5-10x za ponovne posete pravilnim kesiranjem resursa

5-10x
Brze ucitavanje sa kesom
304
Not Modified — usteda bandwidth-a
1 god.
Max cache za staticke resurse
3
Nivoa: Browser, CDN, Server

1. Sta je HTTP caching i zasto je bitan

HTTP caching je mehanizam koji cuva kopije resursa (HTML, CSS, JS, slike) blize korisniku — u browseru, CDN-u ili proxy serveru. Kad korisnik ponovo poseti sajt, resursi se ucitavaju iz kesa umesto da se ponovo preuzimaju sa servera.

  • Brzina: Kesiran resurs se ucitava za ~1ms umesto 200-500ms sa servera
  • Bandwidth: Smanjuje kolicinu podataka za 60-90% za ponovne posete
  • Server opterecenje: Manje zahteva ka serveru = bolje skaliranje
  • Offline pristup: Sa Service Worker-om, sajt moze raditi bez interneta
Statistika: Pravilno kesirani sajt se ucitava 5-10x brze za ponovne posete. Google PageSpeed Insights penalizuje sajtove bez cache headera.

2. Cache-Control header

Cache-Control je najvazniji HTTP header za kontrolu kesiranja. Server ga salje u odgovoru, a browser/CDN ga postuje.

DirektivaZnacenjePrimer
max-age=NKesiraj N sekundi od zahtevamax-age=3600 = 1 sat
s-maxage=NKao max-age ali samo za CDN/proxys-maxage=86400 = CDN cuva 1 dan
publicBilo ko moze kesirati (browser + CDN)Staticke slike, CSS, JS
privateSamo browser kesa (ne CDN)Personalizovani sadrzaj, sesije
no-cacheKesiraj ALI revalidiraj svaki putHTML stranice (cest sadrzaj)
no-storeNE kesiraj uopsteOsetljivi podaci (bankarstvo)
must-revalidatePosle isteka, MORA revalidiratiSa max-age za striktnost
immutableResurs se NIKAD ne menjaFajlovi sa hash-om u imenu
stale-while-revalidate=NKoristi stari kes dok revalidira u pozadiniBalans brzine i svezine
Cesta zabuna: no-cache NE znaci "ne kesiraj"! Znaci "kesiraj ali proveri sa serverom svaki put". Za potpuno iskljucivanje kesa koristite no-store.

3. Preporuke po tipu resursa

ResursCache-ControlZasto
HTMLno-cache ili max-age=0, must-revalidateSadrzaj se menja — uvek proveri svezinu
CSS (sa hash-om)max-age=31536000, immutableHash u imenu se menja kad se fajl promeni
JS (sa hash-om)max-age=31536000, immutableIsto kao CSS — ime se menja pri promeni
Slikemax-age=2592000 (30 dana)Retko se menjaju, ali nemaju hash
Fontovimax-age=31536000, immutableNikad se ne menjaju
API odgovorino-store ili max-age=60Dinamicki podaci, cesto se menjaju
Osetljivi podacino-store, privateNikad kesirati bankarske/medicinske podatke
# Primer odgovora servera za CSS sa hash-om
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable
ETag: "a1b2c3d4"

# Primer za HTML (uvek revalidiraj)
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-cache
ETag: "page-v42"

4. ETag — validacija kesa

ETag (Entity Tag) je jedinstven identifikator verzije resursa. Server ga generise (obicno hash sadrzaja) i browser ga koristi za revalidaciju.

Kako radi

  1. Prva poseta: server salje resurs + ETag: "abc123"
  2. Browser cuva resurs i ETag
  3. Druga poseta: browser salje If-None-Match: "abc123"
  4. Server poredi ETag-ove:
  5. — Ako se poklapa: 304 Not Modified (bez body-ja, ~100 bajtova)
  6. — Ako se ne poklapa: 200 OK sa novim sadrzajem i novim ETag-om
# Zahtev za revalidaciju
GET /style.css HTTP/1.1
If-None-Match: "a1b2c3d4"

# Odgovor: nije se promenilo — usteda bandwidth-a!
HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"
Cache-Control: public, max-age=3600

Weak vs Strong ETag

TipFormatZnacenje
Strong"abc123"Bajt-za-bajt identican sadrzaj
WeakW/"abc123"Semanticki ekvivalentan (moze imati male razlike)

5. Last-Modified / If-Modified-Since

Stariji mehanizam validacije (pre ETag-a). Server salje datum poslednje izmene, browser ga cuva i pita "da li se promenilo od tada?"

# Prva poseta
HTTP/1.1 200 OK
Last-Modified: Wed, 09 Apr 2026 12:00:00 GMT

# Druga poseta
GET /page.html HTTP/1.1
If-Modified-Since: Wed, 09 Apr 2026 12:00:00 GMT

# Odgovor: 304 ako se nije promenilo
ETag vs Last-Modified: ETag je precizniji (hash sadrzaja vs datum). Koristite ETag kao primarni mehanizam, Last-Modified kao fallback. Vecina servera salje oba.

6. Cache-busting strategije

Problem: ako kesirate CSS za 1 godinu, kako naterate browser da preuzme novu verziju kad promenite dizajn?

Hash u filename-u (preporuceno)

<!-- Stara verzija -->
<link rel="stylesheet" href="/css/style.a1b2c3.css">

<!-- Nova verzija — drugaciji hash = browser preuzima ponovo -->
<link rel="stylesheet" href="/css/style.d4e5f6.css">

Build alati (Webpack, Vite, Parcel) automatski dodaju hash u ime fajla. Kad se fajl promeni, hash se menja, browser preuzima novu verziju jer je to "novi" URL.

Query string (alternativa)

<link rel="stylesheet" href="/css/style.css?v=2.1">
<script src="/js/app.js?v=20260409"></script>
Paznja: Query string cache-busting ne radi pouzdano sa svim CDN-ovima. Neki CDN-ovi ignorisu query string za kesiranje. Hash u filename-u je pouzdaniji.

7. Browser vs CDN vs Server cache

NivoGdeKo kontroliseBrzinaPrimer
Browser cacheKorisnikov uredjajCache-Control header~1msPonovna poseta sajtu
CDN cacheEdge server (blizu korisnika)s-maxage, CDN pravila~20-50msCloudflare, Vercel Edge
Server cacheOrigin serverRedis, Memcached, aplikacija~5-20msDatabase query cache
Origin (bez kesa)Server + baza~100-500msSvezi podaci iz baze

Zahtev prolazi nivoe: Browser → CDN → Server → Origin. Ako bilo koji nivo ima validan kes, odgovor se vraca odatle bez dalje propagacije.

8. Service Worker cache

Service Worker je JavaScript koji radi u pozadini i moze presresti mrezne zahteve. Sa njim mozete napraviti offline-first sajt.

// sw.js — Service Worker sa cache-first strategijom
const CACHE_NAME = 'v1';
const ASSETS = ['/', '/index.html', '/css/style.css', '/js/app.js'];

// Instalacija: kesiraj kriticne resurse
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
  );
});

// Zahtev: vrati iz kesa, fallback na mrezu
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request).then(response => {
        // Kesiraj novi resurs za sledecu posetu
        const clone = response.clone();
        caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
        return response;
      });
    })
  );
});

Cache strategije

StrategijaOpisKada koristiti
Cache FirstKes → mreza (ako nema u kesu)Staticni resursi (CSS, JS, slike)
Network FirstMreza → kes (ako mreza ne radi)Dinamicki sadrzaj (API, HTML)
Stale While RevalidateVrati iz kesa odmah + azuriraj u pozadiniSadrzaj gde je svezina pozeljna ali ne kriticna

9. Cache invalidation

Phil Karlton je rekao: "There are only two hard things in Computer Science: cache invalidation and naming things."

  • Hash u filename-u — najcistije resenje. Promena fajla = novi hash = novi URL = browser automatski preuzima.
  • CDN purge — rucno ili API pozivom ocistite CDN kes. Cloudflare: purge by URL ili purge all.
  • Verzioniranje API-ja/api/v2/users umesto /api/users kad se format promeni.
  • stale-while-revalidate — korisnik dobija stari kes odmah, novi se ucitava u pozadini za sledecu posetu.
  • Service Worker azuriranje — promena CACHE_NAME triggeruje reinstalaciju i kesiranje novih resursa.
Opasnost: Ako kesirate HTML za 1 godinu i promenite CSS bez promene HTML-a, korisnici ce videti star CSS sa novim HTML-om = pokvaren sajt. NIKAD ne kesirajte HTML dugotrajno.

10. Konfiguracija: Nginx, Apache, Vercel

Nginx

# /etc/nginx/conf.d/cache.conf
location ~* \.(css|js|woff2|png|jpg|webp|avif|svg)$ {
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
}
location ~* \.html$ {
    add_header Cache-Control "no-cache";
}
location /api/ {
    add_header Cache-Control "no-store";
}

Apache (.htaccess)

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType text/html "access plus 0 seconds"
</IfModule>

<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|woff2|png|jpg|webp)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
  <FilesMatch "\.html$">
    Header set Cache-Control "no-cache"
  </FilesMatch>
</IfModule>

Vercel (vercel.json)

{
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {"key": "Cache-Control", "value": "public, max-age=31536000, immutable"}
      ]
    },
    {
      "source": "/(.*).html",
      "headers": [
        {"key": "Cache-Control", "value": "no-cache"}
      ]
    }
  ]
}

11. Reference i resursi

Proverite cache konfiguraciju vaseg sajta →

HTTP Caching — Cache-Control, ETag, CDN Guide

How to make your site 5-10x faster for return visits with proper resource caching

5-10x
Faster loading with cache
304
Not Modified — bandwidth savings
1 yr
Max cache for static assets
3
Levels: Browser, CDN, Server

1. What is HTTP caching and why it matters

HTTP caching stores copies of resources closer to the user. On return visits, resources load from cache (~1ms) instead of the server (200-500ms).

Statistic: Properly cached sites load 5-10x faster on return visits. PageSpeed Insights penalizes sites without cache headers.

2. Cache-Control header

DirectiveMeaningExample
max-age=NCache for N secondsmax-age=3600 = 1 hour
s-maxage=NCDN/proxy only caches-maxage=86400 = CDN 1 day
publicAnyone can cacheStatic images, CSS, JS
privateBrowser only (not CDN)Personalized content
no-cacheCache BUT revalidate every timeHTML pages
no-storeDon't cache at allSensitive data (banking)
immutableResource NEVER changesFiles with hash in name
Common confusion: no-cache does NOT mean "don't cache"! It means "cache but check with server every time". Use no-store to disable caching completely.

3. Recommendations by resource type

ResourceCache-ControlWhy
HTMLno-cacheContent changes — always check freshness
CSS/JS (hashed)max-age=31536000, immutableHash changes when file changes
Imagesmax-age=2592000 (30 days)Rarely change, no hash
Fontsmax-age=31536000, immutableNever change
APIno-store or max-age=60Dynamic data

4. ETag — cache validation

ETag is a unique version identifier. Browser sends If-None-Match: "etag" → server returns 304 (no body) if unchanged, or 200 with new content.

TypeFormatMeaning
Strong"abc123"Byte-for-byte identical
WeakW/"abc123"Semantically equivalent

5. Last-Modified / If-Modified-Since

Older validation mechanism using dates. ETag is more precise (content hash vs date). Most servers send both.

6. Cache-busting strategies

Hash in filename (recommended): style.a1b2c3.css → build tools auto-generate. File change = new hash = browser downloads.

Query string (alternative): style.css?v=2.1 — less reliable with some CDNs.

7. Browser vs CDN vs Server cache

LevelWhereSpeedExample
BrowserUser's device~1msReturn visit
CDNEdge server (near user)~20-50msCloudflare, Vercel
ServerOrigin server~5-20msRedis, Memcached
OriginServer + database~100-500msFresh data

8. Service Worker cache

Service Worker intercepts requests and can serve from cache even offline.

StrategyDescriptionUse for
Cache FirstCache → network fallbackStatic assets
Network FirstNetwork → cache fallbackDynamic content
Stale While RevalidateReturn stale + update in backgroundNon-critical freshness

9. Cache invalidation

"There are only two hard things in CS: cache invalidation and naming things."

  • Hash in filename — cleanest solution
  • CDN purge — manual or API
  • API versioning/api/v2/
  • stale-while-revalidate — stale cache + background update
Danger: If you cache HTML for 1 year and change CSS without changing HTML, users see old CSS with new HTML = broken site. NEVER cache HTML long-term.

10. Configuration: Nginx, Apache, Vercel

Nginx: expires 1y + Cache-Control "public, max-age=31536000, immutable" for static assets.

Apache: mod_expires + mod_headers in .htaccess.

Vercel: vercel.json headers config per source pattern.

11. References and resources

Check your cache configuration →