Quelques astuces pour booster votre nouveau site

Posté le February 28, 2018

Lorsque que je publie un nouveau site, je vérifie ses performance en utilisant des outils tels que WebPageTest. J'obtiens ainsi une analyse complète avec différentes pistes pour l'améliorer.

Ce blog n'a pas fait exception et les résultats ont été flagrants: un C pour le "First-Byte time" et un F pour "Cache static assets" et "Compress images". De plus, mon Speed index était aux alentours de 1400; pas trop mal mais plus élevé que le max recommandé (1000).

Visiblement, je pouvais améliorer certaines choses.

Compresser les images

Les images sont un point important quand on commence à parler de performance, car ce sont souvent les fichiers les plus lourds. Malgré tout, on peut facilement réduire leurs tailles. Sur ce blog, j'utilise une seule image, dans le header. Avant optimisation elle pesait ~1.1mo ... plus de 90% du poids total de mes resources !

Sur mon Mac, j'utilise imagemagick pour compresser mes images en suivant quelques régles :

 magick convert INPUT.jpg -sampling-factor 4:2:0 -strip -quality 85 -interlace JPEG -colorspace Gray OUTPUT.jpg

Régler la qualité à 85 permet de réduire considérablement la taille du ficher, tout en gardant un bon rendu visuel. Dans mon cas, j'ai même décidé de la réduire à 50.

Avec ça, la taille finale de mon image n'est plus que de 173ko. Parfait !

Au delà de la compression, il faudrait également considérer l'utilisation des images responsives.

Réduire la taille des CSS/JS

Pour réduire la taille des CSS/JS, une étape incontournable est la minification. Si, comme moi, vous utilisez Laravel mix, c'est très simple:

# yarn or npm
yarn run production

Cette commande execute la tâche production qui va minifier les ressources sans configuration supplémentaire.

Malgré tout, il faut tout de même faire attention à ce que l'on inclut dans nos CSS ou JS. Sur des groses bibliothèques, comme Bootstrap, il est souvent possible d'importer les modules individuellement. Cela permet de réduire la taille finale des ressources, en gardant uniquement le nécessaire.

Sur ce projet, j'ai utilisé TailwindCSS, un récent framework CSS utility-first, et il s'est avéré très pratique. En gros, l'idée est d'écrire tout son CSS sous forme de classes utilitaires et les appliquer ensuite directement dans son HTML. J'étais plutôt sceptique la première fois que j'en ai entendu parler, mais maintenant je me dis que c'était le maillon manquant dans mon workflow front-end. À titre d'introduction, je recommande ce post par Adam Wathan, l'un des créateurs de TailwindCSS.

Le principal "revers" de TailwindCSS concerne la taille du CSS compilé. Cela est principalement dû au fait que TailwindCSS fournit énormément d'utilitaires dans sa config par défaut: breakpoints (5 tailles d'écrans), couleurs (10 couleurs et 7 nuances pour chacunes), etc. Heureusement, TailwindCSS est facilement paramètrable et la documentation dispose d'une page consacrée au contrôle de la taille du fichier.

L'un des points abordés est l'usage de PurgeCSS, un outil pour retirer automatiquement le CSS inutilisé d'un projet. La bonne nouvelle c'est que l'on peut facilement l'ajouter à notre workflow Laravel Mix, comme expliqué dans la documentation. Encore mieux, il y a un plugin par Spatie pour gérer ça:

yarn add laravel-mix-purgecss

Ensuite, il suffit d'écrire:

const mix = require('laravel-mix');

require('laravel-mix-purgecss');

// Here are your build task like mix.sass or mix.js

mix.purgeCss();

Si vous n'utilisez pas de librairies externes, cela devrait suffire ! Sinon, il faudra surement un peu de config. Par défaut, le plugin recherche les classes à garder uniquement dans les répertoires app et resources. Cela signifie que toutes les CSS issues des node_modules seront supprimées du fichier de sortie. Pour éviter cela, il faut explicitement dire à PurgeCSS ce que l'on veut garder:

mix.purgeCss({
    globs: [
        // Keep all classes found in JS files in 'node_modules/simplemde'
        path.join(__dirname, 'node_modules/simplemde/**/*.js'),
    ],
    // Keep all classes matching these patterns
    whitelistPatterns: [/hljs/, /noty/],
})

Sur ce site, la taille de mon fichier est passée de 240ko à 16.5ko 💪. Vous trouverez ma config sur Github.

Polices

Comme beaucoup de gens, j'aime utiliser Google Fonts sur mes projets. Cependant, il y a quelques astuces à savoir pour en optimiser l'usage.

Déjà, j'essaie de limiter le nombre de polices, mais également de graisses. Cela rend l'import bien plus léger. Ici, j'ai essayé d'utiliser uniquement les graisses "normal" et "gras", et d'utiliser plutôt les nuances de couleurs pour définir l'importance (plus clair = moins important, plus foncé = plus important).

Une astuce que je ne connaissais pas consiste à importer toutes les polices en une seule fois grâce à l'opérateur |:

<!-- Avant -->
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Satisfy" rel="stylesheet">

<!-- Après -->
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700|Satisfy" rel="stylesheet">

D'ailleurs, j'utilise la police Satisfy uniquement pour mon logo. Je me demande s'il serait judicieux de remplacer cela par un SVG, mais actuellement je ne sais pas ce qui est le mieux.

Dernier truc pour booster Google Fonts: le lien preconnect. C'est très bien expliqué ici, en anglais.

Le type de relation "preconnect" est utilisé pour indiquer une origine qui sera utilisée pour récupérer des ressources. L'initialisation d'une connexion précoce, incluant les négociations DNS, TCP et TLS, permet de masquer la latence élevée liée à l'établissement d'une connexion.

Il suffit d'ajouter dans sonhead:

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>

Le lien preconnect doit également être utile si vous utilisez des domaines externes, comme des CDNs, pour récupérer d'autres ressources.

Quelques optimisations côté serveur

Maintenant que nos ressources sont prêtes, allons configurer le serveur web. J'utilise Nginx mais Apache sait faire la même chose.

Activer la compression GZIP

Déjà, vérifiez que GZIP est activé. Sur mon serveur, tout était déjà OK, mais sinon c'est assez facile à faire. Il suffit d'ouvir le fichier de config /etc/ngninx/nginx.conf, et de rechercher la section GZIP. Puis, décommentez les paramètres par défaut:

gzip on;
gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;

Comme vous pouvez le voir, on peut choisir les types MIME à compresser. Les images (et musiques, vidéos, PDFs, ...) sont déjà compressées donc il n'y a pas besoin de les recompresser avec GZIP. A vrai dire, cela pourrait même être pire, comme on peut le lire ici, en anglais.

Redémarrez Nginx:

sudo service nginx restart

Vérifiez que tout fonctionne:

curl -H "Accept-Encoding: gzip" -I https://yourdomain.com/path/to/css

# Response should have the following header
Content-Encoding: gzip

Activer HTTP/2

HTTP/2 existe maintenant depuis quelques temps, est compatible avec tous les navigateurs modernes, et est réputé plus rapide que sa version précédente. Profitons en pour l'activer.

Il nécessite quelques prérequis, principalement d'utiliser HTTPS avec un certificat SSL valide. En dehors de cela, activer HTTP/2 est facile dans Nginx, il suffit d'ajouter cela dans sa config:

# /etc/nginx/sites-enable/yoursite

server {
    listen 443 ssl http2;
		
    ...
}

Si vous êtes malchanceux comme moi, vous pourrez rencontrer quelques soucis:

  • HTTP/2 est supporté uniquement sur Nginx >= 1.9.5
  • Nginx doit être compilé avec OpenSSL >= 1.02

Ces liens m'ont bien aidé (en anglais):

Mise en cache des ressources statiques

Enfin, gérons la mise en cache de nos ressources statiques (CSS, JS, images, ...). Encore une fois, facile avec Nginx. Ajoutez un bloc location dans votre bloc server:

# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
}

Cela autorisera la mise en cache des fichiers JP(E)G, PNG, GIF, ICO, CSS et JS pour 30 jours.

Cependant, il y a un petit problème si vous utilisez le versioning de Laravel Mix. Cette fonctionnalité ajoute un query string aux fichiers CSS et JS, le but étant d'invalider le cache des fichiers lorsque ceux-ci changent. Le problème avec notre config Nginx, c'est que du coup ces fichiers ne seront jamais autorisés à être mis en cache, justement à cause de ce query string !

Corrigeons cela (notez le ? à la fin du motif):

# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)?$ {
    expires 30d;
}

Bien sûr, vous pouvez définir différentes stratégies selon le type de fichier:

# Cache image for 30 days
location ~* \.(jpg|jpeg|png|gif)?$ {
    expires 30d;
}

# Cache css and js for 7 days
location ~* \.(css|js)?$ {
    expires 7d;
}

Conclusion

Après toutes ces optimisations (en dehors de GZIP déjà activé), j'ai relancé un test sur WebPageTest. Les résultats sont satisfaisants: uniquement des A et un speed index aux alentours de 800. Même si j'ai rencontré quelques soucis pour mettre en place ces optimisations, cela valait le coût.

Je pense qu'il reste pas mal d'améliorations possibles, et une future étape à explorer serait le cache serveur. Il existe des solutions réputées comme Varnish ou nginx_fastcgi_cache. J'aimerais également donner une chance à spatie/laravel-responsecache. Vaste et beau sujet pour un prochain post !