Mesurer l'impact réel des service workers sur les performances

L'un des principaux avantages des service workers (du moins du point de vue des performances) est leur capacité à contrôler de manière proactive la mise en cache des ressources. Une application Web capable de mettre en cache toutes ses ressources nécessaires devrait se charger beaucoup plus rapidement pour les visiteurs réguliers. Mais à quoi ressemblent ces gains pour les utilisateurs réels ? Et comment mesurer cela ?

L'application Web Google I/O (IOWA, pour "Google I/O Web App") est une application Web progressive qui exploite la plupart des nouvelles fonctionnalités offertes par les service workers pour offrir une expérience riche, semblable à une application, à ses utilisateurs. Elle a également utilisé Google Analytics pour collecter des données de performances et des tendances d'utilisation clés auprès de son audience d'utilisateurs large et diversifiée.

Cette étude de cas explique comment l'IOWA a utilisé Google Analytics pour répondre à des questions clés sur les performances et créer des rapports sur l'impact concret des service workers.

En commençant par les questions

Chaque fois que vous implémentez des données analytiques sur un site Web ou dans une application, il est important de commencer par identifier les questions auxquelles vous essayez de répondre à partir des données que vous allez collecter.

Nous avions plusieurs questions à poser, mais pour les besoins de cette étude de cas, nous allons nous concentrer sur deux des plus intéressantes.

1. La mise en cache des service workers est-elle plus performante que les mécanismes de mise en cache HTTP existants disponibles dans tous les navigateurs ?

Nous nous attendons déjà à ce que les pages se chargent plus rapidement pour les visiteurs de retour que pour les nouveaux visiteurs, car les navigateurs peuvent mettre en cache les requêtes et les diffuser instantanément lors des visites répétées.

Les services workers proposent d'autres fonctionnalités de mise en cache qui permettent aux développeurs de contrôler précisément ce qui est mis en cache et comment. Dans IOWA, nous avons optimisé l'implémentation de notre service worker afin que chaque composant soit mis en cache, ce qui permet aux visiteurs réguliers d'utiliser l'application entièrement hors connexion.

Mais cet effort serait-il meilleur que ce que le navigateur fait déjà par défaut ? Si oui, dans quelle mesure ? 1

2. Quel est l'impact d'un service worker sur l'expérience de chargement du site ?

En d'autres termes, à quelle vitesse vous semble-t-il que le site se charge, quel que soit le temps de chargement réel mesuré par les métriques de chargement de page traditionnelles ?

Répondre à des questions sur le ressenti d'une expérience n'est évidemment pas une tâche facile, et aucune métrique ne représentera parfaitement un sentiment aussi subjectif. Cela dit, certaines métriques sont meilleures que d'autres. Il est donc important de choisir les bonnes.

Choisir la bonne métrique

Par défaut, Google Analytics suit le temps de chargement des pages (via l'API Navigation Timing) pour 1% des visiteurs d'un site et rend ces données disponibles grâce à des métriques telles que le temps de chargement moyen de la page.

La métrique Temps de chargement moyen de la page est utile pour répondre à la première question, mais pas particulièrement pour répondre à la seconde. D'une part, l'événement load ne correspond pas nécessairement au moment où l'utilisateur peut réellement interagir avec l'application. De plus, deux applications ayant exactement le même temps de chargement peuvent donner l'impression qu'elles se chargent de manière très différente. Par exemple, un site doté d'un écran de démarrage ou d'un indicateur de chargement a l'impression de se charger beaucoup plus rapidement qu'un site qui n'affiche qu'une page vierge pendant plusieurs secondes.

Dans IOWA, nous avons affiché une animation de compte à rebours sur l'écran de démarrage qui, à mon avis, a très bien servi à divertir l'utilisateur pendant que le reste de l'application se chargeait en arrière-plan. Par conséquent, il est beaucoup plus logique de suivre le temps d'affichage de l'écran de démarrage pour mesurer les performances de chargement perçues. Nous avons choisi la métrique Temps d'affichage de la première peinture pour obtenir cette valeur.

Une fois que nous avons décidé des questions auxquelles nous voulions répondre et identifié les métriques qui nous y aideraient, il était temps d'implémenter Google Analytics et de commencer à mesurer.

Implémentation des données analytiques

Si vous avez déjà utilisé Google Analytics, vous connaissez probablement l'extrait de code de suivi JavaScript recommandé. Elle se présente comme suit :

<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>

La première ligne du code ci-dessus initialise une fonction ga() globale (si elle n'existe pas déjà), et la dernière ligne télécharge de manière asynchrone la bibliothèque analytics.js.

La partie centrale contient les deux lignes suivantes:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

Ces deux commandes permettent de suivre les pages consultées par les utilisateurs qui accèdent à votre site, mais pas beaucoup plus. Si vous souhaitez suivre d'autres interactions utilisateur, vous devez le faire vous-même.

Pour IOWA, nous souhaitions suivre deux éléments supplémentaires:

  • Temps écoulé entre le début du chargement de la page et l'affichage des pixels à l'écran.
  • Indique si un service worker contrôle la page. Grâce à ces informations, nous pouvons segmenter nos rapports pour comparer les résultats avec et sans service worker.

Capturer le délai avant la première peinture

Certains navigateurs enregistrent l'heure exacte à laquelle le premier pixel est peint à l'écran et mettent cette heure à la disposition des développeurs. Cette valeur, comparée à la valeur navigationStart exposée via l'API Navigation Timing, nous donne une estimation très précise du temps écoulé entre le moment où l'utilisateur a initialement demandé la page et celui où il a vu quelque chose pour la première fois.

Comme je l'ai déjà mentionné, le temps de première peinture est une métrique importante à mesurer, car il s'agit du premier point où un utilisateur ressent la vitesse de chargement de votre site. C'est la première impression que les utilisateurs ont de votre application. Une bonne première impression peut avoir un impact positif sur le reste de l'expérience utilisateur.2

Pour obtenir la première valeur de peinture dans les navigateurs qui l'exposent, nous avons créé la fonction utilitaire getTimeToFirstPaintIfSupported :

function getTimeToFirstPaintIfSupported() {
  // Ignores browsers that don't support the Performance Timing API.
  if (window.performance && window.performance.timing) {
    var navTiming = window.performance.timing;
    var navStart = navTiming.navigationStart;
    var fpTime;

    // If chrome, get first paint time from `chrome.loadTimes`.
    if (window.chrome && window.chrome.loadTimes) {
      fpTime = window.chrome.loadTimes().firstPaintTime * 1000;
    }
    // If IE/Edge, use the prefixed `msFirstPaint` property.
    // See http://msdn.microsoft.com/ff974719
    else if (navTiming.msFirstPaint) {
      fpTime = navTiming.msFirstPaint;
    }

    if (fpTime && navStart) {
      return fpTime - navStart;
    }
  }
}

Nous pouvons maintenant écrire une autre fonction qui envoie un événement de non-interaction avec le temps de première peinture comme valeur :3

function sendTimeToFirstPaint() {
  var timeToFirstPaint = getTimeToFirstPaintIfSupported();

  if (timeToFirstPaint) {
    ga('send', 'event', {
      eventCategory: 'Performance',
      eventAction: 'firstpaint',
      // Rounds to the nearest millisecond since
      // event values in Google Analytics must be integers.
      eventValue: Math.round(timeToFirstPaint)
      // Sends this as a non-interaction event,
      // so it doesn't affect bounce rate.
      nonInteraction: true
    });
  }
}

Après avoir écrit ces deux fonctions, notre code de suivi se présente comme suit :

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sends a pageview for the initial pageload.
ga('send', 'pageview');

// Sends an event with the time to first paint data.
sendTimeToFirstPaint();

Notez que, selon le moment où le code ci-dessus s'exécute, les pixels peuvent déjà avoir été peints à l'écran ou non. Pour nous assurer que nous exécutons toujours ce code après la première peinture, nous avons reporté l'appel à sendTimeToFirstPaint() jusqu'après l'événement load. En fait, nous avons décidé de différer l'envoi de toutes les données analytiques jusqu'à ce que la page soit chargée afin de nous assurer que ces requêtes ne concurrencent pas le chargement d'autres ressources.

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Postpones sending any hits until after the page has fully loaded.
// This prevents analytics requests from delaying the loading of the page.
window.addEventListener('load', function() {
  // Sends a pageview for the initial pageload.
  ga('send', 'pageview');

  // Sends an event with the time to first paint data.
  sendTimeToFirstPaint();
});

Le code ci-dessus génère un rapport firstpaint fois pour Google Analytics, mais ce n'est que la moitié du chemin. Nous devions toujours suivre l'état du service worker, sinon nous ne pourrions pas comparer les temps de première peinture d'une page contrôlée par un service worker et d'une page non contrôlée.

Déterminer l'état du service worker

Pour déterminer l'état actuel du service worker, nous avons créé une fonction utilitaire qui renvoie l'une des trois valeurs suivantes:

  • controlled : un service worker contrôle la page. Dans le cas d'IOWA, cela signifie également que tous les composants ont été mis en cache et que la page fonctionne hors connexion.
  • supported: le navigateur est compatible avec un service worker, mais celui-ci ne contrôle pas encore la page. Il s'agit de l'état attendu pour les nouveaux visiteurs.
  • non compatible: le navigateur de l'utilisateur n'est pas compatible avec le service worker.
function getServiceWorkerStatus() {
  if ('serviceWorker' in navigator) {
    return navigator.serviceWorker.controller ? 'controlled' : 'supported';
  } else {
    return 'unsupported';
  }
}

Cette fonction a obtenu l'état du service worker pour nous. L'étape suivante consistait à associer cet état aux données envoyées à Google Analytics.

Suivre des données personnalisées avec des dimensions personnalisées

Par défaut, Google Analytics vous offre de nombreuses possibilités de subdiviser votre trafic total en groupes en fonction des attributs de l'utilisateur, de la session ou de l'interaction. Ces attributs sont appelés dimensions. Les développeurs Web s'intéressent généralement aux dimensions telles que Navigateur, Système d'exploitation ou Catégorie d'appareils.

L'état du service worker n'est pas une dimension fournie par défaut par Google Analytics. Toutefois, Google Analytics vous permet de créer vos propres dimensions personnalisées et de les définir comme vous le souhaitez.

Pour IOWA, nous avons créé une dimension personnalisée appelée État du service worker et défini son champ d'application sur appel (c'est-à-dire par interaction).4 Chaque dimension personnalisée que vous créez dans Google Analytics reçoit un indice unique dans cette propriété. Dans votre code de suivi, vous pouvez faire référence à cette dimension par son indice. Par exemple, si l'index de la dimension que nous venons de créer était 1, nous pourrions modifier notre logique comme suit pour envoyer l'événement firstpaint afin d'inclure l'état du service worker:

ga('send', 'event', {
  eventCategory: 'Performance',
  eventAction: 'firstpaint',
  // Rounds to the nearest millisecond since
  // event values in Google Analytics must be integers.
  eventValue: Math.round(timeToFirstPaint)
  // Sends this as a non-interaction event,
  // so it doesn't affect bounce rate.
  nonInteraction: true,

  // Sets the current service worker status as the value of
  // `dimension1` for this event.
  dimension1: getServiceWorkerStatus()
});

Cela fonctionne, mais n'associe l'état du service worker qu'à cet événement particulier. Étant donné que l'état du service worker peut être utile à connaître pour toute interaction, il est préférable de l'inclure avec toutes les données envoyées à Google Analytics.

Pour inclure ces informations dans tous les appels (par exemple, toutes les pages vues, tous les événements, etc.), nous définissons la valeur de la dimension personnalisée sur l'objet traceur lui-même, avant d'envoyer des données à Google Analytics.

ga('set', 'dimension1', getServiceWorkerStatus());

Une fois définie, cette valeur est envoyée avec tous les appels suivants pour le chargement de page en cours. Si l'utilisateur recharge la page plus tard, une nouvelle valeur sera probablement renvoyée par la fonction getServiceWorkerStatus() et cette valeur sera définie sur l'objet du traceur.

Remarque rapide sur la clarté et la lisibilité du code: étant donné que les autres personnes qui consultent ce code ne savent peut-être pas à quoi renvoie dimension1, il est toujours préférable de créer une variable qui associe des noms de dimensions pertinents aux valeurs qu'analytics.js utilisera.

// Creates a map between custom dimension names and their index.
// This is particularly useful if you define lots of custom dimensions.
var customDimensions = {
  SERVICE_WORKER_STATUS: 'dimension1'
};

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sets the service worker status on the tracker,
// so its value is included in all future hits.
ga('set', customDimensions.SERVICE_WORKER_STATUS, getServiceWorkerStatus());

// Postpones sending any hits until after the page has fully loaded.
// This prevents analytics requests from delaying the loading of the page.
window.addEventListener('load', function() {
  // Sends a pageview for the initial pageload.
  ga('send', 'pageview');

  // Sends an event with the time to first paint data.
  sendTimeToFirstPaint();
});

Comme indiqué précédemment, envoyer la dimension État du service worker avec chaque appel nous permet de l'utiliser pour générer des rapports sur n'importe quelle métrique.

Comme vous pouvez le constater, près de 85% de toutes les pages vues pour l'Iowa provenaient de navigateurs compatibles avec les service workers.

Résultats: répondre à nos questions

Une fois que nous avons commencé à collecter des données pour répondre à nos questions, nous avons pu les analyser pour voir les résultats. (Remarque: toutes les données Google Analytics présentées ici représentent le trafic Web réel vers le site IOWA du 16 au 22 mai 2016.)

La première question que nous nous sommes posée était la suivante: Le cache du service worker est-il plus performant que les mécanismes de mise en cache HTTP existants disponibles dans tous les navigateurs ?

Pour répondre à cette question, nous avons créé un rapport personnalisé qui analysait la métrique Temps de chargement moyen des pages pour différentes dimensions. Cette métrique est bien adaptée pour répondre à cette question, car l'événement load ne se déclenche qu'après le téléchargement de toutes les ressources initiales. Il reflète donc directement le temps de chargement total de toutes les ressources critiques du site.5

Les dimensions que nous avons choisies étaient les suivantes:

  • Notre dimension personnalisée État du service worker.
  • Type d'utilisateur, qui indique s'il s'agit de la première visite de l'utilisateur sur le site ou s'il est un utilisateur connu. (Remarque: un nouveau visiteur n'aura aucune ressource mise en cache, contrairement à un visiteur régulier.)
  • Catégorie d'appareil, qui permet de comparer les résultats sur mobile et sur ordinateur.

Pour vérifier que des facteurs non liés aux service workers ne faussaient pas nos résultats sur le temps de chargement, nous avons limité notre requête à inclure uniquement les navigateurs compatibles avec les service workers.

Comme vous pouvez le constater, les visites de notre application contrôlées par un service worker se chargent beaucoup plus rapidement que les visites non contrôlées, même celles des utilisateurs qui reviennent, qui ont probablement mis en cache la plupart des ressources de la page. Il est également intéressant de noter qu'en moyenne, les visiteurs sur mobile avec un service worker ont vu des chargements plus rapides que les nouveaux visiteurs sur ordinateur.

"…les visites de notre application lorsqu'elles sont contrôlées par un service worker se chargent beaucoup plus rapidement que les visites non contrôlées…"

Pour en savoir plus, consultez les deux tableaux suivants:

Temps de chargement moyen de la page (ordinateur)
État du service worker Type d'utilisateur Temps de chargement moyen de la page (ms) Taille de l'échantillon
A contrôlé Visiteur connu 2568 30860
Compatible Visiteur connu 3612 1289
Compatible Nouveau visiteur 4664 21991
Temps de chargement moyen de la page (mobile)
État du service worker Type d'utilisateur Temps de chargement moyen de la page (ms) Taille de l'échantillon
A contrôlé Visiteur connu 3760 8162
Compatible Visiteur connu 4843 676
Compatible Nouveau visiteur 6158 5779

Vous vous demandez peut-être comment un visiteur qui revient et dont le navigateur est compatible avec le service worker peut se trouver dans un état non contrôlé. Plusieurs explications sont possibles:

  • L'utilisateur a quitté la page lors de la première visite avant que le service worker n'ait eu le temps de terminer son initialisation.
  • L'utilisateur a désinstallé le service worker via les outils pour les développeurs.

Ces deux situations sont relativement rares. Nous pouvons le constater dans les données en examinant les valeurs Exemple de chargement de page dans la quatrième colonne. Notez que les lignes du milieu comportent un échantillon beaucoup plus petit que les deux autres.

Notre deuxième question était la suivante: Quel est l'impact d'un service worker sur l'expérience de chargement du site ?

Pour répondre à cette question, nous avons créé un autre rapport personnalisé pour la métrique Valeur moyenne de l'événement et filtré les résultats pour n'inclure que nos événements firstpaint. Nous avons utilisé les dimensions Catégorie d'appareil et État du service worker personnalisée.

Contrairement à ce que j'aurais pu imaginer, le service worker sur mobile a eu beaucoup moins d'impact sur le délai de première peinture que sur le chargement global de la page.

"... le service worker sur mobile a eu beaucoup moins d'impact sur le délai de chargement initial de la page que sur le chargement global de la page."

Pour comprendre pourquoi c'est le cas, nous devons analyser les données plus en détail. Les moyennes peuvent être utiles pour obtenir une vue d'ensemble, mais pour avoir une idée précise de la répartition de ces chiffres sur un éventail d'utilisateurs, nous devons examiner une distribution sur firstpaint fois.

Obtenir la distribution d'une métrique dans Google Analytics

Pour obtenir la répartition des firstpaint fois, nous avons besoin d'accéder aux résultats individuels de chaque événement. Malheureusement, Google Analytics ne facilite pas cette tâche.

Google Analytics vous permet de répartir un rapport selon la dimension de votre choix, mais pas selon les métriques. Cela ne veut pas dire que c'est impossible, mais cela signifie simplement que nous avons dû personnaliser un peu plus notre implémentation pour obtenir le résultat souhaité.

Étant donné que les résultats des rapports ne peuvent être ventilés que par dimensions, nous avons dû définir la valeur de la métrique (dans ce cas, l'heure firstpaint) en tant que dimension personnalisée sur l'événement. Pour ce faire, nous avons créé une autre dimension personnalisée appelée Valeur de la métrique et mis à jour notre logique de suivi firstpaint comme suit:

var customDimensions = {
  SERVICE_WORKER_STATUS: 'dimension1',
  <strong>METRIC_VALUE: 'dimension2'</strong>
};

// ...

function sendTimeToFirstPaint() {
  var timeToFirstPaint = getTimeToFirstPaintIfSupported();

  if (timeToFirstPaint) {
    var fields = {
      eventCategory: 'Performance',
      eventAction: 'firstpaint',
      // Rounds to the nearest millisecond since
      // event values in Google Analytics must be integers.
      eventValue: Math.round(timeToFirstPaint)
      // Sends this as a non-interaction event,
      // so it doesn't affect bounce rate.
      nonInteraction: true
    }

    <strong>// Sets the event value as a dimension to allow for breaking down the
    // results by individual metric values at reporting time.
    fields[customDimensions.METRIC_VALUE] = String(fields.eventValue);</strong>

    ga('send', 'event', fields);
  }
}

Pour le moment, l'interface Web de Google Analytics ne permet pas de visualiser la distribution de valeurs de métriques arbitraires. Toutefois, grâce à l'API Core Reporting de Google Analytics et à la bibliothèque Google Charts, nous pouvons interroger les résultats bruts et créer nous-mêmes un histogramme.

Par exemple, la configuration de requête d'API suivante a été utilisée pour obtenir une distribution des valeurs firstpaint sur ordinateur avec un service worker non contrôlé.

{
  dateRanges: [{startDate: '2016-05-16', endDate: '2016-05-22'}],
  metrics: [{expression: 'ga:totalEvents'}],
  dimensions: [{name: 'ga:dimension2'}],
  dimensionFilterClauses: [
    {
      operator: 'AND',
      filters: [
        {
          dimensionName: 'ga:eventAction',
          operator: 'EXACT',
          expressions: ['firstpaint']
        },
        {
          dimensionName: 'ga:dimension1',
          operator: 'EXACT',
          expressions: ['supported']
        },
        {
          dimensionName: 'ga:deviceCategory',
          operator: 'EXACT',
          expressions: ['desktop']
        }
      ],
    }
  ],
  orderBys: [
    {
      fieldName: 'ga:dimension2',
      orderType: 'DIMENSION_AS_INTEGER'
    }
  ]
}

Cette requête API renvoie un tableau de valeurs qui se présente comme suit (remarque: il ne s'agit que des cinq premiers résultats). Les résultats sont triés de la plus petite à la plus grande. Ces lignes représentent donc les temps les plus rapides.

Résultats de la réponse de l'API (cinq premières lignes)
ga:dimension2 ga:totalEvents
4 3
5 2
6 10
7 8
8 10

Voici ce que ces résultats signifient en langage clair:

  • Dans 3 événements, la valeur de firstpaint était de 4 ms
  • Deux événements pour lesquels la valeur firstpaint était de 5 ms
  • 10 événements pour lesquels la valeur firstpaint était de 6 ms
  • Huit événements ont eu lieu pour lesquels la valeur firstpaint était de 7 ms.
  • Il y a eu 10 événements où firstpaint value était de 8 ms
  • etc.

À partir de ces résultats, nous pouvons extrapoler la valeur firstpaint pour chaque événement et créer un histogramme de la distribution. Nous avons procédé ainsi pour chacune des requêtes que nous avons exécutées.

Voici à quoi ressemblait la distribution sur ordinateur avec un service worker non contrôlé (mais compatible) :

Distribution du temps de First Paint sur ordinateur (compatible)

La durée firstpaint médiane pour la distribution ci-dessus est de 912 ms.

La forme de cette courbe est assez typique des distributions de temps de chargement. Comparez-le à l'histogramme ci-dessous, qui montre la distribution des événements de première peinture pour les visites au cours desquelles un service worker contrôlait la page.

Distribution du temps de First Paint sur ordinateur (contrôlée)

Notez que lorsque le contrôle de la page était assuré par un service worker, de nombreux visiteurs ont vu leur première peinture presque immédiate, avec une valeur médiane de 583 ms.

"...lorsqu'un service worker contrôlait la page, de nombreux visiteurs ont subi un premier repeinture quasi immédiate..."

Pour mieux comprendre la comparaison de ces deux distributions, le graphique suivant présente une vue fusionnée des deux. L'histogramme des visites de service workers non contrôlés est superposé à l'histogramme des visites contrôlées. Les deux sont superposés sur un histogramme combiné.

Distribution du temps de First Paint sur ordinateur

J'ai trouvé intéressant que la distribution avec un service worker contrôlé présente toujours une courbe en forme de cloche après le pic initial. Je m'attendais à un pic initial important, suivi d'une baisse progressive, mais je ne m'attendais pas à un deuxième pic dans la courbe.

En cherchant la cause de ce problème, j'ai découvert que même si un service worker contrôle une page, son thread peut être inactif. Le navigateur fait cela pour économiser des ressources. Il est évident que vous n'avez pas besoin que tous les services workers de tous les sites que vous avez déjà visités soient actifs et prêts à tout moment. Cela explique la queue de la distribution. Pour certains utilisateurs, un délai s'écoulait lors du démarrage du thread du service worker.

Toutefois, comme le montre la distribution, les navigateurs avec service worker livrent le contenu plus rapidement que les navigateurs passant par le réseau.

Voici à quoi cela ressemblait sur mobile:

Distribution du temps de First Paint sur mobile

Bien que nous ayons constaté une augmentation importante des temps de première peinture quasi immédiats, la queue était beaucoup plus grande et plus longue. Cela est probablement dû au fait que, sur mobile, le démarrage d'un thread de service worker inactif prend plus de temps que sur ordinateur. Cela explique également pourquoi la différence entre la durée moyenne de firstpaint n'était pas aussi importante que je l'attendais (voir ci-dessus).

"...sur mobile, le démarrage d'un thread de service worker inactif prend plus de temps que sur ordinateur."

Voici la répartition de ces variations de temps médians de première peinture sur mobile et ordinateur, regroupée par statut de prestataire de service:

Temps médian avant le premier rendu (ms)
État du service worker Ordinateur Mobile
A contrôlé 583 1634
Compatible (non contrôlé) 912 1933

Bien que la création de ces visualisations de distribution ait pris un peu plus de temps et d'efforts que la création d'un rapport personnalisé dans Google Analytics, elles nous donnent une bien meilleure idée de l'impact des services workers sur les performances de notre site que les moyennes seules.

Autres conséquences des service workers

En plus de l'impact sur les performances, les services workers ont également un impact sur l'expérience utilisateur de plusieurs autres manières mesurables avec Google Analytics.

Accès hors connexion

Les service workers permettent aux utilisateurs d'interagir avec votre site hors connexion. Bien qu'une certaine compatibilité hors connexion soit probablement essentielle pour toute application Web progressive, déterminer son importance dans votre cas dépend en grande partie de l'utilisation hors connexion. Mais comment mesurer cela ?

L'envoi de données à Google Analytics nécessite une connexion Internet, mais il n'est pas nécessaire que les données soient transmises au moment exact de l'interaction. Google Analytics permet d'envoyer des données d'interaction a posteriori en spécifiant un décalage temporel (via le paramètre qt).

Depuis deux ans, l'IOWA utilise un script de service worker qui détecte les appels ayant échoué vers Google Analytics lorsque l'utilisateur est hors connexion et les relance plus tard avec le paramètre qt.

Pour savoir si l'utilisateur était en ligne ou hors connexion, nous avons créé une dimension personnalisée appelée En ligne et l'avons définie sur la valeur navigator.onLine. Nous avons ensuite écouté les événements online et offline, puis mis à jour la dimension en conséquence.

Pour avoir une idée de la fréquence à laquelle les utilisateurs étaient hors connexion lorsqu'ils utilisaient IOWA, nous avons créé un segment qui ciblait les utilisateurs ayant au moins une interaction hors connexion. Il s'a avéré que cela concernait près de 5% des utilisateurs.

Notifications push

Les service workers permettent aux utilisateurs d'activer la réception de notifications push. Dans IOWA, les utilisateurs étaient avertis lorsqu'une session de leur planning était sur le point de commencer.

Comme pour toute forme de notification, il est important de trouver le juste équilibre entre apporter de la valeur à l'utilisateur et les gêner. Pour mieux comprendre ce qui se passe, il est important de savoir si les utilisateurs acceptent de recevoir ces notifications, s'ils interagissent avec elles lorsqu'elles arrivent et si certains utilisateurs qui les avaient précédemment acceptées changent de préférence et les désactivent.

Dans IOWA, nous n'envoyions que des notifications liées à l'horaire personnalisé de l'utilisateur, que seuls les utilisateurs connectés pouvaient créer. Cela limitait l'ensemble des utilisateurs pouvant recevoir des notifications aux utilisateurs connectés (suivi via une dimension personnalisée appelée Connecté) dont les navigateurs étaient compatibles avec les notifications push (suivi via une autre dimension personnalisée appelée Autorisation de notification).

Le rapport suivant est basé sur la métrique Utilisateurs et sur notre dimension personnalisée "Autorisation de notification", segmentée par les utilisateurs qui se sont connectés à un moment donné et dont les navigateurs sont compatibles avec les notifications push.

Nous sommes ravis de constater que plus de la moitié de nos utilisateurs connectés ont choisi de recevoir des notifications push.

Bannières incitant à installer une application

Si une progressive web app répond aux critères et est utilisée fréquemment par un utilisateur, une bannière d'installation de l'application peut s'afficher pour l'inviter à ajouter l'application à son écran d'accueil.

Dans l'Iowa, nous avons suivi la fréquence à laquelle ces requêtes étaient présentées à l'utilisateur (et si elles étaient acceptées) à l'aide du code suivant:

window.addEventListener('beforeinstallprompt', function(event) {
  // Tracks that the user saw a prompt.
  ga('send', 'event', {
    eventCategory: 'installprompt',
    eventAction: 'fired'
  });

  event.userChoice.then(function(choiceResult) {
    // Tracks the users choice.
    ga('send', 'event', {
      eventCategory: 'installprompt',
      // `choiceResult.outcome` will be 'accepted' or 'dismissed'.
      eventAction: choiceResult.outcome,
      // `choiceResult.platform` will be 'web' or 'android' if the prompt was
      // accepted, or '' if the prompt was dismissed.
      eventLabel: choiceResult.platform
    });
  });
});

Parmi les utilisateurs qui ont vu une bannière d'installation d'application, environ 10% ont choisi de l'ajouter à leur écran d'accueil.

Améliorations possibles du suivi (pour la prochaine fois)

Les données analytiques que nous avons collectées auprès d'Iowa cette année ont été inestimables. Mais avec le recul, il y a toujours des failles et des opportunités d'amélioration. Après avoir terminé l'analyse de cette année, voici deux choses que nous aurions aimé faire différemment et que les lecteurs qui souhaitent mettre en œuvre une stratégie similaire pourraient envisager:

1. Suivre plus d'événements liés à l'expérience de chargement

Nous avons suivi plusieurs événements qui correspondent à une métrique technique (par exemple, HTMLImportsLoaded, WebComponentsReady, etc.), mais comme une grande partie de la charge était effectuée de manière asynchrone, le moment où ces événements se sont déclenchés ne correspondait pas nécessairement à un moment particulier de l'expérience de chargement globale.

L'événement principal lié à la charge que nous n'avons pas suivi (mais que nous aurions aimé) est le moment où l'écran de démarrage a disparu et que l'utilisateur a pu voir le contenu de la page.

2. Stocker l'ID client d'analyse dans IndexedDB

Par défaut, analytics.js stocke le champ d'ID client dans les cookies du navigateur. Malheureusement, les scripts de service worker ne peuvent pas accéder aux cookies.

Cela nous a posé problème lorsque nous avons essayé d'implémenter le suivi des notifications. Nous voulions envoyer un événement à partir du service worker (via le protocole de mesure) chaque fois qu'une notification était envoyée à un utilisateur, puis suivre le succès du réengagement de cette notification si l'utilisateur a cliqué dessus et est revenu dans l'application.

Bien que nous ayons pu suivre le succès des notifications en général via le paramètre de campagne utm_source, nous n'avons pas pu associer une session de réengagement spécifique à un utilisateur spécifique.

Pour contourner cette limitation, nous aurions pu stocker l'ID client via IndexedDB dans notre code de suivi. Cette valeur aurait alors été accessible au script du service worker.

3. Autorisez le service worker à indiquer l'état en ligne/hors connexion

L'inspection de navigator.onLine vous indique si votre navigateur peut se connecter au routeur ou au réseau local, mais ne vous indique pas nécessairement si l'utilisateur dispose d'une connectivité réelle. Et comme notre script de service de worker d'analyse hors connexion rejouait simplement les requêtes ayant échoué (sans les modifier ni les marquer comme ayant échoué), nous sous-estimions probablement notre utilisation hors connexion.

À l'avenir, nous devrions suivre à la fois l'état de navigator.onLine et si l'appel a été rejoué par le service worker en raison d'un échec réseau initial. Cela nous donnera une idée plus précise de l'utilisation réelle hors connexion.

Conclusion

Cette étude de cas montre que l'utilisation d'un service worker améliore les performances de chargement de l'application Web Google I/O sur un large éventail de navigateurs, de réseaux et d'appareils. Il a également démontré que lorsque vous examinez la distribution des données de charge sur un large éventail de navigateurs, de réseaux et d'appareils, vous obtenez beaucoup plus d'informations sur la façon dont cette technologie gère les scénarios réels et vous découvrez des caractéristiques de performances auxquelles vous ne vous attendiez peut-être pas.

Voici quelques-uns des principaux points à retenir de l'étude de l'IOWA:

  • En moyenne, les pages se chargeaient beaucoup plus rapidement lorsqu'un service worker contrôlait la page qu'en l'absence d'un service worker, tant pour les nouveaux visiteurs que pour les visiteurs connus.
  • Les visites des pages contrôlées par un service worker se chargeaient presque instantanément pour de nombreux utilisateurs.
  • Lorsqu'ils étaient inactifs, les services workers mettaient un certain temps à démarrer. Toutefois, un service worker inactif est toujours plus performant qu'un service worker inexistant.
  • Le temps de démarrage d'un service worker inactif était plus long sur mobile que sur ordinateur.

Bien que les gains de performances observés dans une application particulière soient généralement utiles à la communauté des développeurs dans son ensemble, il est important de garder à l'esprit que ces résultats sont spécifiques au type de site IOWA (site événementiel) et au type d'audience de l'IOWA (principalement les développeurs).

Si vous implémentez un service worker dans votre application, il est important d'implémenter votre propre stratégie de mesure afin de pouvoir évaluer vos propres performances et éviter toute régression future. Si c'est le cas, veuillez partager vos résultats pour que tout le monde puisse en bénéficier !

Notes de bas de page

  1. Il n'est pas tout à fait juste de comparer les performances de l'implémentation du cache du service worker aux performances de notre site avec le cache HTTP seul. Dans l'optique d'optimiser l'IOWA pour les service workers, nous n'avons pas passé beaucoup de temps à optimiser le cache HTTP. Si nous l'avions fait, les résultats auraient probablement été différents. Pour en savoir plus sur l'optimisation de votre site pour le cache HTTP, consultez Optimiser efficacement le contenu.
  2. Selon la façon dont votre site charge ses styles et son contenu, il est possible que le navigateur puisse peindre avant que le contenu ou les styles ne soient disponibles. Dans ce cas, firstpaint peut correspondre à un écran blanc vide. Si vous utilisez firstpaint, il est important de vous assurer qu'il correspond à un point significatif du chargement des ressources de votre site.
  3. Techniquement, nous pourrions envoyer un hit de synchronisation (qui ne sont pas des interactions par défaut) pour capturer ces informations au lieu d'un événement. En fait, les appels de date et heure ont été ajoutés à Google Analytics spécifiquement pour suivre les métriques de chargement de ce type. Toutefois, ils sont fortement échantillonnés au moment du traitement, et leurs valeurs ne peuvent pas être utilisées dans les segments. Compte tenu de ces limitations actuelles, les événements indépendants de toute interaction restent plus adaptés.
  4. Pour mieux comprendre le champ d'application à attribuer à une dimension personnalisée dans Google Analytics, consultez la section Dimension personnalisée du centre d'aide Analytics. Il est également important de comprendre le modèle de données Google Analytics, qui comprend les utilisateurs, les sessions et les interactions (appels). Pour en savoir plus, regardez la lecon de l'Académie Analytics sur le modèle de données Google Analytics.
  5. Cela ne tient pas compte des ressources chargées de manière paresseuse après l'événement de chargement.