@codenerd på twitter Mit seneste tweet:

Bedre loadtid: GZIP, cache og minify dine CSS og JavaScript filer

Siden “Webstedseffektivitet” blev tilgængeligt i Googles Webmastertools, har folk fokuseret mere og mere på loadtid, og det er selvfølgelig en rigtig god ting. Blandt de råd som kan gøre den største forskel, i forhold til en sides loadtid, er helt sikkert:

  • Kombiner eksterne CSS og JavaScript filer
  • Aktiver GZIP for alt indhold, CSS og JS
  • Minify CSS og JS
  • Aktiver Cache for alle filtyper

Hvis du ikke er interesseret i mere info om de 4 punkter, vil jeg anbefale du blot hopper ned til løsningen

Kombiner eksterne CSS og JavaScript filer

Hvis du virkelig går op i orden, har du sikkert mange JavaScript filer såvel som CSS filer, mange foretrækker fx at have et utal af css filer som går på hvad de påvirker, fx forms.css, tables.css osv. Dette er også ganske fint ud fra et strukturerings synspunkt, men det er faktisk meget skidt for din sides loadtid.

Her anbefales det at du simpelthen kombinerer dine filer. Dette er jo egentligt ganske nemt men hvad nu hvis man gerne vil bevarer overblikket og beholde de mange filer, men samtidig ønsker god loadtid?

Aktiver GZIP for alt indhold, CSS og JS

Aktivering af GZIP komprimering på dine sider er utroligt nemt i PHP, du skal blot starte alle dine PHP sider med:

PHP
1
2
3
<?php
    ob_start("ob_gzhandler");
?>

Så sker komprimering helt automatisk for denne side, i de browsere som understøtter gzip komprimering.

Men for at kunne gøre dette med CSS og JavaScript filer, skal du enten omdøbe dine filer til .php og angive den rigtige mimeheader, eller manuelt gzippe dine filer og henvise til disse i browsere der godtager GZIP komprimerede filer.

Minify CSS og JS

Minifying handler i bund og grund om at fjerne alt unødigt fra dine filer, dvs ingen kommentarer og ingen unødige mellemrum og linieskift. Nu tænker du nok, ja men alt det er da med til at gøre CSS og JavaScript overskueligt. Det er det også, men det skaber også unødige bytes som skal loades, og hvis du har en del JS og CSS, så er der faktisk en del at hente ved at gøre dette.

Aktiver Cache for alle filtyper

Cache gør at dine tilbagevendende besøgende ikke gang på gang skal loade indhold som de faktisk har hentet før, og som ikke har ændret sig siden deres sidste besøg.

Hvis din webhost understøtter mod_headers, er det rimeligt nemt at opsætte serveren til at cache alt indhold, eller fx specifikt på billeder, ønsker du fx at alt skal gemmes i brugerens cache i et år er det blot at skrive følgende i din .htaccess:  Header set Cache-Control “max-age=29030400″

Minify klarer jobbet for dig

Hvis du synes ovenstående var ren volapyk, eller at anbefalingerne gør det besværligt for dig at lave den mindste rettelse på din side, ja så er løsningen helt klart Minify.

Minify er en række PHP scripts, der formindsker din sides loadtid ved at følge de anvisninger som bla. google kommer med. Selvom navnet antyder at Minify kun udfører opgaven “minify CSS og JS”, så klarer scriptet faktisk alle ovenstående opgaver, og du bevarer samme filstruktur som du har nu, dvs ønsker du at ha 10 CSS filer, så har du stadig dette, Minify sørger blot for at sende det hele til brugeren i en enkelt gzip komprimeret fil.

Sådan bruger du Minify

Først og fremmest skal du downloade Minify, denne pakker du ud i en mappe på din server/webhotel, fx i mappen /min/

Herefter opretter du en tmp mappe i /min/ og chmodder tmp mappen til 777. (Permisions i fx dreamweaver og mange ftp klienter)

Herefter skal du tilrette config.php og indsætte/rette: $min_cachePath = “tmp”;

Når du har gjort dette er du klar til at bruge Minify. Har du fx 3 CSS filer du loader på denne måde:

PHP
1
2
3
<link rel="stylesheet" href="/css/css1.css" />
<link rel="stylesheet" href="/css/css2.css" />
<link rel="stylesheet" href="/css/css3.css" />

Kan du nu i stedet loade alle 3 igennem Minify:

PHP
1
<link rel="stylesheet" href="/min/?b=css&f=css1.css,css2.css,css3.css" />

Minify vil nu loade de samme 3 filer, men undervejs kombineres disse til en fil, hvor alle unødige ting bliver fjernet, og gzip bliver slået til. Stien “?b=css&f=css1.css,css2.css,css3.css” opbygges således:

b Mappe angivelse
f Kommasepareret liste over filer der skal kombineres, husk at angive filerne i den rækkefølge du ellers ville loade dem i

På samme måde kan du kombinerer dine JS filer fra:

PHP
1
2
<script type="text/javascript" src="/js/script1.js" />
<script type="text/javascript" src="/js/script2.js" />

Til:

PHP
1
<script type="text/javascript" src="/min/?b=js&f=script1.js,script2.js" />

Selv hvis du har en JS eller CSS fil du ikke ønsker at kombinere, skal du loade denne igennem Minify, for at få gzip komprimering, samt minify slået til.

Ændringer i CSS og JavaScript filer

Når man arbejder med caching af CSS og JavaScript, er det som regel en god ide at ændre filnavn, eller bruge en parameter, som indikerer at der er opdateret indhold i fx en CSS fil. Fx: fra style.css?update=1 til style.css?update=2. Denne nye sti gør at browserne vil reloade CSS filen fra siden i stedet for cachen, også selvom cache måske ikke er udløbet.

Dette lille "hack" er faktisk ikke nødvendigt med Minify, da minify selv undersøger ændringstidspunkt for de enkelte filer, og hvis disse er ændret tvinges frisk indhold til browseren. Derfor vil jeg også helt klart anbefale at du lader Minify håndterer Cache, og ikke fx sætter en Cache for CSS og JS via .htaccess

Minify til WordPress

Blev gjort opmærksom på der findes et plugin til wordpress, som integrerer Minify:
Minify til wordpress

Du kan læse hvordan du gør din WordPress meget hurtigere over hos Claus Heinrich:

Konklusion

Fordelen ved Minify scriptet er helt klart, at alle original filer forbliver intakte, samt at man ikke selv skal sidde og komprimere de enkelte filer efter hver ændring, hvilket jeg tidligere selv har gjort.

Minify fungerer rigtigt godt, og har endnu ikke givet mig nogen problemer i forhold til hvordan siden kører på ukomprimeret filer, og der er en del båndbredde at sparer, så det er bare om at få installeret Minify.

Har du endnu ikke installeret Pagespeed, vil jeg helt klart anbefale du gør dette (kræver firefox og firebug), og så ellers få din side optimeret i forhold til de anbefalinger den kommer med! Minify klarer det meste for dig, så ingen undskyldninger, bare kom i gang :-)

36 kommentarer

  1. Skrevet af Vadskær  d. 11/01/2010 kl 14:12

    Super indlæg, Martin. Det er super-fedt at Google vil til at “belønne” hastighed og forhåbentligt kan det få folk til at skrive noget pænere HTML, droppe table-layout og i højere grad benytte sig af CSS til f.eks. knapper og lignende.

    Kunne være man snart skulle give sine sider en pagespeed-overhaling.

    Svar på kommentaren
  2. Det er altid en god ide at optimere css og JavaScript, men man skal holde tungen lige i munden når man cacher. Hvis du f.eks. sætter en time-to-live på 10 år bliver dit indhold sjovt nok cachet i op til 10 år, og det er fint nok så længe du ikke har tænkt dig at ændre css og scripts i 10 år.

    Med den model du viser, bliver indholdet af din minify først opdateret når cachen udløber, og det skyldes at stien på din minify aldrig ændrer sig. Hvis du ønsker at have mulighed for at cache i flere år og samtidig have mulighed for at opdatere, når der er behov for det, skal din minify sti opdateres til en unik sti hver gang du foretager en ændring.

    Svar på kommentaren
  3. @Jan sker ikke nødvendigvis automatisk på CSS og JS, men kommer lidt an på server opsætning, nogen host sætter faktisk mod_headers op automatisk, der er vist et helt indlæg i sig selv

    @Vadskær, ja jeg skal vist også igang på et par webshops- Det er jo trods alt et rimeligt vigtigt sted at sætte ind, og ikke mindst også et sted der kan afhjælpe lidt belastning.

    @Michael – Glad for det kan bruges

    @Torben Problemet er egentligt ikke større end at tilføje en ekstra parameter fx &update=1 og så opdatere denne hver gang, men ellers håndterer Minify faktisk selv at sende de korrekt not modified/modified headers så hvis man blot lader minify håndtere cache og ikke bruger sin egen htaccess løsning, så er problemet faktisk ikke til stede – men utroligt vigtigt punkt at få frem, som jeg ikke lige havde tænkt over

    Svar på kommentaren
  4. Hejsa Martin,
    Gode forklaringer du har om emnet – er netop det jeg har siddet og rodet med de sidste par dage på nogle af mine blogs. Caching, kombiner css/js, minify, pic optimering, css sprites og CDN.

    Jeg kommer også med et blogindlæg inden alt for længe der vil beskrive lidt af proceduren til WordPress brugerne – der findes nogle smarte plugins der kan ordne alt dette for en.
    Mange af tingene er faktisk så nemme at det vil være direkte ulogisk ikke at benytte sig af det – loadtiderne kan virkelig påvirkes hvis man tweaker lidt med det

    Svar på kommentaren
  5. Super indlæg Martin. Har ventet spændt siden du “annoncerede” at du havde det på tegnebrættet i går på Twitter.

    Men jeg skal vist også lige have gået min blog igennem, for der er helt sikkert steder hvor jeg kan optimere.

    Svar på kommentaren
  6. @claus lyder super smart med plugin til wordpress, trods alt at foretrække for alle der sidder på WP platformen, og glæder mig til at læse med, da jeg nok inden længe får gang i et par wordpress projekter

    @Kim At følge alle anvisninger fra Pagespeed gav mig faktisk en reducering på 45% på første besøg, og 79% på andet ved at få alt gemt i cache, så der er helt sikkert meget at hente

    Svar på kommentaren
  7. Hold da op, ja det lyder da ikke som småting man kan hente der, og hvis man ikke rigtigt behøver at ændre sin kodestil eller noget, men langt hen af vejen kan automatisere optimeringsprocessen, så er det da givet at det er det man skal i gang med :-)

    Svar på kommentaren
  8. @Jesper, ja der er meget og hente og det er super nemt når man ikke selv skal ændre andet end et par stier.

    @Peter God pointe, man kan jo ikke se sig helt fri for at der kommer en dinosaur browser eller 10 forbi. Tilføjer det lige til i en revision af blogindlæget, og velkommen til!

    Svar på kommentaren
  9. Skrevet af Tom Sommer  d. 12/01/2010 kl 10:37

    Det er slet ikke nødvendigt selv at checke HTTP_ACCEPT_ENCODING, det sørger ob_gzhandler selv for, så lad den bare gør det – en fejlkilde mindre

    Before ob_gzhandler() actually sends compressed data, it determines what type of content encoding the browser will accept (“gzip”, “deflate” or none at all) and will return its output accordingly

    http://php.net/manual/en/function.ob-gzhandler.php

    Svar på kommentaren
  10. Skrevet af Mads Madsen  d. 13/01/2010 kl 06:26

    Fin artikel til at forklare problemet, men at det er en løsning er jeg meget uenig i.
    Jeg har selv lavet en ligende løsning, men måtte hurtigt konstater at det er langsommere end at:
    * Kombinere filerne lokalt, også uploade dem.
    * Lade webserveren klare gzip komprimeringen.
    * Udnytte CNAME subdomæner til at levere statisk indhold.

    Problemet med at lave et PHP script varetage opgaver som den egenligt slet ikke er egnet til.
    Ud fra mine egne tests så jeg at 3 javascript filer på 2 – 56kb, komprimeret, kombineret og minified = 64kb, tog det 180ms for PHP at spytte det ud fra en cache, mens ukomprimeret, opdelt i 3 og uminified tog i alt 120ms for at hente alle 3 javascript filer ned.
    Hvis jeg derimod lokalt minified og kombinerede filerne, uploade dem, så tog det webserveren 68ms at sende dem, og 76ms hvis jeg satte webserveren til at komprimer on-the-fly.

    Så hvis Google virkelig ser rigtig højt på hastigheden af dit site, dvs. ned til mindste ms, så gør det ordenligt, og ikke på den dovne måde. :)

    @Martin Din blog er faktisk et godt eksempel på hvordan man ikke skal gøre tingene, no offens :), hvorfor have img tags til feed.png 10 gange og lade browserne oprette 9 HTTP forbindelser uden grund? 😉

    Svar på kommentaren
  11. Hej Mads,
    At feed.png blev serveret fra forskellige urls var en fejl – rettet nu – men den skulle nu alligevel være gemt i Cache, så det går nok 😉

    Grunden til at PHP i nogen tilfælde vil være langsommere, er jo den overhead der naturligt kommer ved at kalde PHP. Specielt på trafiktunge sites vil dette ske, men så må man så også opveje det tidsforbrug man har ved manuelt at gzippe alt. Men før jeg fandt Minify havde jeg et PHP script som jeg kaldte hver gang jeg havde ændret CSS eller JS så blev alle filer gzippet og minifyed. Kunne være jeg skulle finpudse dette script og offentliggøre det. Dog vil jeg sige det må også være en balancegang, ønsker ikke at bruge unødigt tid på manuelt at komprimere en masse filer, så her kan jeg nu godt leve med den lille smule overhead PHP i visse situationer vil give!

    Minify skriver selv om de steder hvor det vil være hurtigere at serverer indhold uden om deres script, men samtidig også at de arbejder på en løsning der vil fjerne det overhead som PHP giver.

    Svar på kommentaren
  12. Super spændende artikel Martin. Det skal jeg helt sikkert igang med ovre hos mig :)

    Du skriver meget om det med at kombinerer scripts. Jeg bruger ikke frygteligt meget JS endnu, så det ene script jeg har ligger bare i selve html-filen og ikke eksternt. Jeg har desuden kun en enkelt CSS fil. Men jeg har selvfølgelig stadig en masse overflødige mellemrum og kommentarer i filerne som skal barberes væk – men har du en fornemmelse af hvad der betyder mest for performance? Mange eksterne filer eller masser af overflødige mellemrum?

    Hvordan måler du egentlig performance mens du arbejder med optimering? Måler du på antal ms det tager at hente en side eller antal kb der skal sendes? Og hvilket tool bruger du?

    Svar på kommentaren
  13. Jeg kan sige at jeg som udgangspunkt bruger YSlow-pluginet til Firefox. Det giver et godt billede af hvad man kan gøre. Både ved første hit og efterfølgende hits (altså cache browseren noget). PageTest er også et glimrende tool (open source) og kan hentes her –> http://pagetest.sourceforge.net/

    Mht. at sætte JavaScripts sammen o.lign. så er der mange artikler på nettet – både for og imod. White-spaces er ikke så slemme, især ikke taget i betragtning af hvis du bruger gzip – så bliver det jo bare pakket sammen :)

    Svar på kommentaren
  14. Hej Jacob,
    Som Peter skriver, så er YSlow rigtigt godt, bruger også PageSpeed som google anbefaler, giver ca samme informationer. Og ellers bruger jeg Chromes værktøjer der fortæller både kb hentet og ms og hvad det er der tager tid osv, så egentligt mange af de ting som YSlow fortæller også.
    GZIP komprimering og cache giver helt sikkert mest. Og som Peter skriver er whitespace ikke så farligt når gzip er slået til. Men hvis du som jeg skriver mange kommentarer i din CSS og JS, så fjerner minify også disse, og de bliver til en del bytes. Så selvom man kun har en CSS og en JS kan det godt betale sig at optimere disse.

    Det der virkelig taler for Minify er at den selv styrer cachen på baggrund af ændringer i filerne, så du ikke risikerer at dine tilbagevendende besøg får serveret en gammel JS fil eller CSS fil serveret. Ulempen ved dette er jo så at hvis man har slået 5 JS filer sammen og har lavet en ændring i en af disse, så vil man sende flere bytes til de tilbagevendende besøg, da alle 5 js filer jo så vil blive serveret som frisk indhold.

    Svar på kommentaren
  15. Hmmm… jeg har lige opdaget, at hvis man kører caching via htaccess metoden OG caching med W3TC samtidig, så får jeg problemer med at lave flere nye weblog indlæg lige i træk.

    Jeg kan godt oprette indlæg #1. Men hvis jeg umiddelbart efter vil lave indlæg #2, så tror wordpress bare, at det er en revision af indlæg #1. Jeg kan simpelthen ikke få den til at fatte, at der er tale om et nyt indlæg, som skal have et nyt ID.

    Efter at have slettet caching via htaccess virker alt igen.

    Andre der har samme oplevelse?

    Svar på kommentaren
  16. Jacob? Hvem Jacob? 😉

    Nå, anyways. Det er også min konklusion nu. Jeg er stoppet med at bruge caching via htaccess og nu virker alting. Og som bonus er mine sites faktisk hurtigere nu hvor der kun bruges W3TC :-)

    Svar på kommentaren
  17. Lige meget om man kører wordpress eller hjemmekodet bør man altid holde sig til en type cache.

    Fx hvis du på en hjemmekodet blog sætter en standard lifetime på 1 år i htaccess, så kan minify ikke nødvendigvis få lov til at sende friskt indhold til browseren når du har ændret i dine JS eller CSS.

    Ved ikke om W3C til wordpress også automatisk sætter cache på grafik og billeder, for ellers vil det være en god ide at gøre dette manuelt i htaccess:

    
    <FilesMatch \"\.(gif|jpg|png)\">
      Header set Cache-Control \"max-age=29030400\"
    </FilesMatch>
    
    Svar på kommentaren
  18. Jeg har gjort det for at prøve at tvinge en Expires Header på mine billeder. Det burde betyde at når en bruger har hentet mine billeder og besøger en ny side på min blog, er vedkommende ikke tvunget til at downloade samtlige billeder igen, men har en cached version af billederne på sin HD med en “Far Future” expirations dato.
    Standard er ofte at der ingen udløbsdata er på billederne, så selvom brugeren allerede har billedet liggende i sin egen cache, vil den downloade det på ny ved hver reload.

    Svar på kommentaren
  19. Der sker da lidt når man sådan holder en offline dag :-)

    @Claus underligt med din 404, kører med præcis den kode her på siden.

    @Michael, ja det er en rigtig god ide, som claus siger, så selvom browseren cacher et billede, hvis der ingen udløbsdato er på cache, så vil browseren i langt de fleste tilfælde hente en ny version af billedet. Så der er en del at hente.

    Svar på kommentaren
  20. Jeg må hellere lige korrigere mig selv vedr. Expires Header både på billeder og filer. (Browseren downloader ikke i alle tilfælde et billede hvis du allerede har det i cachen uden en Expires Header i fremtiden)

    Hvis browsere ser et billede i cachen uden Expires dato ude i fremtiden, vil den lave en Last Modified request til serveren og hører om hvornår billedet er redigeret sidste gang. Den dato sender serveren tilbage til browseren der så vurderer om last modified er nyere/ældre end sidste gang den hentede billedet. Hvis Last Modified er ældre, benytter den blot dit billede i cachen ellers skal den til at hente det nye billede. (og hele den procedure laver den for samtlige statiske filer)
    Så det bliver til en del unødig trafik & handshaking frem og tilbage mellem browser og server, man reelt kunne være foruden blot ved at knalde et par linier ind i sin .htaccess

    Svar på kommentaren
  21. @Claus jeps en fejl 500 gav ovenstående kode ind til for et par uger siden hvor jeg forespurgte mod_headers og mod_expires på unoeuros servere og fik det :-) så nu burde det virke

    Og super forklaring på hvornår der bliver hentet fra cache og ej! Og du har helt ret, specielt når der er meget trafik er der utroligt meget at hente ved at få lang expire sat på de statiske billeder GUCCA oplever virkelig et fald i data men ikke i trafik, så det er jo super!

    Svar på kommentaren
  22. Hej Martin.

    Fandt til alt held denne glimrende artikel, efter at Google Webmaster Tools anbefalede mig at komprimere CSS og JavaScript for at opnå en bedre Site Performance. :-) Har kastet mig over din guide til Minify, som var lige til at gå til. Herligt!

    Havde dog en smule bøvl med at få komprimeringen af JavaScript til at fungere. Fandt så ud af, at jeg skulle afslutte mit script-tag i et selvstændigt end tag og ikke sidst i start tag’et, som din guide viser. Måske fordi du benytter en anden DOCTYPE end jeg? (Jeg benytter visse steder 1.1 – og din er 1.0, kan jeg se)

    Svar på kommentaren
  23. Faldt lige over dette indlæg, da jeg forsøgte at få Minify til at køre på mit webhotel men fik en “open_basedir restriction” fejl. Efter at have sat en /temp-folder i min/config.php kører det som det skal.
    Tak for artiklen!

    Svar på kommentaren
  24. Pingback: Tip: Load dit JavaScript bibliotek igennem google | Martin Nielsens nørdede tanker

  25. Hej Martin

    Tak for dit indlæg – her et par år senere ;o)

    Jeg fandt dit indlæg på en Google-søgning efter “minify” og “UnoEuro” fordi jeg havde problemer med “open_basedir”. Jeg bruger WordPress, men ikke et plugin til dette. Derfor var min løsning at sætte cachePath til følgende:

    $min_cachePath = $_SERVER[‘DOCUMENT_ROOT’] .’/wp-content/uploads/cache';

    …blot hvis andre skulle få behov for den info.

    Svar på kommentaren

Leave a Reply to Jacob Worsøe Cancel reply

Krævede felter er markeret med *.

*