Versionierung von statischen Ressourcen bei WordPress

Wenn man sich mit WordPress beschäftigt, stellt schnell fest, dass statische Ressourcen, wie Stylesheets und Scripte mit dem Parameter „ver“ eingebunden werden, wie z.B. beim „Hemingway“-Theme:

href='http://server.example/wp-content/themes/hemingway/style.css?ver=4.1'

Lässt man die Website dann überprüfen, z.B. bei  Pingdom, wird der „ver“-Parameter als Problem für effizientes Caching bemängelt, weil Inhalte von URLs mit Parametern mitunter nicht im Cache gespeichert, sondern jedesmal vom Server neu geholt werden.

Andererseits ist die richtige Versionsangabe aber auch für die korrekte Funktion von Themes und Plugins wichtig, besonders wenn diese aktualisiert werden – wer selber als Autor/in in diesem Bereich aktiv ist, sollte unbedingt den Hinweis am Ende dieses Beitrags beachten.

Parameter aus den URLs entfernen – eine gute Idee?

Manche Leute schlagen vor mit eigenen WordPress-Filtern für die Aktionen script_loader_src und style_loader_src die „ver“-Parameter zu entfernen. Es gibt auch Plugins, die das erledigen.

Das ist aber keine gute Idee!

Zur Erläuterung:

Stylesheets und Scripte werden vom Browser im lokalen Cache abgelegt. Dort verbleiben diese Daten mehrere Tage oder länger je nach Konfiguration der Servers und der angegebenen Gültigkeitsdauer. Es ist auch eine gute Idee, die Gültigkeit für statische Ressourcen auf einen hohen Wert zu setzen, um unnötige Anfragen für Dinge zu vermeiden, die sich ohne nur selten ändern.

Wenn man nun ein Update von WordPress, eines Themes oder eines Plugins vornimmt, ändert sich dadurch normalerweise auch der „ver“-Parameter und der Browser wird die neue Version vom Server anfordern, da der Inhalt des Cache durch den geänderten Parameter nicht mehr gültig ist.

Fehlt der Parameter „ver“ aber, ist die URL auch bei einer neueren Version immer noch unverändert und der Browser wird auch die neuere Version nicht vom Server anfordern, da er bereits eine lokale Kopie im Cache hat. Besucher/innen erhalten möglicherweise eine Mischung aus alten und neuen Ressourcen, die zu einer fehlerhaften oder komplett unbenutzbaren Darstellung führt.

Der Parameter ist also nicht überflüssig, sondern stellt sicher, dass ein Browser keine veralteten Inhalte  aus dem Cache verwendet, sondern bei Bedarf die neue Version vom Server anfordert.

Elegantere Lösung: Parameter als Bestandteil der URL

John Beales hat einen eleganten Ansatz vorgeschlagen, mit dem die URLs für Stylesheets und Scripte in WordPress so angepasst werden, dass sie weiterhin für jede Version eindeutig sind, aber ohne den „ver“-Parameter am Ende auskommen. Das erfordert auch eine Rewrite-Regel im Webserver, da die URLs keine existierenden Dateien mehr auf dem Server adressieren.

Schritt 1: Anpassen der URLs in WordPress

Um die von WordPress erzeugten URLs anzupassen, erweitert man die Datei functions.php seines Themes mit folgendem Code:

function my_versioned_url($url, $defaultversion = false) {
    // Im Backend keine Anpassung vornehmen
    if(!is_admin()) {
        // URL parsen
        $parts = parse_url($url);

        // Und dann die Query-Parameter
        $query = wp_parse_args($parts['query'], array());

        // Pruefen, ob es eine Versionsangabe in den Parametern gibt.
        // Falls ja, sind weitere Dinge notwendig.
        if(isset($query['ver']) || false !== $defaultversion) {
            if(isset($query['ver'])) {
                // Praefix "/res-" + {version value} im *Pfad* ergaenzen
                $parts['path'] = '/res-'.$query['ver'].$parts['path'];

                // Version im Query-String entfernen, da sie jetzt Teil
                // des Pfades ist.
                unset($query['ver']);
            } else {
                $parts['path'] = '/res-'.$defaultversion.$parts['path'];
            }

            // Falls es sich um eine PHP-Datei handelt, nichts weiter tun
            if(!preg_match( '~\.php$~', $parts['path'])) {
                // URL wieder neu aufbauen
                $url = $parts['scheme'].'://'.$parts['host'].$parts['path'];

                // Pruefen, ob es noch weitere Parameter gibt
                if(count( $query ) > 0) {
                    // Abfrage mit allen verbleibenden Parametern bauen
                    $parts['query'] = build_query( $query );

                    // Und als Parameter an die URL anhaengen
                    $url .= '?'.$parts['query'];
                }
            }
        }
    }

    return $url;
}

// Funktion als Filter eintragen
add_filter( 'style_loader_src', 'my_versioned_url' );
add_filter( 'script_loader_src', 'my_versioned_url' );

Zur Erläuterung:

Die Funktion my_versioned_url() wird als Filter für das Laden von Stylesheets und Scripten im Frontend verwendet und erhält zwei Parameter: die URL der zu ladenden Datei sowie optional die Angabe der Versionsnummer, die WordPress als „ver“-Parameter an die URL anhängt. Als Ergebnis liefert sie eine angepasste URL zurück, die nach folgendem Schema aufgebaut ist:

http://<servername>/res-<version>/<url>

Bezogen auf das Beispiel vom Anfang würde das dann so aussehen:

http://server.example/res-4.1/wp-content/themes/hemingway/style.css

Schritt 2: Rewrite-Regel in Apache ergänzen

Für die korrekte Behandlung der angepassten URLs genügen in Apache zwei Zeilen, die man z.B. in der Datei .htaccess im Hauptverzeichnis der Website ergänzt:

RewriteEngine on
RewriteRule ^res-([\d\.\-a-zA-Z]+)/(.*) $2?ver=$1 [L]

Wichtig: Wenn man statt „res-“ einen anderen Präfix verwenden will, muss das in der Filterfunktion als auch in der Rewrite-Regel angepasst werden!

Ein Hinweis für Autor/innen von Themes und Plugins

Leider ist es nicht verpflichtend, eine Versionsnummer anzugeben, wenn man Scripte oder Stylesheets in einem Theme oder Plugin einbindet.  Falls keine spezifische Version angegeben wird, verwendet WordPress automatisch seine eigene Version als „ver“-Parameter (z.B. 4.2.1) – aber das bedeutet auch, dass die Version unverändert bleibt wenn das Theme oder Plugin aktualisiert wird, aber WordPress selbst weiterhin in der selben Version benutzt wird. Das führt schließlich dazu, dass Browser eine alte Version der Stylesheets oder Scripte aus dem Cache benutzen und Leute werden sich beschweren, dass das Theme oder Plugin nach dem Update nicht mehr richtig funktioniert!

Zur Vermeidung von Problemen sollte man immer einen Versionsparameter für alle Scripte und Stylesheets angeben, wie im folgenden Beispiel gezeigt:

wp_register_style(
    'my-cool-widget',
    plugins_url('styles/widget.css', __FILE__),
    array(),
    '1.0.0' // <-- Das ist die Version DEINES Widgets, nicht von WordPress!
);
wp_enqueue_style('my-cool-widget');

Und selbstverständlich sollte man darauf achten, bei einem Update auch eine neue Versionsnummer zu vergeben, wenn die eingebundenen Scripte oder Stylesheets geändert wurden!

3 Gedanken zu „Versionierung von statischen Ressourcen bei WordPress“

  1. Hallo,
    ich habe mich über Ihren Artikel sehr gefreut und den Code gleich eingebaut.

    In Schritt 1 funktioniert bei mir alles wunderbar. Jedoch wird vermutlich Schritt 2 nicht umgesetzt, da das Dokument mit der neuen URL angefragt aber nicht gefunden wird.

    Haben Sie eine Idee, was da schiefläuft?
    Viele Grüße

    P.S. Den Code habe ich erst einmal wieder rausgenommen.
    Liane

    1. Arno Welzel

      Ohne genauere Analyse der konkreten Umgebung kann ich dazu leider nicht viel sagen. Zur Eingrenzung des Fehlers würde ich erstmal nur die Apache-Rewrite-Regel einbauen ohne die WordPress-Anpassungen vorzunehmen – dann funktioniert WordPress unverändert weiter. Dann kann man in Ruhe ausprobieren, ob die Zugriffe auf die veränderte URL in der Form http://<servername>/res-<version>/<url> funktionieren. Was bei WordPress generell gerne Probleme macht sind Plugins zum Cachen von Inhalten oder für eine „optimierte“ Ausgabe in komprimierter Form.

      1. Hallo,

        danke für die schnelle Antwort. Ich habe es leider jetzt erst bemerkt, da ich dachte, dass ich per E-Mail benachrichigt werde…

        Ich habe den Code heute noch einmal probiert.

        Und zwar habe ich erst (wie von ihnen vorgeschlagen) Schritt 2 ausgeführt. Der Aufruf einer Datei mit dieser Struktur http://<servername>/res-<version>/<url> hat funktioniert. Das ging letzte Woche nicht – keine Ahnung warum.

        Danach habe ich dann Schritt 1 durchgeführt. Alles läuft prima.

        Vielen Dank noch mal!

        Viele Grüße
        Liane

Öffentlichen Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Wenn Du mich persönlich erreichen möchtest, siehe Impressum.