Artigo original: How to set up performance and user tracking in React with Google Analytics
Escrito por: Mohammad Iqbal
O monitoramento de usuários e a performance de apps é uma parte crucial do desenvolvimento para a web moderno. Talvez você já tenha visto empresas que aumentaram sua renda, simplesmente, ao diminuir o tempo de carregamento de seu app em algumas centenas de milissegundos.
Monitorar o comportamento de usuários também é crucial. Permitirá modificar e criar seu app de acordo com as preferências de interação dos usuários, resultando em usuários felizes e mais tráfego para o seu site.
Neste guia, vou dar a você noções fundamentais e completas para o monitoramento de performance e de comportamento dos usuários. Ao fim desse tutorial, você terá tudo o que é necessário para criar configurações complexas de monitoramento de usuários e performance.
Índice
- Introdução
- Google Analytics
- Monitoramento pelas Page Views (Visualização de Páginas)
- Monitoramento pela performance de carregamento
- Monitoramento pela performance de renderização
- Monitoramento a performance de paint
- Monitoramento por rolagem
- Monitoramento por eventos
- Dicas rápidas sobre performance e heurísticas
Introdução
Vou mostrar para você algumas métricas de performance para o desenvolvimento em React e tentarei manter este tutorial conciso. Em uma situação da vida real, porém, não use a versão do Desenvolvedor, porque ela contém muitos erros nos códigos de manipulação e não possui tantas otimizações, o que pode levar a resultados altamente distorcidos.
Por essa razão, é melhor testar na build de produção e, assim, estabelecer algumas métricas na linha de base.
Por simplicidade, vou me referir ao Google Analytics como "GA".
O GA não funciona com local host. Então, para ter acesso a uma simulação do que provavelmente será enviado para o GA — sem realmente enviar e acabar por estragar seus analytics — você pode simplesmente substituir o console.log em qualquer lugar que você pretenda colocar seus hits do GA.
<ReactGA> é uma função global e é conhecida como um comando de fila, porque não executa comandos imediatamente. Ela. porém, adiciona comandos a uma fila, e aí, os envia assincronamente. Isso não está conectado com a main thread e o seu tracking code não prejudicará a performance de seu app.
Um "hit" é quando o GA tracker envia dados para o Google Analytics.
Vamos focar no tutorial de códigos em React, pois há outros tutoriais muito melhores para aprender a como usar o GA.
Existem três principais funções usados para enviar hits ao GA. Na verdade, há mais funções. Neste tutorial, contudo, vamos focar apenas em três.
GAReact.pageview()
: vai passar por uma string que contém a route.
GAReact.timing()
: vai pegar um objeto como parâmetro. E irá conter informações relacionadas às métricas de performance, as quais veremos logo abaixo. Os campos serão category
, variable
, value
e label
. Note que apenas a propriedade value
virá pelo nosso app, o restante das propriedades serão definidas pelo user.
GAReact.event()
: vai pegar um objeto como parâmetro. Ela conterá dados sobre eventos que aocntecem no app (submissões de formulário, cliques etc). Também possuirá campos como category
, action
, label
e value
. Note que apenas a propriedade value
virá pelo nosso app, o restante das propriedades serão definidas pelo user.
Testes Sintéticos x RUM
Você deve estar se perguntando o porquê de fazer tudo isso para a configuração das métricas do Performance Observer, se é possível usar ferramentas como Lighthouse ou Webpage, rodar um teste rápido e coletar essas métricas apenas entrando no URL.
Ferramentas como essas são importantes, mas elas são conhecidas como "testes sintéticos": o teste será feito em seu dispositivo e não refletirá o que os seus usuários estão experienciando. Você pode tentar limitar a quantidade de dados transmitidos pela CPU ou pela rede quando fizer esses testes. No entanto, eles ainda serão simulações.
Ao usar o GA com as métricas do Performance Observer, teremos números realistas com usuários finais, dispositivos e redes também reais. Isso é conhecido como RUM ou Real User Monitoring (em português, monitoramento do usuário real).
Para rodar testes sintéticos em ferramentas, simplesmente, insira o URL do seu app.
- https://www.webpagetest.org/
- https://www.thinkwithgoogle.com/feature/testmysite
- https://developers.google.com/speed/pagespeed/insights/
- https://www.uptrends.com/tools/website-speed-test
- https://tools.pingdom.com/
Google Analytics
Configuração
Se você já tiver a configuração do Google Analytics na sua aplicação, fique à vontade para pular esta seção, pois não há nada de novo aqui.
Para fazer a configuração do GA, vá até o dashboard e clique na janela de administrador, ao lado. Depois, clique em "add property". Preencha os campos obrigatórios.
Se está lendo este tutorial, vou entender que você consegue fazer a configuração do Google Analytics usando apenas os passos simples acima. Em caso negativo, use este guia rápido do Google (em inglês).
Depois de finalizar a configuração, note que um "tracking ID" aparece no topo da página, Vamos usar isso no nosso React App.
Configuração no React
Não precisamos inventar a roda aqui. Podemos usar uma biblioteca feita pela Mozilla Foundation para nos ajudar com a configuração no React.
Para instalar a biblioteca, basta rodar este código: npm install react-ga
Depois, para usar o ReactGA, importe-o para qualquer lugar de sua escolha:
import ReactGA from 'react-ga';
Você também precisará inicializar a o componente raíz com o Tracking ID do Google Analytics:
...
ReactGA.initialize('UA-00000-1');
...
Observers
O ReactGA não faz nada além de enviar dados para o site do Google Analytics. Para fazer com que as métricas de performance sejam enviadas, vamos usar a API "Performance Observer". Essa API não tem nada a ver com o GA nem com o React. É uma API de navegador disponível nos navegadores mais modernos.
O funcionamento do PerformanceObserver e APIs similares é um tema muito profundo e está fora de escopo deste tutorial. Caso queira ler sobre conteúdos relacionados a esses tópicos, dê uma olhada na seção "Leia mais" para maiores informações e links de tutoriais.
Em termos gerais, eles "observam" alguma coisa, e aí, enviam esses dados assincronamente, no momento configurado pelo navegador. Isso mantém a thread principal livre para funcionalidades críticas do app e tasks relacionadas, portanto, rastrear eventos não afeta a performance do app.
Antes dos "Observers", você teria de adicionar uma "escuta de eventos" e dispará-la sincronamente, todas às vezes que alguma coisa acontecesse — isso resultaria em problemas de performance evidentes, caso muitos eventos fossem disparados de uma vez só. Esse é o problema que os "observers" procuram resolver.
Monitoramento das Page Views (Visualização de Páginas)
Monitorar visualizações de páginas em uma página de app única, como no caso do React, pode parecer complicado. É, porém, relativamente simples, graças ao React-router e ao histórico de bibliotecas.
history.listen((location) => {
ReactGA.set({ page: location.pathname });
ReactGA.pageview(location.pathname)
}
);
O history.listen()
permite que você dispare uma função de callback, nas mudanças de route, que, no nosso caso, são os hits do GA. A route está contida na propriedade pathname
de location
. Algumas coisas, no entanto, precisam ser observadas.
Lidando com o carregamento inicial
O histórico está "escutando" por mudanças de páginas, mas não gera um hit no carregamento inicial da página.
Existem algumas maneiras de lidar com o carregamento inicial. Eu encontrei um caminho simples para se fazer isso e considero que exija pouquíssimo código e complexidade: eu apenas salve a variável de carregamento inicial na declaração global. Na Home Page, use uma condicional para checar se é falsa. Em caso positivo, configure-a para verdadeira depois do hit.
Variável de contexto no componente pai:
...
const [initialLoad, setInitialLoad] = useState(false)
...
<Context.Provider
//Initial Load
initialLoadProp: initialLoad,
setInitialLoadProp: () => setInitialLoad(true),
>
</Context.Provider>
Em seguida, o useEffect()
na home do componente filho:
...
useEffect(() => {
if(!context.initialLoadProp) {
ReactGA.pageview(props.location.pathname);
context.setInitialLoadProp(true)
}
}, [])
...
Há outras implementações e discussões que você encontrará aqui.
Monitoramento de páginas com User IDs
Uma outra coisa que você talvez queira saber é quantos usuários visitam seus perfis de páginas privadas. O PageViews apenas daria um URL único para cada hit e não funcionaria.
Por exemplo, suponha que você tenha os seguintes URLs com user IDs:
user/4543456/messages
user/4543456/account
user/3543564/messages
user/3543564/replytomessage
user/3543564/account
Todos eles dariam um hit único. Um ajuste simples seria apenas checar com uma condicional e remover o ID único. Você também pode usar uma condicional para ter certeza de que não enviará essas páginas para o Google Analytics. Veja:
...
history.listen((location) => {
if(location.pathname.includes('/user')) {
let rootURL = location.pathname.split('/')[1]
let userPage = location.pathname.split('/')[3]
let pageHit = `/${rootURL}/${userPage}`
ReactGA.pageview(pageHit)
} else {
ReactGA.set({ page: location.pathname });
ReactGA.pageview(location.pathname)
}
});
...
Estamos simplesmente desmembrando o User ID e concatenando a route de novo, antes de enviar o hit.
No entanto, isso, provavelmente, não seria verdade para posts em fóruns. Ter um URL único para cada post seria o correto, considerando que você gostaria de saber quantas pessoas acessaram cada post.
Monitoramento de performance de carregamento
Acessar a performance de carregamento é relativamente fácil. Todos os dados de carregamento de performance estão na entrada de navigation
e essa, por sua vez, faz parte da API navigation timing.
O Performance Observer pode ser configurado na raiz do componente pai, como mostra o exemplo abaixo:
const callback = list => {
list.getEntries().forEach(entry => {
ReactGA.timing({
category: "Load Performace",
variable: "Some metric",
value: "Value of Metric"
})
})
}
var observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['navigation'] })
Primeiro, definimos a função que será chamada para cada entry. Depois, passamos essa função de callback para o nosso PerformanceObserver
e, finalmente, chamamos o método .observe()
para o nosso observador e passamos para navigation
como uma função de entryType.
Assim, nós teremos a seguinte função de entry:

É um volume bem alto de informação, mas precisamos apenas de três propriedades para monitorar a performance de carregamento principal:
requestStart
: quando o navegador emite uma solicitação para obter a página da web do servidor;
responsesStart
: quando o primeiro byte do site chega;
responseEnd
: quando o último byte do site chega.
Há algumas coisas que acontecem antes da requestStart
, como: a consulta DNS e o handshake TLS. Use esses dados e veja se consegue combiná-los com outras propriedades para fazer novas métricas.
Com as três propriedades acima, conseguimos criar três métricas importantes. Simplesmente, substitua as propriedades variable
e value
pelas respectivas métricas.
const callback = list => {
list.getEntries().forEach(entry => {
ReactGA.timing({
category: "Load Performace",
variable: 'Server Latency',
value: entry.responseStart - entry.requestStart
})
})
}
Latência do servidor:entry.responseStart - entry.requestStart
Tempo de download:entry.responseEnd - entry.responseStart
Tempo total de carregamento do app:entry.responseEnd - entry.requestStart
Tempo para interatividade
Basicamente, essa métrica considera o tempo que usuários levam para interagir com o seu site.
Até que uma métrica do tipo native TTI esteja disponível pela API do navegador, podemos usar esse módulo de polyfill do npm. Esse módulo foi criado pelo Google.
npm install tti-polyfill
Depois, conseguimos usar o polyfill
como no exemplo abaixo:
...
import ttiPolyfill from 'tti-polyfill';
ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
ReactGA.timing({
category: "Load Performace",
variable: 'Time to Interactive',
value: tti
})
});
...
Estamos, simplesmente, enviando um hit dentro da nossa função encadeada com a declaração .then()
, visto que vamos obter os dados dessa métrica assincronamente.
Monitorando a performance de renderização
Agora, conseguiremos nos aprofundar na performance de renderização: o tempo que o React leva para construir a árvore de nós do DOM. Podemos monitorar a performance de renderização com as entradas mark
e measure
.
mark
é usado para marcar o ponto no tempo que você queira monitorar. Apenas passe-o em uma string como o nome da marca na linha exata onde você deseja marcar para rastreamento.
performance.mark("nome da marca")
measure
é a diferença entre as duas marcas. Apenas configure no nome da measure
e passe nas marcas de começo e fim; isso trará a diferença entre marcas, em milissegundos.
performance.measure.("name of mark", startingMark, EndingMark)
Felizmente, o React vem com marcas e métricas pré-compiladas, o que nos poupa de ter que abrir o React Source e nós mesmos escrevê-los. Apenas, mude a mark
e a measure
para "entry types" e pronto!
...
var observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['mark', 'measure'] })
...
Isso vai dar a você o tempo que as raízes dos componentes "pai" e todos os componentes "filhos" levam para renderizar em milissegundos. Você terá entradas que se parecem com estas:

Você também terá o tempo de execução dos métodos de ciclo de vida. Há uma riqueza de informações aqui, escolha apenas a que você quer rastrear e a envie para a GA; para isso, procure pelo nome em uma declaração condicional.
...
const callback = list => {
list.getEntries().forEach(entry => {
if(entry.name.includes('App') ) {
ReactGA.timing({
category: "App Render Performace",
variable: entry.name,
value: entry.duration
})
}
})
}
...
Monitorando a performance de paint
Agora, podemos rastrear o paint; o momento que os pixels são desenhados (ou "pintados") na tela, depois que a árvore do DOM é renderizada.
O rastreamento de "Paint Performance" inclui três métricas: First Paint (FP), First Contentful Paint (FCP) e First Meaningful Paint (FMP).
First Paint: primeira vez que qualquer coisa ao lado de uma página branca é apresentada na tela.
First Contentful Paint: quando o primeiro elemento do DOM está presente. Texto, imagem etc.
First Paint e First Contentful Paint serão dados automaticamente pela API. Apenas, faça o seguinte:
...
const callback = list => {
list.getEntries().forEach(entry => {
ReactGA.timing({
category: "Paint",
variable: entry.name,
value: entry.startTime
})
})
}
var observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['paint'] })
As entradas serão similares a estas:

É completamente possível que as métricas sejam exatamente as mesmas, por conta dos milissegundos. Ainda que não sejam as mesmas, é provável que haja uma diferença irrelevante entre elas. Em razão disso, há uma outra métrica mais usada, chamada:
First Meaningful Paint: quando alguma coisa "significativa" é pintada na tela. "Significativa" é escrito aqui com aspas, pois esse termo é intencionalmente considerado vago, o que permite a pessoa desenvolvedora decidir o que ela deseja testar.
De acordo com o Google, o First Paint e o First Contentful Paint respondem a questão "Está acontecendo?" e o First Meaningful Paint responde a pergunta "Isso é útil?". Já questão "Isso é usável?" é respondida pelo "Tempo para interatividade".
É comum usar o First Meaningful Paint para testar o elemento "Hero", que, por sua vez, é o elemento principal da página.
Um exemplo disso, no YouTube, seria o player do vídeo. Vídeos e comentários sugeridos seriam elementos secundários, não heroes. Monitorar quando o reprodutor de vídeo acaba de "pintar", seria o First Meaningful Paint, nesse caso.
Um elemento hero comum em homepages é a imagem de plano de fundo, próximo ao header, o que constitui a proposta de valor ou o tema do site. Sabendo disso, podemos usar o recurso de tempo da API para metrificar o momento que a imagem acaba de carregar e fazemos da First Meaningful Paint (FMP), a nossa métrica.
Se o seu elemento hero é uma imagem, você pode apenas considerar o recurso de tempo da API e a propriedade responseEnd
como seu FMP.
...
const callback = list => {
list.getEntries().forEach(entry => {
ReactGA.timing({
category: "First Meaningful Paint",
variable: entry.name,
value: entry.responseEnd
})
})
}
var observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['resource'] })
...
Recurso inteiro do timing de resposta:

Dado que, tecnicamente, a imagem carregada não significa que está pintada, você também pode tentar configurar as marcas, manualmente.
//jsx
{performance.mark('start')
<img />
{performance.mark('end')
{performance.measure('diff', 'start', 'end')
De novo: há muitas possibilidades nessa métrica, realmente considere o seu contexto e o que você está tentando metrificar.
Monitoramento de rolagem
Rastrear a posição de rolagem de seus usuários é uma métrica realmente muito importante de analytics. Você pode, por exemplo, ver quantos usuários passaram rapidamente uma certa imagem ou seção do texto. Tendo essa informação, você pode dar uma mexida no seu conteúdo e aumentar conversões.
Você já deve ter visto implementações antigas que usam getBoundingClientRect(), mas isso bloquearia a thread principal e, então, o monitoramento de rolagem diminuiria a performance. Você pode executar os eventos de rolagem assincronamente com o IntersectionObserver
.
O IntersectionObserver
necessita de dois argumentos, uma função de callback e um objeto de opções. O objeto de opções terá três propriedades:
root
: é o elemento em que você está tentando testar a intersecção. Na maioria dos caso, esse será o viewport, que será o valor de null
.
rootMargin
: são as margens ao redor do elemento raiz. Exemplo: "10px"
threshold
: quanto do elemento é visível antes que a propriedade isIntersecting
é verdadeira. Exemplo: 0.5 significa que isIntersecting
é verdadeira, quando 50% do elemento é visível. "Zero" significa o topo do elemento e 1.0 significa o elemento inteiro.
A entrada retornará um objeto como este:

isIntersecting
: essencialmente, usado para determinar se o elemento é visível, o que seria verdadeiro, quando o limite é alcançado. Vamos ao código:
//Get the element you want to track with useRef
const intersectTarget = useRef(null)
//Use the observer inside the component you want to track scroll
useEffect(() => {
const opts = {
root: null,
rootMargin: '0px',
threshold: 0
}
const callback = list => {
list.forEach(entry => {
if(entry.isIntersecting) {
ReactGA.event({
category: 'Scroll',
action: 'Scrolled to heading 2',
value: entry.intersectionRatio
})
}
})
}
const observerScroll = new IntersectionObserver(callback, opts)
observerScroll.observe(intersectTarget.current)
}, [])
//jsx
<h1 ref={intersectTarget}
id="heading2"
>
Second Heading
</h1>
A principal ideia aqui, é inicializar o hook useRef
. Depois, usamos a ref no elemento do qual você quer monitorar a rolagem. A função de callback e a observer serão chamadas pelo hook useEffect
. Enquanto que o elemento será passado para o método .observe()
com o nome da ref e a propriedade .current
.
Observação 1: a intersecção do "observer" será acionada na página de carregamento inicial, mesmo que o elemento não esteja visível. Isso é normal, você também verá que a propriedade isIntersecting
é falsa.
Observação 2: nesse momento do código, também há a propriedade isVisible
na entrada, mas ela não funciona como o nome sugere. Ela se mantém falsa, ainda que o elemento esteja visível. Não há documentações sobre isso, então não consigo comentar sobre o seu propósito. Recomendo substituir pela propriedade isIntersecting
.
Monitoramento de eventos
A ideia básica do monitoramento de eventos é enviar hits para o GA, dentro da função de callback, anexados a um manipulador de eventos. Realmente, não há muito o que fazer além disso.
Um ponto a considerar é se o seu usuário está enviando um formulário, você consegue especificar com a propriedade transport: beacon
no hit do evento, o qual vai deixar você enviar o hit, mesmo que a página seja recarregada para uma outra página. Isso não é considerado um grande problema, em uma página de app única como o React, mas, se você quisesse fazer isso, saiba que essa opção está disponível.
Consulte o sendBeacon do navegador para ver mais informações.
Vou mostrar como monitorar envios de formulário. Este padrão funciona, basicamente, com qualquer evento, Novamente, você está apenas enviando o hit em uma função anexada a um manipulador de eventos.
...
const handleSubmit = (event) => {
event.preventDefault();
ReactGA.event({
category: 'Form',
action: 'Form Submit',
transport: 'beacon'
});
apiCall('/apicall', event.target.value)
};
...
<form onSubmit={handleSubmit}>
...
</form>
...
Dicas rápidas sobre performance e heurísticas
A maior área de melhoria para desenvolvedores, que eu vejo, é programar do zero ao invés de usar bibliotecas. O "Tree Shaking" reduz um pouco o excesso de código, mas ainda há um "inchaço" considerável quando comparado a códigos feitos por você. Isso vai permitir que você escreva apenas os códigos que necessita. Tente o usar o mínimo de bibliotecas possível. Ao invés de usar bibliotecas para algumas poucas funções, consulte só o código-fonte da biblioteca e tente, você mesmo, implementar essas funções. Além disso, mantenha em mente que a maioria das bibliotecas têm de priorizar facilidade de uso e personalização, em detrimento da performance.
Mais algumas dicas:
- Para acionar eventos como rolagem, você definitivamente precisará desacoplar. Não é necessário fazer isso para observers.
- Apenas importe funções e variáveis que você usará e deixe que o "Tree Shaking" descarte códigos que não foram usados.
- Não passe funções anônimas para eventos.
- Transforme seu app em React em uma PWA. Assim, os usuários conseguirão baixar e instalar o seu app, localmente, em seus aparelhos.
- Reduza os "payloads" com "code splitting" e "lazy loading".
- Diminua o tamanho do arquivo usando "gzip" ou compressões similares.
- Sirva imagens a partir de um CDN.
- Habilite o cache por meio do título "http" nas respostas do servidor.
- Otimize imagens. Confira o guia de fundamentos do Google para saber como fazer isso.
- Use o novo layout do CSS Flexbox. Evite o Layout Thrashing.
- Use somente transformadores e opacidade para mudanças de CSS. Ou seja, ao invés de alterar altura e largura, use a escala X e a escala Y.
Felizmente, muitas dessas otimizações, como minificação e "gzip", são realizadas automaticamente, quando o comando "npm build" é executado em um projeto do React.
Agradeço a leitura! 😀