Pro Yearly is on sale from $80 to $50! »

Optimisez votre webapp

D3bcad37b1ec0fc8ecdeb199e54a61e4?s=47 Dev-Mind
October 28, 2016

Optimisez votre webapp

Le but de cette présentation est de rappeler différentes étapes pour optimiser une webapp : loadtime, first load, refresh, offline. Les slides comportent à la fin des exemples de configuration SpringBoot. https://github.com/Dev-Mind/web-performance

D3bcad37b1ec0fc8ecdeb199e54a61e4?s=128

Dev-Mind

October 28, 2016
Tweet

Transcript

  1. @guillaumeehret #softshake16 Optimiser une webapp Serveur et standards du web

    @guillaumeehret Guillaume EHRET - @guillaumeehret 2016
  2. @guillaumeehret #softshake16 @guillaumeehret Guillaume EHRET https://www.dev-mind.fr https://javamind-fr.blogspot.fr/

  3. @guillaumeehret #softshake16 https://giphy.com #softshake16 @guillaumeehret

  4. @guillaumeehret #softshake16 http://blog.mobileangelo.fr #softshake16 @guillaumeehret

  5. @guillaumeehret #softshake16 Nous voulons des sites webs performants

  6. @guillaumeehret #softshake16 https://giphy.com #softshake16 @guillaumeehret 57% des visiteurs abandonnent votre

    page si elle prend plus de 3 secondes à charger (loadtime)
  7. @guillaumeehret #softshake16 Le page rank est calculé sur des critères

    de performance : rapidité et sécurité #softshake16
  8. @guillaumeehret #softshake16 loadtime < 1 sec

  9. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Nous utilisons de plus en plus

    notre mobile pour surfer 53% des utilisateurs dans le monde utilisent Internet depuis leur mobile (C’est 75% aux USA)
  10. @guillaumeehret #softshake16 #softshake16 @guillaumeehret La qualité du réseau dépend du

    contexte
  11. @guillaumeehret #softshake16 #softshake16 @guillaumeehret ou de la zone géographique...

  12. @guillaumeehret #softshake16 7 6 @guillaumeehret Exemple Mesurez 1 2 3

    Feuilles de styles 4 HTTP 2 Limitez Images 8 Fonts 5 Asynchrone Service Worker Mise en cache Intro First load Other loads Offline Critical
  13. @guillaumeehret #softshake16 Exemple Backend : Java et Spring Boot Frontend

    : JS, HTML, CSS @guillaumeehret
  14. @guillaumeehret #softshake16 #softshake16 @guillaumeehret

  15. @guillaumeehret #softshake16 Mesurez Constatez les performances et les gains éventuels

    @guillaumeehret
  16. @guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com Utilisez les outils pour les

    développeurs dans les navigateurs
  17. @guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com Pour nos tests on désactive

    le cache et on se met en situation d’un réseau 3G
  18. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Fonctionnement du navigateur Le navigateur charge

    le fichier HTML et charge les ressources décrites dans leur ordre de définition HTML CSS JS download JS parse API call 1er affichage
  19. @guillaumeehret #softshake16 HTTP 2 Il est temps de changer @guillaumeehret

    1 Intro
  20. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Le navigateur limite le nb d’appels

    en parallèle
  21. @guillaumeehret #softshake16 #softshake16 @guillaumeehret HTTP 1 vers HTTP 2 La

    solution en HTTP1 ⇒ concaténer les ressources quand c’est possible
  22. @guillaumeehret #softshake16 #softshake16 @guillaumeehret avec HTTP 2

  23. @guillaumeehret #softshake16 #softshake16 @guillaumeehret avec HTTP 1

  24. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Utiliser HTTP 2 1. Choisir un

    container Jetty 9.2, Undertow (JBoss) 1.3, Tomcat > 8.5 2. Activer le mode HTTP 2 3. Créer un keystore 4. Paramétrer le serveur 5. Ajouter le jar ALPN au jdk utilisé pour lancer la webapp
  25. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Début Après % Temps total 14.13

    s 13.83 s -2.1% Passage à HTTP2 GAIN 13.8s -2.1% 14.13s ⇒
  26. @guillaumeehret #softshake16 Limiter Moins il y a de choses à

    transférer plus le chargement est rapide @guillaumeehret 2 First load
  27. @guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com

  28. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Minifier vos ressources Minifier consiste à

    retirer tous les espaces, les commentaires et les retours à la ligne inutiles du code JS : https://github.com/mishoo/UglifyJS2 HTML : https://github.com/jserme/htmlmin CSS : https://github.com/ben-eb/cssnano
  29. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Minifier vos ressources GAIN 13.2s -6.6%

    14.13s ⇒ Début Step -1 Après % HTML 4.3 ko 3.1 ko - 28% CSS 142 ko 116 ko -18% JS 51.5 ko 12 ko -76% Temps total 14.13 s 13.83 s (-2.1%) 13.2s -6.6%
  30. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Compresser les ressources Les navigateurs acceptent

    tous le gzip server: compression: enabled: true mime-types: application/json,text/html,text/css,application/javascript min-response-size: 2048
  31. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + minifié GAIN 8.5s -39%

    14.13s ⇒ Début Step -1 Après % HTML 4.3 ko 3.1 ko (-28%) 1.67 ko -62% CSS 142 ko 116 ko (-18%) 19.8 ko -86% JS 51.5 ko 12 ko (-76%) 9 ko -82% Json (rest) 187 ko 175 ko (-6.5%) 63 ko -66% Temps total 14.13 s 13.2s (-6.6%) 8.5s -39%
  32. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Limiter la taille des Json avec

    JsonView public class SessionDto { public String lang; public String format; public String title; public String summary; public String description; public String ideaForNow; ... }
  33. @guillaumeehret #softshake16 #softshake16 @guillaumeehret JsonView…. ou on crée des DTO

    qui collent à l’API public class SessionDto { public interface SessionList {} public String lang; public String format; @JsonView(SessionList.class) public String title; @JsonView(SessionList.class) public String summary; public String description; public String ideaForNow; ... } public class SessionListDto { public String title; public String summary; ... }
  34. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Limiter la taille des Json (2/2)

    @GetMapping @JsonView(SessionDto.SessionList. class) public List<SessionDto> getAllSessions() { return sessionRepository.findAllSessions().stream() .filter(session -> nonNull(session.getStart())) .sorted(Comparator. comparing(Session::getStart)) .map(SessionDto:: convert) .collect(Collectors. toList()); }
  35. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + JsonView GAIN 7.9s -44%

    14.13s ⇒ Début Step -1 Après % Json (rest) 187 ko 63 ko (-66% ) 34 ko -81% Temps total 14.13 s 8.5s (-39%) 7.9s -44%
  36. @guillaumeehret #softshake16 Les images Optimisez l’utilisation des images @guillaumeehret 3

    First load
  37. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Réduire la taille gulp.task('images', () =>

    gulp.src(`${paths.main}/images/**/*.{svg,png,jpg}`) .pipe($.newer(`${paths.tmp}/img`)) .pipe($.imagemin({ progressive: true, interlaced: true, arithmetic:true })) .pipe(gulp.dest(`${paths.dist}/img`)) ); https://github.com/imagemin/imagemin
  38. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Images compressées GAIN 7.1s -50% 14.13s

    ⇒ Début Step -1 Après % Images 459 ko 386 ko -15.8% Temps total 14.13 s 7.9s (-44%) 7.1s -50%
  39. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple d’images <img …. <div...

  40. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Gérer le responsive en CSS <div

    class="dm_img__inbackground">Mon texte</div> .dm_img__inbackground{ background-size: cover; @media only screen and (min-width: 0) { background-image: url("/img/mixit/mixit-amphi2_640.jpg"); } @media only screen and (min-width: 641px) and (max-width: 1024px) { background-image: url("/img/mixit/mixit-amphi2_1024.jpg"); } @media only screen and (min-width: 1025px) { background-image: url("/img/mixit/mixit-amphi2_2048.jpg"); } }
  41. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Et quid de la balise img

    ? <img src="img/mixit/mixit-amphi_2048.jpg">
  42. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Propriété srcset <img src="img/mixit/mixit-amphi_640.jpg" srcset="img/mixit/mixit-amphi_640.jpg 640w,

    img/mixit/mixit-amphi_1024.jpg 1024w, img/mixit/mixit-amphi_2048.jpg">
  43. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Propriété srcset <img src="img/mixit/mixit-amphi_640.jpg" srcset="img/mixit/mixit-amphi_640.jpg 640w,

    img/mixit/mixit-amphi_1024.jpg 1024w, img/mixit/mixit-amphi_2048.jpg"> srcset : liste des URLs des images séparées par des virgules. Chaque URL peut être suivie de 2 options permettant au navigateur de déterminer quelle image utilisée en fonction du contexte 1. la largeur de la fenêtre : nombre entier suivi par 'w' (640w), par défaut c’est l’infini 2. la densité de pixel : nombre entier suivi par 'x' (2x), par défaut c’est 1x. De 0 à 640px ⇒ mixit-amphi_640 De 641 à 1024px ⇒ mixit-amphi_1024 De 1025 à infini ⇒ mixit-amphi_2048
  44. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Utiliser la balise picture Elle offre

    plus de souplesse pour faire des niveaux de zooms différents en fonction des devices (art direction)
  45. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple balise picture <picture> <source media="(max-width:

    640px)" srcset="img/mixit/mixit-amphi_640.jpg" /> <source media="(max-width: 1024px)" srcset="img/mixit/mixit-amphi_1024.jpg" /> <source srcset="img/mixit/mixit-amphi_2048.jpg" /> <img src="img/mixit/mixit-amphi_640.jpg"> </picture>
  46. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Tenter le format webp gulp.task('images-webp', ['images'],

    () => gulp.src(`${paths.tmp}/img/**/*.{png,jpg}`) .pipe($.webp()) .pipe(gulp.dest(`${paths.dist}/img`)) ); https://developers.google.com/speed/webp/ https://github.com/imagemin/imagemin-webp https://github.com/mozilla/mozjpeg
  47. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple balise picture avec type webp

    <picture class="img-responsive"> <source srcset="img/mixit/mixit-amphi_640.webp 640w, img/mixit/mixit-amphi_1024.webp 1024w, img/mixit/mixit-amphi_2048.webp" type="image/webp" class="img-responsive"/> <source srcset="img/mixit/mixit-amphi_640.jpg 640w, img/mixit/mixit-amphi_1024.jpg 1024w, img/mixit/mixit-amphi_2048.jpg" class="img-responsive"/> <img src="img/mixit/mixit-amphi_640.jpg" alt="Mix-IT amphi" class="img-responsive"> </picture>
  48. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + webp Jpg, png Jpg

    +compres webp Gain Image 459 ko 386 ko 163.4 ko -64.4% Temps total 14.13 s 7.1s 4.33s -69% GAIN 4.33s -69% 14.13s ⇒
  49. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Quel format utiliser? Format Transparence Couleurs

    Compression Animation Navigateur Use Case GIF Oui 256 Sans perte Oui Tous Animation PNG Oui Au choix Sans perte Non Tous Fond transparent PNG 8 bit Oui 256 Sans perte Non Tous Couleur lmitée JPEG Non Toutes Au choix Non Tous Photos WebP Oui Toutes Avec perte Oui Chrome, Opera, Android Photos SVG Oui Toutes Sans perte Non Tous Logo, diagramme
  50. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Préférez le vectoriel png ou jpg

    svg
  51. @guillaumeehret #softshake16 Les feuilles de styles Keep it simple stupid

    @guillaumeehret 4 First load
  52. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Framework CSS Material Design Lite

  53. @guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com

  54. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Framework CSS…. un coût bootstrap.min.css :

    121.2 Ko bootstrap.min.js : 37 Ko jquery.slim.min.js: 69.3 Ko Material Design Lite material.min.css : 141 Ko material.min.js : 62.3 Ko foundation.min.css : 77.4 Ko foundation.min.js : 107.9 Ko jquery.slim.min.js: 69.3 Ko base-min.css : 3 Ko pure-min.css : 17.2 Ko
  55. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Nettoyer vos feuilles de styles uncss

    permet de supprimer les styles non utilisés return gulp.src(`${paths.main}/component/**/*.css`) .pipe($.if('*.css', $.uncss({ html : [`${paths.main}/*.html`, `${paths.main}/component/**/*.html`, `${paths.main}/component/**/*.js`] }))) .pipe($.if('*.css', $.cssnano())) .pipe(gulp.dest(`${paths.dist}/styles`));
  56. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + uncss GAIN 4.16s -70%

    14.13s ⇒ Début Step -1 Après % CSS 142 ko 19.8 ko (-86%) 2.8 ko -98% Temps total 14.13 s 8.5s (-39%) 4.16s -70.5%
  57. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Quelques astuces N’utilisez que les modules

    nécessaires de votre framework CSS Apprenez le CSS : animations, les flexbox remplacent de longues lignes de code…. Utilisez normalize.css pour fixer les disparités entre navigateur et autoprefixer pour automatiquement ajouter les préfixes propres à chaque navigateur pour les fonctions expérimentales
  58. @guillaumeehret #softshake16 Les fonts Utilisez le bon format @guillaumeehret 5

    First load
  59. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Utilisez le moins de font possible

  60. @guillaumeehret #softshake16 Format Extension Exemple Font Robot Compression EOT .eot

    179 ko ⬤ TrueType .ttf 179 ko ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ WOFF .woff 89 ko ⬤ ⬤ >10 ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ WOFF2 .woff2 64ko ⬤ 30% + que WOFF ⬤ ⬤ ⬤ ⬤ >10 ⬤ ⬤ ⬤ > 10 ⬤ SVG .svg 743 ko ⬤ ⬤ #softshake16 @guillaumeehret Les différents formats IE Edge Firefox Chrome Safari Opera Opera Mini Android Browser iOS Safari Chrome for Android
  61. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Déclarer une police avec @font-face @font-face

    { font-family: Roboto; src: local('Roboto'), url(../fonts/Roboto-Regular.woff2) format('woff2'), url(../fonts/Roboto-Regular.woff) format('woff'), url(../fonts/Roboto-Regular.eot), url(../fonts/Roboto-Regular.ttf) format('truetype'), url(../fonts/Roboto-Regular.svg#Roboto) format('svg'); font-weight: 300; font-style: normal } body { font-family: Roboto, Arial, sans-serif; font-weight: 300 }
  62. @guillaumeehret #softshake16 Mettre en cache Pourquoi recharger une ressource qui

    n’a pas changé ? @guillaumeehret 6 Second loads
  63. @guillaumeehret #softshake16 #softshake16 @guillaumeehret La ressource la plus rapide et

    la mieux optimisée est une ressource qui n'est pas envoyée.
  64. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache base de données Coût élevé

    dans le cloud avec une tarification liée au volume de données Les temps d’accès fluctuent beaucoup Abstraction Spring pour différente solution de cache partagé ou non Generic, JCache, EhCache, Hazelcast, Infinispan, Couchbase, ...
  65. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache des ressources Le serveur peut

    renvoyer des informations de cache pour le navigateur Cache-Control max-age exprime la durée pendant laquelle la ressource est valide ETag token qui permet d’identifier la version de la donnée
  66. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache des ressources

  67. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration par défaut Par défaut pas

    de cache sur les ressources
  68. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring Ajoute un cache-control pour

    toutes les ressources statiques (ici de 10 jours) Mais on peut avoir des problèmes de mise à jour du cache.
  69. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring pour ETag

  70. @guillaumeehret #softshake16 #softshake16 @guillaumeehret ... + cache GAIN 2.1s -85%

    14.13s ⇒ Début Step -1 Après % Temps total 14.13 s 4.16s (-70.5%) 2.1s -85.5%
  71. @guillaumeehret #softshake16 Service worker Mode offline, cache de ressources côté

    client @guillaumeehret 7 Offline
  72. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Que se passe t-il en offline

    ?
  73. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Les SW se basent sur les

    Webs workers Javascript est mono-thread et tout passe par le même endroit Les web workers permettent d’exécuter des traitements dans un thread séparé mais ils n’auront pas accès au DOM. La communication se fait par échange de messages
  74. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Créer un Web worker Dans votre

    script principal var worker = new Worker('doWork.js'); worker.addEventListener ('message', function(e) { console.log('Worker said: ', e.data); }, false); worker.postMessage('Hello World'); // Send data to our worker. Le script dédié au web worker self.addEventListener ('message', function(e) { self.postMessage(e.data); }, false);
  75. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Service worker Ils peuvent accéder à

    d’autres API comme IndexedDB API Équivaut à un proxy entre votre client et le serveur Les services workers ne sont accessibles qu’en HTTPS
  76. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Cycle de vie d’un service worker

  77. @guillaumeehret #softshake16 #softshake16 @guillaumeehret sw-precache et sw-toolbox Grâce à Google

    les services workers peuvent être vus comme une boîte noire. sw-precache : module node qui fait le boulot pour vous. sw-toolbox : lib JS qui fournit des utilitaires pour créer vos propres service workers. Ils proposent entre autre différent pattern pour gérer le cache.
  78. @guillaumeehret #softshake16 #softshake16 @guillaumeehret gulp.task('generate-service-worker', ['copy-sw-scripts'], () => { const

    filepath = path.join(paths.tmp, 'service-worker.js'); return swPrecache.write(filepath, { cacheId: 'dev-mind', importScripts: [ 'scripts/sw/sw-toolbox.js', 'scripts/sw/runtime-caching.js' ], staticFileGlobs: [ paths.dist + '/**/*.{js,html,css,png,jpg,json,gif,svg,webp,eot,ttf,woff,woff2}'], stripPrefix: paths.dist + '/' }); });
  79. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Le script runtime-caching global.toolbox.router.get('/(.*)', global.toolbox.fastest, {});

  80. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Stratégie sw-toolbox toolbox.networkFirst fait la requête

    et stocke les résultats dans le cache. Sinon regarde dans le cache si on a rien (bien quand vous voulez toujours les données les plus fraîches) toolbox.cacheFirst si présent dans le cache répond immédiatement, sinon appelle le réseau. A n’utiliser que pour des ressources qui ne change pas toolbox.fastest lance les 2 en parallèles et c’est le plus rapide qui gagne. toolbox.cacheOnly ne regarde que le cache. Utile quand le réseau est tombé ou lorsque vous voulez préserver la batterie d’un mobile... toolbox.networkOnly un peu comme si on n’utilisait pas les services workers
  81. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Dans votre application if ('serviceWorker' in

    navigator) { navigator.serviceWorker.register('service-worker.js').then(function(registration) { if (typeof registration.update === 'function') { registration.update(); } }).catch(function(e) { console.error('Error during service worker registration:', e); }); }
  82. @guillaumeehret #softshake16 #softshake16 @guillaumeehret

  83. @guillaumeehret #softshake16 Asynchrone Reportez le chargement de ce qui n’est

    pas nécessaire au premier chargement @guillaumeehret 8 Critical
  84. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Le but est d’optimiser le chemin

    critique du rendu (Critical rendering path) En gros charger le plus important tout de suite et le reste plus tard
  85. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Quand vos scripts sont dans le

    <head> <html> <head> <!-- ... → <script src="scripts/vendors/vendor.min.js"></script> <script src="scripts/app.js"></script> <!-- ... --> </head> <body> <!-- ... --> </body> </html>
  86. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 5 sec après

    le chargement CSS + JS
  87. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Quand vos scripts sont à la

    fin de <body> <html> <head> <!-- ... --> </head> <body> <!-- ... → <script src="scripts/vendors/vendor.min.js"></script> <script src="scripts/app.js"></script> <!-- ... --> </body> </html>
  88. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 1.5 sec après

    le chargement CSS
  89. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 1.5s -70%

    5s ⇒ Script Header Script body % Temps total 5 s 1.5 s -70 %
  90. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des JS La propriété

    async permet de reporter le chargement de vos scripts <script async src="scripts/vendors/jquery.slim.min.js"></script> <script async src="scripts/vendors/bootstrap.min.js"></script>
  91. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des CSS Les navigateurs

    pourront bientôt charger en asynchrone vos feuilles de styles <link rel="preload" href="styles/app.css" as="style" onload="this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="styles/app.css"></noscript> LoadCSS permet d’anticiper cette nouvelle fonctionnalité <script src="scripts/vendors/loadCSS.js"></script> <script src="scripts/vendors/cssrelpreload.js"></script>
  92. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des fonts Utilisez l’API

    Web Font Loading var font = new FontFace("Roboto", "url(/fonts/Roboto-Regular.woff2)", { style: 'normal', weight: '300' }); font.load(); font.ready().then(function() { document.fonts.add(font); document.body.style.fontFamily = "Roboto, Arial, sans-serif"; });
  93. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 500 ms après

    le chargement HTML
  94. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Oops une page pas très jolie

  95. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner vos critical CSS Pour avoir

    un rendu rapide inliner votre CSS minimal pour afficher une page qui ressemble à quelque chose Inliner aussi votre logo pour que celui-ci d’affiche directement
  96. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner votre logo <svg xmlns="http://www.w3.org/2000/svg" viewBox="0

    0 71 19"> <g stroke-width="0" fill="#00adee"> <ellipse rx=".416" ry=".36" cy=".742" cx="23.251"/><ellipse rx=".416" ry=".353" cy="1.435" cx="23.795"/><ellipse rx=".416" ry=".353" cy=".733" cx="27.206"/><ellipse rx=".416" ry=".353" cy="1.432" cx="27.203"/><path d="M23.21.385h3.967v1.403H23.21z"/><path d="M22.835.715h4.786v.694h-4.785z"/><ellipse rx=".416" ry=".36" cy="1.424" cx="23.251"/></g><g transform="translate(0 -31)" stroke-width="0"><g fill="#00adee"><g fill-rule="evenodd"><ellipse rx="2.317" ry="1.61" cy="47.814" cx="10.304"/><ellipse rx="2.251" ry="1.61" cy="47.825" cx="17.923"/><ellipse rx="2.117" ry="1.61" cy="47.832" cx="2.53"/><path d="M5.037 34.198c-2.547 0-4.612 1.364-4.612 3.047.002 1.59 1.74 1.4 4.14 1.522l.115-.05h3.323v.856c1.042-.578 1.644-1.43 1.646-2.33 0-1.682-2.066-3.045-4.613-3.046zM15.567 34.195a4.612 3.03 0 0 1 4.612 3.03 4.612 3.03 0 0 1-4.256 3.02v-1.553H12.6v.85a4.612 3.03 0 0 1-1.645-2.316 4.612 3.03 0 0 1 4.612-3.03z"/></g><path d="M.42 37.366h4.225v10.55H.42zM7.99 37.418h4.635V47.88H7.99zM15.67 37.315h4.506v10.478H15.67z"/><ellipse rx="2.436" ry="2.392" cy="47.049" cx="32.474"/><ellipse rx=".14" ry=".029" cy="46.435" cx="33.073"/><ellipse rx="2.422" ry="2.363" cy="36.403" cx="32.485"/><ellipse rx="2.345" ry="2.363" cy="47.06" cx="25.356"/><ellipse rx="2.348" ry="2.363" cy="36.462" cx="25.362"/><path d="M23.015 36.586h4.688v10.5h-4.688z"/></g><g fill="#7f7f7f"><path d="M58.855 34.515h11.87v1.066h-11.87z"/><path d="M58.855 35.448h1.132v2.734h-1.132zM69.6 35.507h1.102v2.653H69.6zM64.213 35.502h1.117V48.5h-1.117z"/><path d="M61.964 48.317h5.652v1.14h-5.652zM50.943 48.3h5.652v1.138h-5.652z"/></g><path fill="#787878" d="M53.18 35.446H54.3v12.988H53.18z"/><path fill="#7f7f7f" d="M50.95 34.496h5.653v1.14H50.95zM46.15 41.265h3.39v.853h-3.39z"/><path d="M30.773 38.067l3.56 3.477 3.32-3.2-3.41-3.565z" fill-rule="evenodd" fill="#00adee"/><ellipse rx="2.422" ry="2.363" cy="36.389" cx="42.899" fill="#7f7f7f"/><ellipse rx="2.422" ry="2.363" cy="47.068" cx="42.936" fill="#7f7f7f"/><g fill-rule="evenodd"><path d="M34.32 41.54l3.33-3.2 3.328 3.347-3.28 3.25z" fill="#005676"/><path d="M30.795 45.326l3.534-3.78 3.378 3.398-3.26 3.52z" fill="#00adee"/><path d="M41.106 48.618l-3.4-3.687 3.276-3.246 3.813 3.847-3.66 3.103z" fill="#7f7f7f"/><path d="M41.187 34.728l-3.57 3.626 3.36 3.34 3.9-3.916z" fill="#7f7f7f"/></g><path fill="#00adee" d="M4.88 34.196h10.903v3.716H4.88z"/></g> </svg>
  97. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner vos styles principaux <html lang="fr">

    <head> <!-- ... --> <link rel="preload" href="styles/app.css" as="style" onload="this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="styles/app.css"></noscript> <!-- Inlined styles for critical rendering path --> <style> main { flex: 1 1 auto; overflow-x: hidden; overflow-y: scroll; } // ... </style> </head> <body></body> </html>
  98. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 650 ms Mais

    avec un rendu minimal
  99. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 650ms -87%

    5s ⇒ Script Header Script body + async + inline % First paint 5 s 650ms -87 %
  100. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Objectifs atteint sur 3G Script Header

    Script body % First paint 6 s 650ms -70 % Temps total 14.13 s 4.4s -87 % Offline/refresh 14.13 s 1.8s -87 %
  101. @guillaumeehret #softshake16 13.2s -6.6% Compress @guillaumeehret Exemple Page des sessions

    Mesurer 14.3s 1 2 HTTP 2 Limiter 8 Asynchrone Intro First load Other loads Offline Critical 13.8s -2.1% Minify 8.5s -39% Json 7.9s -44% 7.1s -50% Format webp 3 Images Compress 4.3s -69% Résumé des temps observés sur une connexion 3G (750kb/s ⇓ 250kb/s ⇑) Feuilles de styles 4 4.16s -70% Uncss Fonts 5 7 6 Service Worker Mise en cache 2.1s -85%
  102. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Ressources https://developers.google.com/web/fundamentals/performance https://github.com/ben-eb/gulp-uncss https://github.com/filamentgroup/loadCSS https://github.com/necolas/normalize.css https://github.com/postcss/autoprefixer

    http://www.stylestats.org/ https://www.html5rocks.com/en/tutorials/workers/basics/ https://html.spec.whatwg.org/multipage/workers.html https://github.com/GoogleChrome/sw-precache https://github.com/GoogleChrome/sw-toolbox https://developer.mozilla.org/fr/docs/Web/API/ServiceWorker https://jakearchibald.com/2014/offline-cookbook/ https://www.youtube.com/watch?v=FEs2jgZBaQA
  103. @guillaumeehret #softshake16 #softshake16 @guillaumeehret https://github.com/Dev-Mind/web-performance

  104. @guillaumeehret #softshake16 @guillaumeehret questions

  105. @guillaumeehret #softshake16 Merci @guillaumeehret

  106. @guillaumeehret #softshake16 Annexes @guillaumeehret

  107. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration serveur https://github.com/h5bp/server-configs

  108. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring Boot pour HTTP2 1.

    Utilisez un container compatible Jetty 9.2, Undertow, Tomcat > 8.5 Changez le serveur par défaut compile("org.springframework.boot:spring-boot-starter-web") { exclude module: "spring-boot-starter-tomcat" } compile "org.springframework.boot:spring-boot-starter-undertow"
  109. @guillaumeehret #softshake16 #softshake16 @guillaumeehret 2. Activer le mode HTTP 2

    @Configuration public class HttpConfig { @Bean public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() { UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory(); factory.addBuilderCustomizers(builder -> builder.setServerOption( UndertowOptions. ENABLE_HTTP2, true)); return factory; } }
  110. @guillaumeehret #softshake16 #softshake16 @guillaumeehret 3. Créer un keystore dans /src/main/resources

    keytool -genkeypair -alias mycert -keyalg RSA -sigalg MD5withRSA -keystore perf.jks -storepass DevMind -keypass DevMind -validity 9999 4. Le paramétrer dans application.yml server: ssl: key-store: classpath:perf.jks key-store-password: DevMind key-password: DevMind protocol: TLSv1.2
  111. @guillaumeehret #softshake16 #softshake16 @guillaumeehret 5. Activer ALPN (Application Layer Protocol

    Negotiation) ALPN permet de définir quel protocole utiliser pour une connection sécurisée. Vous devez l’ajouter dans vos options de démarrage de la JVM java -Xbootclasspath/p:<path_to_alpn_boot_jar> ... Il existe des implémentations pour chaque version d’Open JDK (http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html #alpn-versions )
  112. @guillaumeehret #softshake16 #softshake16 @guillaumeehret @Configuration @EnableCaching public class WpCacheConfig {

    public static final String CACHE_SPONSOR = "sponsor"; public static final String CACHE_SESSION = "session"; @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager( CACHE_SESSION, CACHE_SPONSOR); } } Configuration Cache serveur
  113. @guillaumeehret #softshake16 #softshake16 @guillaumeehret public interface SessionRepository extends CrudRepository<Session, Long>

    { @Cacheable(WpCacheConfig. CACHE_SESSION) @Query(value = "SELECT DISTINCT s FROM Session s left join fetch s.speakers sp left join fetch s.votes") List<Session> findAllSessions(); }
  114. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration cache de ressources Spring @Configuration

    public class WpCacheHttpConfig { @Bean public WebMvcConfigurer configurer() { return new WebMvcConfigurerAdapter() { public void addResourceHandlers(ResourceHandlerRegistry reg) { reg.addResourceHandler( "/**/*") .addResourceLocations( "classpath:/static/") .setCacheControl(CacheControl. maxAge(10, TimeUnit.DAYS)); } }; } }
  115. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring pour Etag @Configuration public

    class WpCacheHttpConfig { @Bean public WebMvcConfigurer configurer() { return ... } @Bean public Filter etagFilter() { return new ShallowEtagHeaderFilter(); } }
  116. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache sur appel REST en GET

    @GetMapping public ResponseEntity<List<MemberDto>> getAllSponsors() { List<MemberDto> sponsors = sponsorRepository.findAllSponsor().stream() .map(MemberDto:: convert) .collect(Collectors. toList()); return ResponseEntity .ok() .cacheControl(CacheControl. maxAge(2, TimeUnit.DAYS)) .eTag(String. valueOf(sponsors.size() + sponsors.stream() .collect(Collectors. summingInt(MemberDto::getVersion)))) .body(sponsors); }
  117. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Tester vos services workers google-chrome --user-data-dir=/tmp/foo

    --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost