Verfallsdatum überschritten – xtcModified on steroids

36 von 100 möglichen Punkten erreicht ein frisch installierter xtcModified auf einem 1&1 Virtual Server. Diesen Wert gilt es jetzt im Rahmen dieser Artikelserie zu optimieren.

Browser-Caching nutzen

Ganz oben auf der Liste von Page Speed steht das fehlende Browser Caching.

Die Aktualität der folgenden Cache-fähigen Ressourcen ist nur von kurzer Dauer. Legen Sie fest, dass folgende Ressourcen künftig mindestens einmal pro Woche ablaufen:

Und darunter kommt so ziemlich jedes Bild, das Stylesheet und die Javascript-Dateien, die alle kein Ablaufdatum haben. Dieses würde festlegen, wie lange der Browser oder auch ein Proxyserver die Datei als aktuell betrachtet. Mit der aktuellen Konfiguration fordert er jede Datei bei jedem Aufruf erneut an, denn kein Ablaufdatum bedeutet, dass die Datei aus Browsersicht sofort veraltet ist.

Ein solches Verhalten ist sinnvoll und wünschenswert bei Inhalten, die bei jeder Anforderung die aktuellen Gegebenheiten reflektieren müssen, so führt beispielsweise das hinzufügen eines Artikels zum Warenkorb zu einer Änderung und das HTML für den Warenkorb darf dann eben nicht aus dem Cache geholt werden. Bilder, Stylesheets und Scripts ändern sich aber in der Regel nicht mit jedem Klick. Es ist also völlig unnötig, diese Dateien jedes Mal erneut anzufordern bzw. auch nur beim Server anzufragen, ob sich was geändert hat.

mod_expires for the rescue

Zwar gibt es in der .htaccess-Datei, die mit xtcModified mitkommt, einen Eintrag, um bestimmte Formate mit einem Ablaufdatum von einem Monat zu versehen (der aber standardmäßig nicht aktiv ist), ich bevorzuge hier aber die feiner aufgranulierte Lösung aus dem HTML5Boilerplate:

<IfModule mod_expires.c>
  ExpiresActive on
 
# Perhaps better to whitelist expires rules? Perhaps.
  ExpiresDefault                          "access plus 1 month"
 
# cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5)
  ExpiresByType text/cache-manifest       "access plus 0 seconds"
 
# Your document html 
  ExpiresByType text/html                 "access plus 0 seconds"
 
# Data
  ExpiresByType text/xml                  "access plus 0 seconds"
  ExpiresByType application/xml           "access plus 0 seconds"
  ExpiresByType application/json          "access plus 0 seconds"
 
# Feed
  ExpiresByType application/rss+xml       "access plus 1 hour"
  ExpiresByType application/atom+xml      "access plus 1 hour"
 
# Favicon (cannot be renamed)
  ExpiresByType image/x-icon              "access plus 1 week" 
 
# Media: images, video, audio
  ExpiresByType image/gif                 "access plus 1 month"
  ExpiresByType image/png                 "access plus 1 month"
  ExpiresByType image/jpg                 "access plus 1 month"
  ExpiresByType image/jpeg                "access plus 1 month"
  ExpiresByType video/ogg                 "access plus 1 month"
  ExpiresByType audio/ogg                 "access plus 1 month"
  ExpiresByType video/mp4                 "access plus 1 month"
  ExpiresByType video/webm                "access plus 1 month"
 
# HTC files  (css3pie)
  ExpiresByType text/x-component          "access plus 1 month"
 
# Webfonts
  ExpiresByType font/truetype             "access plus 1 month"
  ExpiresByType font/opentype             "access plus 1 month"
  ExpiresByType application/x-font-woff   "access plus 1 month"
  ExpiresByType image/svg+xml             "access plus 1 month"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
 
# CSS and JavaScript
  ExpiresByType text/css                  "access plus 1 year"
  ExpiresByType application/javascript    "access plus 1 year"
 
  <IfModule mod_headers.c>
    Header append Cache-Control "public"
  </IfModule>
 
</IfModule>

Das ganze in If-Bedingungen zu verpacken verhindert effektiv Internal Server Errors (500), wenn das Modul nicht installiert ist. Klar, dass das ganze dann auch nichts bringt, aber ein Shop, der Inhalte ohne die Optimierung ausliefert ist immer noch besser als ein Shop, der nur eine Fehlermeldung zurückgibt.
Wenn aber mod_expires aktiv ist geschieht folgendes: Der generelle Wert für das Ablaufdatum wird auf einen Monat gesetzt und das ganze dann für Inhalte, die nicht gecached werden sollen, wieder zurückgesetzt. Anschließend werden die Werte für einzelnen Formate basierend auf dem MIME-Type gesetzt. Man könnte alles weglassen, was dem generellen Wert entspricht, aber ich lasse das hier mal mit aufgeführt, vielleicht will man den ein oder anderen Ablaufzeitraum doch anpassen, bei X-Skating sind es beispielsweise 10 Jahre für Bilder. Auf die dabei auftretenden Probleme gehe ich später noch ein.
Cache-Control “public” sorgt dann noch dafür, dass diese Inhalte auch von Proxies gecached werden können, auch wenn man sich vorher per HTTP authentifizieren musste (was für xtcModified aber nicht zutrifft).

Da mod_expires auf Grundlage der MIME-Types arbeitet empfiehlt es sich, noch einen anderen Bereich aus der Datei von HTML5Boilerplate zu übernehmen, nämlich den Teil, wo die MIME-Types vereinheitlicht werden, gerade Javascript kommt je nach Serverkonfiguration mit den obskursten MIME-Types daher:

# JavaScript
#   Normalize to standard type (it's sniffed in IE anyways)
#   tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript         js
 
# Audio
AddType audio/ogg                      oga ogg
AddType audio/mp4                      m4a
 
# Video
AddType video/ogg                      ogv
AddType video/mp4                      mp4 m4v
AddType video/webm                     webm
 
# SVG
#   Required for svg webfonts on iPad
#   twitter.com/FontSquirrel/status/14855840545
AddType     image/svg+xml              svg svgz
AddEncoding gzip                       svgz
 
# Webfonts
AddType application/vnd.ms-fontobject  eot
AddType application/x-font-ttf         ttf ttc
AddType font/opentype                  otf
AddType application/x-font-woff        woff
 
# Assorted types
AddType image/x-icon                        ico
AddType image/webp                          webp
AddType text/cache-manifest                 appcache manifest
AddType text/x-component                    htc
AddType application/x-chrome-extension      crx
AddType application/x-opera-extension       oex
AddType application/x-xpinstall             xpi
AddType application/octet-stream            safariextz
AddType application/x-web-app-manifest+json webapp
AddType text/x-vcard                        vcf

Die einzige Änderung, die wir noch an den Expires vornehmen ist das Favicon, das wird ebenfalls auf einen Monat gesetzt. Die .htaccess, wie sie bis zu diesem Zeitpunkt aussieht, kann hier heruntergeladen werden.

Erneut durch YSlow und Page Speed überprüft ergibt sich schon ein wesentlich besseres Bild. YSlow pendelt sich jetzt bei 89 Punkten ein, noch deutlicher ist die Steigerung bei Page Speed, 60 von 100 möglichen Punkten stehen jetzt bereits zu Buche.

YSlow klettert von 80 auf 89 Punkte...


...Page Speed sogar von 36 auf 60

Auch schön zu sehen ist die extreme Reduktion an HTTP-Requests durch diese Maßnahme. So zeigt das Wasserfalldiagramm bei webpagetest.org ohne mod_expire auch bei der Anfragewiederholung noch alle Bilder etc. Der Server antwortet zwar mit 304 Not Modified, aber die Anfrage geht trotzdem raus und muss beantwortet werden. Sind Expire-Header gesetzt gehört das der Vergangenheit an.

Inhalte aktualisieren bei langen Expires

(Sehr) lange Expire-Zeiten bringen ein Problem mit sich: Da der Browser davon ausgeht, dass die CSS-Datei, die er gestern geladen hat, noch ein Jahr gültig ist, wird er erst gar nicht auf die Idee kommen, diese neu anzufordern. Daher kommt man um eine Versionierung nicht herum. HTML5Boilerplate empfiehlt das über Query-Paramter zu machen, statt stylesheet.css ruft man also beispielsweise stylesheet.css?20120112 auf. Damit man das nicht jedes Mal händisch aktualisieren muss sollte man die Zeilen 15 und 16 in templates/xtc5/css/general.css.php wie folgt ändern:

<link rel="stylesheet" href="<?php echo 'templates/'.CURRENT_TEMPLATE; ?>/stylesheet.css?<?php echo filemtime('templates/'.CURRENT_TEMPLATE.'/stylesheet.css') ?>" type="text/css" />
<link rel="stylesheet" href="<?php echo 'templates/'.CURRENT_TEMPLATE; ?>/css/thickbox.css?<?php echo filemtime('templates/'.CURRENT_TEMPLATE.'/css/thickbox.css') ?>" type="text/css" media="screen" />

Damit wird immer das Datum der letzten Modifikation als Unix-Zeitstempel angehangen. Das gleiche sollte man auch für das Javascript machen, aber da wir uns dem eh im Rahmen dieser Serie noch gesondert annehmen müssen verschieben wir das auf einen späteren Zeitpunkt.

Als letztes Problemkind bleiben die Bilder. Die Lösung dafür ist etwas aufwendiger und wird behandelt werden wenn wir auf das Thema Content Delivery Networks (CDN) zu sprechen kommen.

Weitere Artikel der Serie


Disclaimer: Ich bin nach wie vor der Meinung, dass viele der Einstellungen in der Serverkonfiguration besser aufgehoben sind als in der .htaccess-Datei, da aber sicherlich viele das ganze auch auf Shared Hosting umsetzen möchten werden alle Änderungen, die auch über die .htaccess durchgeführt werden können, im Rahmen dieser Artikelreihe dort vorgenommen.

Ich bin Web Developer und arbeite als Lead Developer bei WIBROS. Ich liebe das Internet, Baseball, Softball, Bier und die Farbe orange. Ich hab früher mal zu viel Kaffee getrunken.

Comments (10) Write a comment

  1. Pingback: Druck machen – xtcModified on steroids

  2. Pingback: Hinten anstellen – xtcModified on steroids

  3. hallo Matt,
    nach deinen vielen und effizienten Hilfen und Tipps auch mal auf diesem Weg danke. Nachvollziehbar auch für Laien, macht Spaß, damit zu arbeiten. Besonders diese Tipps hier habe ich mehrfach erfolgreich angewandt. Auch lohnt das Stöbern im Archiv und deinen anderen Seiten.
    Dein Sprachstil gefällt mir auch.
    Also danke und halt durch!
    Manfred

    Reply

  4. Pingback: Der schnellste Erzgebirge-Palast, den es je gab.

  5. Hallo …. fange gerade mit dem Thema an und freue mich über sooo viel info !

    werde es gleich mal ausprobieren ….

    danke

    Reply

  6. wobei…..
    ich frage mich jetzt wo ich den quellcode oben genau in meine .htaccess datei einfügen bzw. ersetzen muss oder habe ich da irgendwas falsch verstanden ?

    Reply

  7. Hi,
    ich habe die .htaccess folgendermaßen angepasst und komme auf deutlich schnellere Ladezeiten als du mit deiner 🙂


    #Order deny,allow
    #Deny from all
    #Allow from 87.166.153.132
    #Allow from 176.199.29.150

    #AddHandler php53-cgi .php

    ##-- $Id: _.htaccess 2670 2012-02-23 12:53:47Z dokuman $

    ##-- Default charset
    #AddDefaultCharset utf-8
    AddDefaultCharset ISO-8859-15

    ##-- Disable the server signature
    ServerSignature Off

    ##-- Turn off ETags in HTTP-header (use both directives)

    Header unset ETag

    FileETag None

    ##-- When caching of gzipped JS and CSS files is used, enable this setting
    ##
    # Header set Vary Accept-Encoding
    #

    ## Hoster 1&1 (activate PHP5)
    #AddType x-mapp-php5 .php
    #AddHandler x-mapp-php5 .php

    # Disable directory browsing
    Options All -Indexes

    ##-- Enable the compression for any type of content

    SetOutputFilter DEFLATE

    ##-- Customization of HTTP request and response headers

    Header set Cache-Control "max-age=2592000, public"

    Header set Cache-Control "max-age=604800, public"

    Header set Cache-Control "max-age=216000, private"

    Header set Cache-Control "max-age=216000, public, must-revalidate"

    Header set Cache-Control "max-age=1, private, must-revalidate"

    ##-- Generation of Expires and Cache-Control HTTP headers

    ExpiresActive On
    ExpiresDefault "access plus 1 seconds"
    ExpiresByType image/gif "access plus 2592000 seconds"
    ExpiresByType image/jpeg "access plus 2592000 seconds"
    ExpiresByType image/png "access plus 2592000 seconds"
    ExpiresByType text/html "access plus 1 seconds"
    ExpiresByType text/css "access plus 604800 seconds"
    ExpiresByType text/javascript "access plus 216000 seconds"
    ExpiresByType application/x-javascript "access plus 216000 seconds"

    ##-- Configure php_flags if necessary
    ##-- use IfModule clause if PHP runs in CGI mode, otherwise just uncomment the lines with php_flag...
    #
    ##-- Warn when arguments are passed by reference at function call time (from PHP5 allow_call_time_pass_reference is deprecated)
    #php_flag allow_call_time_pass_reference on
    ##-- Disable transparent sid support PHP-default is off (XTC Session only on first visit)
    #php_flag session.use_trans_sid off
    ##-- set suhosin flags because of errors with attributes (for webhosters with suhosin hardening patch enabled)
    #php_value suhosin.post.max_array_depth 0
    #php_value suhosin.post.max_array_index_length 0
    #php_value suhosin.post.max_vars 0
    #php_value suhosin.request.max_array_depth 0
    #php_value suhosin.request.max_array_index_length 0
    #php_value suhosin.request.max_vars 0
    ##-- set suhosin flags to have unencrypted session data, affecting "whos_online" & "shopping cart" (for webhosters with suhosin hardening patch enabled)patch enabled
    #php_value suhosin.session.encrypt Off
    #php_value suhosin.session.cryptkey ''
    #

    ##-- when using Provider 1&1 set the following lines to activate PHP5
    #AddType x-mapp-php5 .php
    #AddHandler x-mapp-php5 .php

    ##-- Redirect error pages to Sitemap
    ErrorDocument 400 /sitemap.html?error=400
    ErrorDocument 401 /sitemap.html?error=401
    ErrorDocument 402 /sitemap.html?error=402
    ErrorDocument 403 /sitemap.html?error=403
    ErrorDocument 404 /sitemap.html?error=404
    ErrorDocument 500 /sitemap.html?error=500

    ##-----------------------------------------
    ##- SEO Shopstat Modul (Hartmut König)
    ##-----------------------------------------

    ##-- Initialize and enable rewrite engine
    RewriteEngine On
    # Rewriteregel fü Proxy (ssl-account.com) bei eigenen Zertifikat bitte diese drei Zeilen auskommentieren.
    #RewriteCond %{HTTP_HOST} !^www. [NC]
    #RewriteCond %{REMOTE_ADDR} !^85.13.128.137 [NC]
    #RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [L,R=301]

    ##-- EXAMPLE: If your shop is located at "http://www.yourdomain.com/shop",
    ##-- set the following line like e.g.: RewriteBase /shop
    # RewriteBase /
    ##-- Use canonical URLs
    ##-- redirect to www-domain, when www is missing and no subdomain given and not using an ssl-proxy
    # RewriteCond %{HTTP:X-Forwarded-Server} !^ssl\.webpack\.de$ [NC]
    # RewriteCond %{HTTP:X-Forwarded-Server} !^sslsites\.de$ [NC]
    # RewriteCond %{HTTP_HOST} !^www\..* [NC]
    # RewriteCond %{HTTP_HOST} !^.*\..*\..* [NC]
    # RewriteCond %{HTTP_HOST} !^localhost(.*)$ [NC]
    # RewriteCond %{REMOTE_ADDR} !127.0.0.1$ [NC]
    # RewriteRule ^(.*) http://www.%{HTTP_HOST}/$1 [R=301,L]

    #-- redirect /folder/index.php to /folder/ (i.e. hide index.php)
    RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\ HTTP/
    RewriteRule ^index\.php$ http://%{HTTP_HOST}/ [R=301,L]

    ##-- Sitemap
    RewriteRule ^sitemap(.*)\.html$ shop_content.php?coID=8 [QSA,L]

    ##----------------------------------------------------------------------------------
    ##-- When working with SEO-Urls you can decide, wheter to use a colon ":" or a dash "-" symbol
    ##-- (Windows Servers might have problems with colon as delimiter!)
    ##-- Change the delimiter symbol also in file "/inc/shopstat_functions.inc.php"
    ##----------------------------------------------------------------------------------

    ##-- Use colon delimiter ":" for SEO-URLS (default setting)
    ##-- Categories (:::)
    RewriteCond %{REQUEST_URI} (.*):::([_0-9]+):([_0-9]+)\.html$
    RewriteRule (.*):::([_0-9]+):([_0-9]+)\.html$ index.php?cPath=$2&page=$3 [QSA,L]
    RewriteCond %{REQUEST_URI} (.*):::([_0-9]+)\.html$
    RewriteRule (.*):::([_0-9]+)\.html$ index.php?cPath=$2 [QSA,L]

    ##-- Products (::)
    RewriteRule (.*)::(.+)\.html$ product_info.php?products_id=$2 [QSA,L]

    ##-- Content (:_:)
    RewriteRule (.*):_:([0-9]+)\.html$ shop_content.php?coID=$2 [QSA,L]

    ##-- Manufacturers (:.:)
    RewriteCond %{REQUEST_URI} (.*):.:([_0-9]+):([_0-9]+)\.html$
    RewriteRule (.*):.:([_0-9]+):([_0-9]+)\.html$ index.php?manufacturers_id=$2&page=$3 [QSA,L]
    RewriteCond %{REQUEST_URI} (.*):.:([_0-9]+)\.html$
    RewriteRule (.*):.:([0-9]+)\.html$ index.php?manufacturers_id=$2 [QSA,L]

    ##-- Use dash delimiter "-" for SEO-URLS
    ##-- Categories (---)
    #RewriteCond %{REQUEST_URI} (.*)---([_0-9]+)-([_0-9]+)\.html$
    #RewriteRule (.*)---([_0-9]+)-([_0-9]+)\.html$ index.php?cPath=$2&page=$3 [QSA,L]
    #RewriteCond %{REQUEST_URI} (.*)---([_0-9]+)\.html$
    #RewriteRule (.*)---([_0-9]+)\.html$ index.php?cPath=$2 [QSA,L]

    ##-- Products (--)
    #RewriteRule (.*)--(.+)\.html$ product_info.php?products_id=$2 [QSA,L]

    ##-- Content (-_-)
    #RewriteRule (.*)-_-([0-9]+)\.html$ shop_content.php?coID=$2 [QSA,L]

    ##-- Manufacturers (-.-)
    #RewriteCond %{REQUEST_URI} (.*)-.-([_0-9]+)-([_0-9]+)\.html$
    #RewriteRule (.*)-.-([_0-9]+)-([_0-9]+)\.html$ index.php?manufacturers_id=$2&page=$3 [QSA,L]
    #RewriteCond %{REQUEST_URI} (.*)-.-([_0-9]+)\.html$
    #RewriteRule (.*)-.-([0-9]+)\.html$ index.php?manufacturers_id=$2 [QSA,L]

    Reply

Leave a Reply to Mark N. Cancel reply

Required fields are marked *.