Medición del impacto en el rendimiento real de los trabajadores de servicios

Uno de los beneficios más importantes de los service worker (al menos desde el punto de vista del rendimiento) es su capacidad para controlar proactivamente el almacenamiento en caché de los recursos. Una aplicación web que pueda almacenar en caché todos los recursos necesarios debería cargarse mucho más rápido para los visitantes recurrentes. Pero ¿cómo se ven estos beneficios en realidad para los usuarios reales? ¿Y cómo lo mides?

La app web de Google I/O (IOWA) es una app web progresiva que aprovechó la mayoría de las capacidades nuevas que ofrecen los service worker para ofrecer a sus usuarios una experiencia enriquecida de tipo app. También utilizó Google Analytics para recopilar datos clave de rendimiento y patrones de uso de su amplio y diverso público de usuarios.

Este caso de éxito explora cómo IOWA utilizó Google Analytics para responder a preguntas clave sobre el rendimiento y generar informes sobre el impacto en el mundo real de los service workers.

Comienza con las preguntas

Cada vez que implementes estadísticas en un sitio web o una aplicación, es importante comenzar por identificar las preguntas que intentas responder a partir de los datos que recopilarás.

Si bien teníamos varias preguntas que queríamos responder, a los efectos de este caso práctico, enfoquémonos en dos de las más interesantes.

1. ¿El almacenamiento en caché del service worker tiene mejor rendimiento que los mecanismos de almacenamiento en caché HTTP existentes que están disponibles en todos los navegadores?

Ya esperamos que las páginas se carguen más rápido para los visitantes recurrentes que para los visitantes nuevos, ya que los navegadores pueden almacenar las solicitudes en caché y entregarlas al instante en las visitas repetidas.

Los service workers ofrecen capacidades alternativas de almacenamiento en caché que les brindan a los desarrolladores un control detallado sobre qué es exactamente y cómo se realiza el almacenamiento en caché. En IOWA, optimizamos la implementación de nuestro service worker para que todos los recursos se almacenaran en caché, de modo que los visitantes recurrentes pudieran usar la app completamente sin conexión.

Pero ¿sería este esfuerzo mejor que lo que el navegador ya hace de forma predeterminada? Si es así, ¿cuánto mejor? 1

2. ¿Cómo afecta el service worker a la experiencia de carga del sitio?

En otras palabras, ¿qué tan rápido siente que el sitio se carga, independientemente de los tiempos de carga reales, como los miden las métricas tradicionales de carga de la página?

Responder preguntas sobre cómo se siente una experiencia no es una tarea fácil, y ninguna métrica representará a la perfección una opinión tan subjetiva. Dicho esto, algunas métricas son mejores que otras, por lo que es importante elegir las adecuadas.

Elige la métrica adecuada

De forma predeterminada, Google Analytics hace un seguimiento de los tiempos de carga de las páginas (mediante la API de Navigation Timing) del 1% de los visitantes de un sitio y pone esos datos a disposición mediante métricas como el tiempo promedio de carga de la página.

El tiempo de carga promedio de la página es una buena métrica para responder la primera pregunta, pero no es una métrica particularmente buena para responder la segunda pregunta. Por ejemplo, el evento load no necesariamente corresponde al momento en el que el usuario puede interactuar con la app. Además, dos apps con exactamente el mismo tiempo de carga pueden sentirse que se cargan de forma muy diferente. Por ejemplo, es probable que un sitio con una pantalla de presentación o un indicador de carga parezca mucho más rápido que uno que solo muestra una página en blanco durante varios segundos.

En IOWA, mostramos una animación de cuenta regresiva de la pantalla de presentación que, en mi opinión, sirvió para entretener al usuario mientras el resto de la aplicación se cargaba en segundo plano. Por este motivo, rastrear cuánto tarda en aparecer la pantalla de presentación tiene mucho más sentido como una manera de medir el rendimiento de carga percibido. Elegimos la métrica tiempo hasta la primera pintura para obtener este valor.

Una vez que decidimos cuáles son las preguntas que queríamos responder e identificamos las métricas que serían útiles para responderlas, llegó el momento de implementar Google Analytics y empezar a realizar mediciones.

La implementación de estadísticas

Si ya utilizaste Google Analytics anteriormente, es probable que conozcas el fragmento de seguimiento de JavaScript recomendado. El aspecto resultante será el siguiente:

<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 primera línea del código anterior inicializa una función ga() global (si aún no existe) y la última línea descarga la biblioteca analytics.js de forma asíncrona.

La parte central contiene estas dos líneas:

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

Estos dos comandos hacen un seguimiento de las páginas que visitan las personas que visitan tu sitio, pero no mucho más. Si desea realizar un seguimiento de las interacciones adicionales del usuario, debe hacerlo usted mismo.

Para IOWA, queríamos hacer un seguimiento de dos aspectos adicionales:

  • Es el tiempo que transcurre desde que la página comienza a cargarse por primera vez hasta que aparecen los píxeles en la pantalla.
  • Indica si un service worker controla la página o no. Con esta información, podríamos segmentar nuestros informes para comparar los resultados con y sin service worker.

Captura el tiempo para el primer procesamiento de imagen

Algunos navegadores registran el tiempo exacto en el que se pinta el primer píxel en la pantalla y lo ponen a disposición de los desarrolladores. Ese valor, en comparación con el valor de navigationStart expuesto a través de la API de Navigation Timing, nos brinda un cálculo muy preciso del tiempo que transcurrió desde que el usuario solicitó la página inicialmente hasta que vio algo por primera vez.

Como ya mencioné, el tiempo hasta la primera pintura es una métrica importante para medir porque es el primer punto en el que el usuario experimenta la velocidad de carga de tu sitio. Es la primera impresión que reciben los usuarios, y una buena primera impresión puede afectar positivamente al resto de la experiencia del usuario.2

Para obtener el primer valor de pintura en navegadores que lo exponen, creamos la función de utilidad 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;
    }
  }
}

Con esto, ahora podríamos escribir otra función que envíe un evento sin interacción con el tiempo para la primera pintura como su valor: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
    });
  }
}

Después de escribir estas dos funciones, nuestro código de seguimiento se verá de la siguiente manera:

// 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();

Ten en cuenta que, según el momento en que se ejecute el código anterior, es posible que los píxeles se hayan pintado en la pantalla o no. Para garantizar que siempre ejecutemos este código después de que se produzca el primer procesamiento de imagen, pospusimos la llamada a sendTimeToFirstPaint() hasta después del evento load. De hecho, decidimos posponer el envío de todos los datos de estadísticas hasta después de que se haya cargado la página para garantizar que esas solicitudes no compitan con la carga de otros recursos.

// 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();
});

El código anterior se informa firstpaint veces a Google Analytics, pero eso es solo la mitad de la historia. Todavía necesitábamos hacer un seguimiento del estado de los service worker. De lo contrario, no podríamos comparar los tiempos de procesamiento de imagen de una página controlada por service worker y de una no controlada.

Determina el estado del service worker

Para determinar el estado actual del service worker, creamos una función de utilidad que devuelve uno de tres valores:

  • controlado: un service worker controla la página. En el caso de IOWA, eso también significa que todos los elementos se almacenaron en caché y que la página funciona sin conexión.
  • supported: El navegador admite el service worker, pero este aún no controla la página. Este es el estado esperado para los visitantes nuevos.
  • no admitido: El navegador del usuario no es compatible con el service worker.
function getServiceWorkerStatus() {
  if ('serviceWorker' in navigator) {
    return navigator.serviceWorker.controller ? 'controlled' : 'supported';
  } else {
    return 'unsupported';
  }
}

Esta función nos obtuvo el estado del service worker. El siguiente paso fue asociar este estado con los datos que estábamos enviando a Google Analytics.

Seguimiento de datos personalizados con dimensiones personalizadas

De forma predeterminada, Google Analytics te ofrece muchas formas de subdividir tu tráfico total en grupos según los atributos del usuario, la sesión o la interacción. Estos atributos se conocen como dimensiones. Las dimensiones comunes que les interesan a los desarrolladores web son el navegador, el sistema operativo o la categoría del dispositivo.

El estado del service worker no es una dimensión que Google Analytics proporciona de forma predeterminada. Sin embargo, Google Analytics te da la posibilidad de crear tus propias dimensiones personalizadas y definirlas como desees.

En el caso de IOWA, creamos una dimensión personalizada denominada Estado del service worker y establecimos su alcance en hit (es decir, por interacción).4 A cada dimensión personalizada que se crea en Google Analytics se le asigna un índice único dentro de esa propiedad, y en tu código de seguimiento puedes hacer referencia a esa dimensión según su índice. Por ejemplo, si el índice de la dimensión que acabamos de crear fuera 1, podríamos actualizar nuestra lógica de la siguiente manera para enviar el evento firstpaint y que incluya el estado del 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()
});

Esto funciona, pero solo asociará el estado del service worker con ese evento en particular. Dado que el estado del service worker es algo que podría ser útil saber para cualquier interacción, se recomienda incluirlo en todos los datos que se envían a Google Analytics.

Para incluir esta información en todos los hits (p.ej., todas las vistas de página, eventos, etc.), establecemos el valor de la dimensión personalizada en el objeto de seguimiento antes de enviar datos a Google Analytics.

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

Una vez establecido, este valor se envía con todos los hits posteriores de la carga de página actual. Si el usuario vuelve a cargar la página más adelante, es probable que se muestre un valor nuevo de la función getServiceWorkerStatus(), que se establecerá en el objeto de seguimiento.

Nota rápida sobre la claridad y legibilidad del código: dado que es posible que otras personas que vean este código no sepan a qué se refiere dimension1, siempre es mejor crear una variable que asigne nombres de dimensión significativos a los valores que usará analytics.js.

// 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();
});

Como ya mencioné, enviar la dimensión Estado del trabajador de servicio con cada hit nos permite usarla para generar informes sobre cualquier métrica.

Como puedes ver, casi el 85% de todas las páginas vistas de IOWA provino de navegadores que admiten service worker.

Los resultados: responder nuestras preguntas

Una vez que empezamos a recopilar datos para responder a nuestras preguntas, podíamos crear informes sobre ellos para ver los resultados. (Nota: Todos los datos de Google Analytics que se muestran aquí representan el tráfico web real hacia el sitio de IOWA del 16 al 22 de mayo de 2016).

La primera pregunta que nos hicimos fue la siguiente: ¿El almacenamiento en caché del service worker tiene un mejor rendimiento que los mecanismos de almacenamiento en caché HTTP existentes que están disponibles en todos los navegadores?

Para responder esa pregunta, creamos un informe personalizado que analizaba la métrica Tiempos promedio de carga de la página en varias dimensiones. Esta métrica es adecuada para responder esta pregunta porque el evento load se activa solo después de que se descargan todos los recursos iniciales. Por lo tanto, refleja directamente el tiempo de carga total de todos los recursos críticos del sitio.5

Las dimensiones que elegimos fueron las siguientes:

  • Nuestra dimensión personalizada Estado de los trabajadores de servicios.
  • Tipo de usuario, que indica si esta es la primera visita del usuario al sitio o si este regresa. (Nota: Un visitante nuevo no tendrá recursos almacenados en la caché, pero sí podría ocurrir un visitante recurrente).
  • Categoría de dispositivo, que nos permite comparar los resultados en dispositivos móviles y computadoras de escritorio.

Para controlar la posibilidad de que factores no relacionados con service worker estuvieran sesgando nuestros resultados de tiempo de carga, limitamos nuestra consulta para incluir solo navegadores compatibles con service worker.

Como puedes ver, las visitas a nuestra app cuando las controla un service worker se cargan bastante más rápido que las visitas no controladas, incluso las de usuarios recurrentes que probablemente tenían la mayoría de los recursos de la página almacenados en caché. También es interesante observar que, en promedio, los visitantes de dispositivos móviles con un service worker observaron cargas más rápidas que los visitantes nuevos de computadoras de escritorio.

"... las visitas a nuestra aplicación cuando las controla un service worker se cargan bastante más rápido que las visitas no controladas..."

Puedes ver más detalles en las siguientes dos tablas:

Tiempo promedio de carga de la página (computadoras de escritorio)
Estado de Service Worker Tipo de usuario Tiempo promedio de carga de la página (ms) Tamaño de la muestra
Controlaste Visitante recurrente 2568 30860
Admitido Visitante recurrente 3612 1289
Admitido Visitante nuevo 4664 21991
Tiempo promedio de carga de la página (dispositivos móviles)
Estado de Service Worker Tipo de usuario Tiempo promedio de carga de la página (ms) Tamaño de la muestra
Controlaste Visitante recurrente 3760 8162
Admitido Visitante recurrente 4843 676
Admitido Visitante nuevo 6158 5779

Es posible que te preguntes cómo es posible que un visitante recurrente cuyo navegador admita un service worker esté en un estado no controlado. Existen algunas explicaciones posibles para esto:

  • El usuario abandonó la página en la visita inicial antes de que el service worker pudiera terminar de inicializarse.
  • El usuario desinstaló el service worker a través de las herramientas para desarrolladores.

Ambas situaciones son relativamente raras. Podemos verlo en los datos si observamos los valores de Muestra de carga de página en la cuarta columna. Observa que las filas del medio tienen una muestra mucho más pequeña que las otras dos.

Nuestra segunda pregunta fue: ¿Cómo afecta el service worker la experiencia de carga del sitio?

Para responder esta pregunta, creamos otro informe personalizado para la métrica Valor del evento prom. y filtramos los resultados para que solo incluyeran nuestros eventos firstpaint. Usamos las dimensiones Categoría de dispositivo y nuestra dimensión personalizada Estado de trabajador de servicio.

Contrario a lo que esperaba, el service worker en dispositivos móviles tuvo mucho menos impacto en el tiempo de creación de la primera pintura que en la carga general de la página.

"... service worker en dispositivos móviles tuvo mucho menos impacto en el tiempo de ejecución del primer procesamiento de imagen que en la carga general de la página".

Para explorar por qué este es el caso, tenemos que profundizar en los datos. Los promedios pueden ser útiles para descripciones generales generales y para trazos generales, pero para tener una idea de cómo se desglosan estos números en un rango de usuarios, es necesario observar una distribución de firstpaint veces.

Cómo obtener la distribución de una métrica en Google Analytics

Para obtener la distribución de firstpaint veces, necesitamos acceso a los resultados individuales de cada evento. Lamentablemente, Google Analytics no facilita esta tarea.

Google Analytics nos permite desglosar un informe según la dimensión que queramos, pero no nos permite desglosar un informe por métricas. Esto no quiere decir que sea imposible, solo significa que tuvimos que personalizar un poco más nuestra implementación para obtener el resultado deseado.

Dado que los resultados de los informes solo se pueden desglosar por dimensiones, tuvimos que establecer el valor de la métrica (en este caso, firstpaint vez) como una dimensión personalizada del evento. Para ello, creamos otra dimensión personalizada llamada Valor de la métrica y actualizamos nuestra lógica de seguimiento firstpaint de la siguiente manera:

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);
  }
}

Actualmente, la interfaz web de Google Analytics no proporciona una forma de visualizar la distribución de valores de métricas arbitrarios. Sin embargo, con la ayuda de la API de Google Analytics Core Reporting y la biblioteca de Gráficos de Google, podríamos consultar los resultados sin procesar y, luego, crear un histograma nosotros mismos.

Por ejemplo, se usó la siguiente configuración de solicitud a la API para obtener una distribución de los valores de firstpaint en computadoras de escritorio con un service worker no controlado.

{
  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'
    }
  ]
}

Esta solicitud a la API muestra un array de valores similar al siguiente. (Nota: Estos son solo los primeros cinco resultados). Los resultados se ordenan de menor a mayor, por lo que estas filas representan los tiempos más rápidos.

Resultados de la respuesta de la API (primeras cinco filas)
ga:dimensión2 ga:totalEvents
4 3
5 2
6 10
7 8
8 10

Esto es lo que significan estos resultados en un simple inglés:

  • Hubo 3 eventos en los que el valor de firstpaint era de 4 ms
  • Hubo 2 eventos en los que el valor de firstpaint era de 5 ms
  • Hubo 10 eventos en los que el valor de firstpaint era de 6 ms
  • Hubo 8 eventos en los que el valor de firstpaint era de 7 ms
  • Hubo 10 eventos en los que el valor de value firstpaint fue de 8 ms
  • etcétera

A partir de estos resultados, podemos extrapolar el valor firstpaint para cada evento y crear un histograma de la distribución. Hicimos esto para cada una de las consultas que ejecutamos.

Así se ve la distribución en computadoras de escritorio con un service worker no controlado (pero compatible):

Distribución del tiempo de la primera pintura en computadoras (compatible)

La mediana de tiempo de firstpaint para la distribución anterior es de 912 ms.

La forma de esta curva es bastante típica de las distribuciones del tiempo de carga. Compara esto con el histograma a continuación, que muestra la distribución de los primeros eventos de pintura para las visitas en las que un service worker controlaba la página.

Distribución del tiempo para el primer procesamiento de imagen en computadoras (controlado)

Ten en cuenta que, cuando un service worker controlaba la página, muchos visitantes experimentaron un primer procesamiento de imagen casi inmediato, con una mediana de 583 ms.

"...cuando un service worker controla la página, muchos visitantes experimentan una primera pintura casi inmediata..."

Para tener una mejor idea de cómo se comparan estas dos distribuciones, en el siguiente gráfico, se muestra una vista combinada de ambas. El histograma que muestra las visitas de un service worker no controlado está superpuesto sobre el histograma que muestra las visitas controladas, y ambos están superpuestos sobre un histograma que muestra ambos combinados.

Distribución del tiempo de la primera pintura en computadoras

Algo que me pareció interesante sobre estos resultados fue que la distribución con un service worker controlado tenía una curva en forma de campana después del aumento repentino inicial. Esperaba un aumento repentino inicial y, luego, un avance gradual; no esperaba un segundo pico en la curva.

Cuando investigué lo que podría estar causando esto, aprendí que, aunque un service worker puede controlar una página, su subproceso puede estar inactivo. El navegador hace esto para ahorrar recursos. Obviamente, no es necesario que todos los service worker estén activos y listos de inmediato para cada sitio que visitaste. Esto explica la cola de la distribución. Para algunos usuarios, hubo un retraso mientras se iniciaba el subproceso del service worker.

Sin embargo, como puedes ver en la distribución, incluso con esta demora inicial, los navegadores con service worker entregan contenido más rápido que los navegadores a través de la red.

Así se veían las cosas en dispositivos móviles:

Distribución del tiempo para el primer procesamiento de imagen en dispositivos móviles

Si bien aún tuvimos un aumento considerable en los tiempos del primer procesamiento de imagen casi inmediatos, la cola era un poco más grande y más larga. Esto probablemente se deba a que, en un dispositivo móvil, iniciar un subproceso de service worker inactivo demora más que en una computadora de escritorio. También explica por qué la diferencia entre el tiempo promedio de firstpaint no fue tan grande como esperaba (se explica más arriba).

"...en un dispositivo móvil, iniciar un subproceso de service worker inactivo demora más que en una computadora de escritorio".

A continuación, se muestra un desglose de estas variaciones de los tiempos del primer procesamiento de imagen en dispositivos móviles y computadoras de escritorio agrupados por estado del service worker:

Tiempo promedio hasta la primera pintura (ms)
Estado de Service Worker Computadoras Dispositivos móviles
Controlaste 583 1634
Compatible (no controlado) 912 1962

Aunque crear estas visualizaciones de distribución requirió un poco más de tiempo y esfuerzo que crear un informe personalizado en Google Analytics, nos brindan una mejor idea de cómo los service workers afectan el rendimiento de nuestro sitio que los promedios solo.

Otro impacto de Service Workers

Además del impacto en el rendimiento, los service worker también influyen en la experiencia del usuario de otras formas que se pueden medir con Google Analytics.

Acceso sin conexión

Los service workers permiten que los usuarios interactúen con tu sitio mientras están sin conexión. Si bien es posible que algún tipo de soporte sin conexión sea fundamental para cualquier app web progresiva, determinar la importancia que tiene en tu caso depende en gran medida de la cantidad de uso que se produce sin conexión. Pero ¿cómo medimos eso?

Se requiere una conexión a Internet para enviar datos a Google Analytics, pero no es necesario que los datos se envíen en el momento exacto en que ocurrió la interacción. Google Analytics admite el envío de datos de interacción después del hecho mediante la especificación de una compensación horaria (mediante el parámetro qt).

Durante los últimos dos años, IOWA ha usado una secuencia de comandos de service worker que detecta hits con errores en Google Analytics cuando el usuario está sin conexión y los vuelve a reproducir más tarde con el parámetro qt.

Para hacer un seguimiento de si el usuario estaba en línea o sin conexión, creamos una dimensión personalizada llamada En línea y la establecimos en el valor de navigator.onLine. Luego, escuchamos los eventos online y offline, y actualizamos la dimensión según corresponda.

Además, para tener una idea de lo común que era que un usuario estuviera sin conexión mientras usaba IOWA, creamos un segmento que se segmentaba para los usuarios con, al menos, una interacción sin conexión. Resulta que ese era casi el 5% de los usuarios.

Notificaciones push

Los service workers permiten que los usuarios acepten recibir notificaciones push. En IOWA, se notificaba a los usuarios cuando una sesión de su programa estaba a punto de comenzar.

Al igual que con cualquier tipo de notificación, es importante encontrar el equilibrio entre proporcionar valor al usuario y molestarlo. Para comprender mejor lo que sucede, es importante hacer un seguimiento de si los usuarios están habilitando estas notificaciones, si interactúan con ellas cuando llegan y si los usuarios que las habilitaron previamente cambiaron sus preferencias y las rechazaron.

En IOWA, solo enviamos notificaciones relacionadas con la agenda personalizada del usuario, algo que solo los usuarios que habían accedido a sus cuentas podían crear. Esto limitó el conjunto de usuarios que podían recibir notificaciones de usuarios que accedían a sus cuentas (se realiza un seguimiento mediante una dimensión personalizada denominada Acceso) cuyos navegadores admiten notificaciones push (se realiza un seguimiento mediante otra dimensión personalizada denominada Permiso de notificaciones).

El siguiente informe se basa en la métrica Usuarios y nuestra dimensión personalizada Permiso de notificaciones, segmentado por los usuarios que accedieron en algún momento y cuyos navegadores admiten notificaciones push.

Nos alegra ver que más de la mitad de los usuarios que accedieron a sus cuentas aceptaron recibir notificaciones push.

Banners de instalación de apps

Si una app web de progreso cumple con los criterios y un usuario la usa con frecuencia, es posible que se le muestre un banner de instalación de app en el que se le solicite que agregue la app a su pantalla principal.

En IOWA, hicimos un seguimiento de la frecuencia con la que se mostraban estos mensajes al usuario (y si se aceptaron) con el siguiente código:

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
    });
  });
});

De los usuarios que vieron un banner de instalación de app, alrededor del 10% eligió agregarlo a la pantalla principal.

Posibles mejoras en el seguimiento (para la próxima vez)

Los datos estadísticos que recopilamos de IOWA este año fueron invaluables. Sin embargo, la retrospectiva siempre trae a los agujeros de luz y oportunidades para mejorar las cosas para la próxima vez. Después de finalizar el análisis de este año, aquí hay dos cosas que nos gustaría haber hecho de otra manera que los lectores que buscan implementar una estrategia similar deberían considerar:

1. Realiza un seguimiento de más eventos relacionados con la experiencia de carga

Realizamos un seguimiento de varios eventos que corresponden a una métrica técnica (p.ej., HTMLImportsLoaded, WebComponentsReady, etc.), pero como gran parte de la carga se realizó de forma asíncrona, el punto en el que se activaron estos eventos no necesariamente se corresponde con un momento particular de la experiencia de carga general.

El evento principal relacionado con la carga del que no hicimos un seguimiento (pero obtendríamos que lo hicieramos) es el punto en el que la pantalla de presentación desapareció y el usuario podía ver el contenido de la página.

2. Almacena el ID de cliente de Analytics en IndexedDB

De manera predeterminada, analytics.js almacena el campo de ID de cliente en las cookies del navegador. Lamentablemente, las secuencias de comandos del service worker no pueden acceder a las cookies.

Esto nos presentó un problema cuando intentamos implementar el seguimiento de notificaciones. Queríamos enviar un evento desde el service worker (a través del Protocolo de medición) cada vez que se enviaba una notificación a un usuario y, luego, hacer un seguimiento del proceso de reactivación de la participación de esa notificación si el usuario hacía clic en ella y volvía a la app.

Si bien pudimos realizar un seguimiento del éxito de las notificaciones en general mediante el parámetro de campaña utm_source, no pudimos vincular una sesión específica para reactivar la participación con un usuario concreto.

Lo que podríamos haber hecho para evitar esta limitación fue almacenar el ID de cliente a través de IndexedDB en nuestro código de seguimiento y, luego, ese valor habría sido accesible para la secuencia de comandos del service worker.

3. Permitir que el service worker informe el estado en línea o sin conexión

Al inspeccionar navigator.onLine, sabrás si tu navegador puede conectarse al router o a la red de área local, pero no necesariamente te dirá si el usuario tiene conectividad real. Y debido a que nuestra secuencia de comandos del service worker de estadísticas sin conexión solo reproducía los hits con errores (sin modificarlos ni marcarlos como con errores), es probable que no informemos demasiado nuestro uso sin conexión.

En el futuro, debemos realizar un seguimiento del estado de navigator.onLine y de si el service worker volvió a reproducir el hit debido a una falla inicial de la red. Esto nos brindará una visión más precisa del verdadero uso sin conexión.

Conclusión

Este caso práctico ha demostrado que el uso de service worker realmente mejoró el rendimiento de carga de la aplicación web de Google I/O en una amplia gama de navegadores, redes y dispositivos. También demostró que cuando observas una distribución de los datos de carga en una amplia gama de navegadores, redes y dispositivos, obtienes mucha más información sobre cómo esta tecnología maneja escenarios del mundo real y descubres características de rendimiento que quizás no esperabas.

Estas son algunas de las conclusiones clave del estudio de IOWA:

  • En promedio, las páginas se cargaban bastante más rápido cuando un service worker controlaba la página, en comparación con los visitantes nuevos y recurrentes.
  • Las visitas a páginas controladas por un service worker se cargan casi al instante para muchos usuarios.
  • Los service workers, cuando están inactivos, tardan un poco en iniciarse. Sin embargo, un service worker inactivo sigue funcionando mejor que ningún service worker.
  • El tiempo de inicio de un service worker inactivo fue más prolongado en un dispositivo móvil que en una computadora de escritorio.

Si bien las mejoras de rendimiento observadas en una aplicación en particular suelen ser útiles para informar a la comunidad de desarrolladores más grande, es importante recordar que estos resultados son específicos para el tipo de sitio IOWA (un sitio de evento) y el tipo de público que tiene IOWA (principalmente desarrolladores).

Si implementas un service worker en tu aplicación, es importante que implementes tu propia estrategia de medición para poder evaluar tu propio rendimiento y evitar futuras regresiones. Si lo haces, comparte los resultados para que todos puedan beneficiarse.

Pies de página

  1. No es del todo justo comparar el rendimiento de la implementación de la caché de nuestro service worker con el rendimiento de nuestro sitio solo con el caché de HTTP. Debido a que estábamos optimizando IOWA para el service worker, no invertimos mucho tiempo en optimizar la caché HTTP. Si hubiésemos tenido, probablemente los resultados habrían sido diferentes. Si quieres obtener más información sobre cómo optimizar tu sitio para el almacenamiento en caché de HTTP, consulta Cómo optimizar el contenido de manera eficiente.
  2. Según cómo cargue los estilos y el contenido de su sitio, es posible que el navegador pueda pintar antes de que el contenido o los estilos estén disponibles. En esos casos, firstpaint puede corresponder a una pantalla blanca en blanco. Si usas firstpaint, es importante que te asegures de que corresponda a un punto significativo en la carga de los recursos de tu sitio.
  3. Técnicamente, podríamos enviar un hit de sincronización (que, de forma predeterminada, no son interacciones) para capturar esta información en lugar de un evento. De hecho, los hits de tiempo se agregaron a Google Analytics específicamente para hacer un seguimiento de métricas de carga como esta. Sin embargo, los hits de tiempo se muestrean en gran medida en el momento de procesamiento y sus valores no se pueden utilizar en segmentos. Dadas estas limitaciones actuales, los eventos sin interacción siguen siendo más adecuados.
  4. Para comprender mejor qué alcance asignar a una dimensión personalizada en Google Analytics, consulta la sección Dimensión personalizada del Centro de ayuda de Analytics. También es importante comprender el modelo de datos de Google Analytics, que consta de usuarios, interacciones (hits) y sesiones. Para obtener más información, mira la lección de Analytics Academy sobre el modelo de datos de Google Analytics.
  5. Esto no considera los recursos que se cargan de forma diferida después del evento de carga.