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 Sekunden

Direktiven:

| 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 GMT

ETag

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 GMT

Nginx 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 GMT

Browser 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 Tag

Troubleshooting

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 None

Fazit

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.