Browser-Caching speichert Ressourcen lokal im Browser des Besuchers. Bei wiederholten Besuchen werden Dateien vom lokalen Cache statt vom Server geladen.
Wie Browser-Caching funktioniert
Erster Besuch:
Browser ─────Anfrage────→ Server
←──Datei + Cache-Header──
Zweiter Besuch (gecacht):
Browser ─────prüft Cache────→ Lokal
←──Datei aus Cache──
(Keine Server-Anfrage)Vorteile
- Schnellere Ladezeiten: Lokaler Cache ist schneller
- Weniger Traffic: Dateien werden nicht erneut übertragen
- Geringere Server-Last: Weniger Anfragen
- Bessere UX: Seiten laden sofort
Cache-Header verstehen
Cache-Control
Der wichtigste Header für modernes Caching:
Cache-Control: max-age=31536000, public
│ │
│ └── Darf öffentlich gecacht werden
└──────────────────── 1 Jahr in SekundenDirektiven:
| Direktive | Bedeutung | |-----------|-----------| | max-age=N | Cache-Dauer in Sekunden | | public | Darf von allen gecacht werden | | private | Nur Browser-Cache, keine Proxies | | no-cache | Revalidierung erforderlich | | no-store | Nie cachen (sensible Daten) | | immutable | Ändert sich nie (mit Versioning) |
Expires
Älterer Header, setzt Ablaufdatum:
Expires: Thu, 31 Dec 2025 23:59:59 GMTETag
Eindeutige Kennung der Dateiversion:
ETag: "5f3b8e1c-1a2b"Browser sendet bei Revalidierung:
If-None-Match: "5f3b8e1c-1a2b"Server antwortet mit 304 Not Modified wenn unverändert.
Last-Modified
Zeitstempel der letzten Änderung:
Last-Modified: Wed, 15 Jan 2025 10:00:00 GMTNginx Cache-Konfiguration
Mit expires-Direktive
# /etc/nginx/nginx.conf oder site-config
# Statische Assets
location ~* \.(jpg|jpeg|png|gif|webp|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML - kürzer cachen
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public";
}
# Kein Cache für dynamische Inhalte
location /api/ {
add_header Cache-Control "no-store";
}Vollständige Server-Konfiguration
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/html;
# HTML, XML
location ~* \.(?:html|xml)$ {
expires 1h;
add_header Cache-Control "public";
}
# CSS, JS mit Versioning
location ~* \.(?:css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Bilder
location ~* \.(?:jpg|jpeg|png|gif|webp|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Fonts
location ~* \.(?:woff2?|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# PDF, Dokumente
location ~* \.(?:pdf|doc|docx)$ {
expires 1M;
add_header Cache-Control "public";
}
}Map für differenziertes Caching
map $sent_http_content_type $expires {
default off;
text/html 1h;
text/css 1y;
application/javascript 1y;
image/jpeg 1y;
image/png 1y;
image/webp 1y;
image/svg+xml 1y;
font/woff2 1y;
}
server {
expires $expires;
# ...
}Apache Cache-Konfiguration
Mit mod_expires
a2enmod expires
a2enmod headers# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/html
<IfModule mod_expires.c>
ExpiresActive On
# Standard: 1 Monat
ExpiresDefault "access plus 1 month"
# HTML: 1 Stunde
ExpiresByType text/html "access plus 1 hour"
# CSS, JS: 1 Jahr
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
# Bilder: 1 Jahr
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
# Fonts: 1 Jahr
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
# Cache-Control für statische Assets
<FilesMatch "\.(jpg|jpeg|png|gif|webp|css|js|woff2?)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Kein Cache für HTML
<FilesMatch "\.html$">
Header set Cache-Control "public, max-age=3600"
</FilesMatch>
</IfModule>
</VirtualHost>Via .htaccess
# .htaccess
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
# Nach Typ
ExpiresByType text/html "access plus 1 hour"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|webp|woff2?)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>Cache-Busting mit Versioning
Problem: Lange Cache-Zeiten verhindern Updates. Lösung: Dateinamen ändern bei Updates.
Query-String
<link rel="stylesheet" href="style.css?v=1.2.3">
<script src="app.js?v=1.2.3"></script>Nachteil: Manche Proxies cachen Query-Strings nicht.
Dateiname mit Hash
<link rel="stylesheet" href="style.abc123.css">
<script src="app.def456.js"></script>Build-Tools wie Webpack/Vite generieren automatisch Hashes.
WordPress: Asset-Versioning
// functions.php
wp_enqueue_style('theme-style', get_stylesheet_uri(), [], '1.2.3');
wp_enqueue_script('theme-js', get_template_directory_uri() . '/js/app.js', [], '1.2.3', true);Empfohlene Cache-Zeiten
| Dateityp | Ohne Versioning | Mit Versioning | |----------|-----------------|----------------| | HTML | 1 Stunde | 1 Stunde | | CSS | 1 Woche | 1 Jahr | | JavaScript | 1 Woche | 1 Jahr | | Bilder | 1 Monat | 1 Jahr | | Fonts | 1 Jahr | 1 Jahr | | API-Antworten | no-store | no-store |
Cache-Header testen
Mit curl
curl -I https://example.com/style.css
# Ausgabe:
# Cache-Control: public, max-age=31536000, immutable
# Expires: Sun, 25 Jan 2026 10:00:00 GMT
# ETag: "abc123"
# Last-Modified: Wed, 01 Jan 2025 10:00:00 GMTBrowser DevTools
1. DevTools öffnen (F12) 2. Network-Tab 3. Datei auswählen 4. Response Headers prüfen
Online-Tools
- https://www.webpagetest.org
- https://redbot.org
- Google PageSpeed Insights
Service Worker für Offline-Caching
Für fortgeschrittenes Caching:
// service-worker.js
const CACHE_NAME = 'v1';
const ASSETS = [
'/',
'/css/style.css',
'/js/app.js',
'/images/logo.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(ASSETS))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});CDN-Caching
Bei CDN-Nutzung zusätzlich beachten:
# Vary-Header für korrektes CDN-Caching
add_header Vary "Accept-Encoding";
# s-maxage für CDN-spezifische Dauer
add_header Cache-Control "public, max-age=3600, s-maxage=86400";
# Browser: 1 Stunde, CDN: 1 TagTroubleshooting
Cache wird ignoriert
# Headers prüfen
curl -I https://example.com/file.js | grep -i cache
# Keine Cache-Control = kein CachingÄnderungen nicht sichtbar
1. Browser-Cache leeren (Strg+Shift+R) 2. Versioning nutzen (Dateinamen ändern) 3. ?v=timestamp anhängen
ETag deaktivieren
Manchmal problematisch bei Load Balancing:
# Nginx
etag off;# Apache
Header unset ETag
FileETag NoneFazit
Browser-Caching ist essentiell für schnelle Webseiten. Konfigurieren Sie lange Cache-Zeiten (1 Jahr) für statische Assets mit Versioning. HTML sollte kürzer gecacht werden (1 Stunde). Nutzen Sie immutable für versionierte Dateien. Testen Sie die Header mit curl oder Browser DevTools und verwenden Sie Cache-Busting bei Updates.