Pular para o conteúdo

16. Análise de Dados - Parte 1: Fundamentos e Implementação

Este documento apresenta estratégias e práticas para implementar análise de dados em soluções web desenvolvidas com Astro 5, permitindo coletar, processar e visualizar dados de usuários de forma ética e eficiente.

Fundamentos da Análise de Dados

Conceitos Básicos

  • Análise de Dados: Processo de inspeção, limpeza, transformação e modelagem de dados para descobrir informações úteis e apoiar a tomada de decisões.
  • Métricas: Valores quantificáveis que permitem medir o desempenho de um site ou aplicação.
  • KPIs (Key Performance Indicators): Indicadores-chave de desempenho que medem o sucesso em relação a objetivos específicos.
  • Eventos: Ações realizadas pelos usuários que são registradas para análise posterior.
  • Funil de Conversão: Sequência de etapas que os usuários percorrem até realizar uma ação desejada.

Tipos de Dados para Análise

  1. Dados de Comportamento: Como os usuários interagem com o site (cliques, navegação, tempo de permanência).
  2. Dados de Performance: Velocidade de carregamento, tempos de resposta, erros.
  3. Dados Demográficos: Informações sobre os usuários (localização, dispositivo, navegador).
  4. Dados de Conversão: Ações que representam valor para o negócio (cadastros, compras, downloads).

Benefícios da Análise de Dados

  1. Tomada de Decisão Baseada em Dados: Fundamentar decisões em evidências concretas.
  2. Otimização da Experiência do Usuário: Identificar pontos de atrito e oportunidades de melhoria.
  3. Personalização: Adaptar o conteúdo e funcionalidades às necessidades dos usuários.
  4. Medição de ROI: Avaliar o retorno sobre investimento de iniciativas de marketing e desenvolvimento.

Implementação com Astro

Estratégias de Coleta de Dados

1. Integração com Serviços de Análise

Google Analytics 4

src/components/Analytics.astro
---
const GA_MEASUREMENT_ID = import.meta.env.PUBLIC_GA_MEASUREMENT_ID;
---
{GA_MEASUREMENT_ID && (
<Fragment>
<script
type="text/partytown"
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
></script>
<script type="text/partytown" define:vars={{ GA_MEASUREMENT_ID }}>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', GA_MEASUREMENT_ID, {
send_page_view: false, // Configuramos manualmente para SPA
});
// Enviar pageview inicial
gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href,
page_path: window.location.pathname
});
// Monitorar mudanças de rota em SPAs
document.addEventListener('astro:page-load', () => {
gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href,
page_path: window.location.pathname
});
});
</script>
</Fragment>
)}

Plausible Analytics (Alternativa Focada em Privacidade)

src/components/PlausibleAnalytics.astro
---
const PLAUSIBLE_DOMAIN = import.meta.env.PUBLIC_PLAUSIBLE_DOMAIN;
---
{PLAUSIBLE_DOMAIN && (
<script
defer
data-domain={PLAUSIBLE_DOMAIN}
src="https://plausible.io/js/script.js"
></script>
)}

2. Implementação Própria com Endpoint de API

src/components/EventTracker.astro
<script>
class EventTracker {
constructor() {
this.queue = [];
this.flushInterval = 10000; // 10 segundos
this.endpoint = '/api/analytics';
this.sessionId = this.generateSessionId();
this.clientId = this.getClientId();
// Iniciar flush periódico
setInterval(() => this.flush(), this.flushInterval);
// Enviar eventos pendentes antes do usuário sair da página
window.addEventListener('beforeunload', () => this.flush());
// Registrar pageview inicial
this.trackPageView();
// Monitorar mudanças de página em SPAs
document.addEventListener('astro:page-load', () => this.trackPageView());
}
generateSessionId() {
return `${Date.now()}-${Math.random().toString(36).substring(2, 12)}`;
}
getClientId() {
let clientId = localStorage.getItem('analytics_client_id');
if (!clientId) {
clientId = `${Date.now()}-${Math.random().toString(36).substring(2, 16)}`;
localStorage.setItem('analytics_client_id', clientId);
}
return clientId;
}
trackEvent(eventName, eventData = {}) {
const event = {
name: eventName,
data: eventData,
timestamp: new Date().toISOString(),
url: window.location.href,
referrer: document.referrer,
sessionId: this.sessionId,
clientId: this.clientId,
userAgent: navigator.userAgent,
screenSize: `${window.innerWidth}x${window.innerHeight}`,
language: navigator.language,
};
this.queue.push(event);
// Flush imediatamente se for um evento importante
if (eventName === 'conversion' || eventName === 'signup' || eventName === 'purchase') {
this.flush();
}
}
trackPageView() {
this.trackEvent('pageview', {
title: document.title,
path: window.location.pathname,
});
}
async flush() {
if (this.queue.length === 0) return;
const events = [...this.queue];
this.queue = [];
try {
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ events }),
// Usar keepalive para garantir que a requisição seja concluída
// mesmo se o usuário navegar para outra página
keepalive: true,
});
if (!response.ok) {
throw new Error(`Analytics API responded with ${response.status}`);
}
} catch (error) {
console.error('Failed to send analytics events:', error);
// Recolocar eventos na fila para tentar novamente
this.queue = [...events, ...this.queue];
}
}
}
// Inicializar e expor globalmente
window.eventTracker = new EventTracker();
// API simplificada
window.trackEvent = (name, data) => {
window.eventTracker.trackEvent(name, data);
};
</script>

Endpoint de API para receber eventos:

src/pages/api/analytics.js
export async function post({ request }) {
try {
const { events } = await request.json();
// Validar dados recebidos
if (!Array.isArray(events)) {
return new Response(JSON.stringify({ error: 'Invalid events format' }), {
status: 400,
headers: {
'Content-Type': 'application/json'
}
});
}
// Processar eventos (exemplo: salvar em banco de dados)
await processEvents(events);
return new Response(JSON.stringify({ success: true, count: events.length }), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
});
} catch (error) {
console.error('Error processing analytics events:', error);
return new Response(JSON.stringify({ error: 'Internal server error' }), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
});
}
}
async function processEvents(events) {
// Implementação depende do seu backend
// Exemplos:
// - Salvar em banco de dados (Supabase, Firebase, MongoDB, etc.)
// - Enviar para serviço de streaming (Kafka, Kinesis, etc.)
// - Armazenar em arquivo de log
// Exemplo com Supabase
if (import.meta.env.SUPABASE_URL && import.meta.env.SUPABASE_KEY) {
const { createClient } = await import('@supabase/supabase-js');
const supabase = createClient(
import.meta.env.SUPABASE_URL,
import.meta.env.SUPABASE_KEY
);
const { error } = await supabase
.from('analytics_events')
.insert(events);
if (error) throw error;
}
}

Integração no Layout Principal

src/layouts/BaseLayout.astro
---
import Analytics from '../components/Analytics.astro';
import EventTracker from '../components/EventTracker.astro';
import PlausibleAnalytics from '../components/PlausibleAnalytics.astro';
const { title, description } = Astro.props;
---
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<meta name="description" content={description}>
<!-- Componentes de Analytics -->
<Analytics />
<PlausibleAnalytics />
<slot name="head" />
</head>
<body>
<header>
<!-- Cabeçalho do site -->
</header>
<main>
<slot />
</main>
<footer>
<!-- Rodapé do site -->
</footer>
<!-- Rastreador de eventos personalizado -->
<EventTracker />
</body>
</html>

Rastreamento de Eventos Personalizados

Rastreamento de Interações do Usuário

src/components/ContactForm.astro
<form id="contact-form" class="contact-form">
<div class="form-group">
<label for="name">Nome</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">E-mail</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="message">Mensagem</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit">Enviar Mensagem</button>
</form>
<script>
const form = document.getElementById('contact-form');
if (form) {
// Rastrear visualização do formulário
window.trackEvent('form_view', { form_id: 'contact' });
// Rastrear início do preenchimento
const inputs = form.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('focus', () => {
if (!input.dataset.focused) {
window.trackEvent('form_field_focus', {
form_id: 'contact',
field_id: input.id
});
input.dataset.focused = 'true';
}
});
});
// Rastrear envio do formulário
form.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(form);
const formValues = Object.fromEntries(formData.entries());
// Enviar dados do formulário para o servidor
fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formValues),
})
.then(response => response.json())
.then(data => {
// Rastrear sucesso
window.trackEvent('form_submit', {
form_id: 'contact',
success: true
});
// Mostrar mensagem de sucesso
alert('Mensagem enviada com sucesso!');
form.reset();
})
.catch(error => {
// Rastrear erro
window.trackEvent('form_submit', {
form_id: 'contact',
success: false,
error: error.message
});
// Mostrar mensagem de erro
alert('Erro ao enviar mensagem. Por favor, tente novamente.');
});
});
}
</script>

Rastreamento de Engajamento com Conteúdo

src/components/ArticleContent.astro
---
const { article } = Astro.props;
---
<article class="article-content" data-article-id={article.id}>
<h1>{article.title}</h1>
<p class="article-meta">
Publicado em {article.publishDate}{article.readingTime} min de leitura
</p>
<div class="article-body">
<slot />
</div>
</article>
<script>
document.addEventListener('DOMContentLoaded', () => {
const article = document.querySelector('.article-content');
if (!article) return;
const articleId = article.dataset.articleId;
const articleTitle = article.querySelector('h1')?.textContent || '';
// Rastrear visualização do artigo
window.trackEvent('article_view', {
article_id: articleId,
article_title: articleTitle,
});
// Rastrear progresso de leitura
const articleBody = article.querySelector('.article-body');
if (!articleBody) return;
let readingProgress = 0;
let maxProgress = 0;
let timeOnPage = 0;
let intervalId;
// Iniciar timer
intervalId = setInterval(() => {
timeOnPage += 5;
// Enviar evento a cada 30 segundos
if (timeOnPage % 30 === 0) {
window.trackEvent('article_engagement', {
article_id: articleId,
time_on_page: timeOnPage,
max_scroll_percentage: maxProgress,
});
}
}, 5000);
// Rastrear scroll
window.addEventListener('scroll', () => {
const rect = articleBody.getBoundingClientRect();
const windowHeight = window.innerHeight;
// Calcular quanto do artigo já foi visualizado
if (rect.top < windowHeight && rect.bottom > 0) {
const totalHeight = rect.height;
const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0);
const visiblePercentage = Math.round((visibleHeight / totalHeight) * 100);
// Atualizar progresso máximo
if (visiblePercentage > maxProgress) {
maxProgress = visiblePercentage;
// Rastrear marcos de progresso (25%, 50%, 75%, 100%)
if (maxProgress >= 25 && readingProgress < 25) {
readingProgress = 25;
trackReadingMilestone(25);
} else if (maxProgress >= 50 && readingProgress < 50) {
readingProgress = 50;
trackReadingMilestone(50);
} else if (maxProgress >= 75 && readingProgress < 75) {
readingProgress = 75;
trackReadingMilestone(75);
} else if (maxProgress >= 90 && readingProgress < 90) {
readingProgress = 90;
trackReadingMilestone(90);
}
}
}
});
function trackReadingMilestone(percentage) {
window.trackEvent('article_milestone', {
article_id: articleId,
percentage: percentage,
time_on_page: timeOnPage,
});
}
// Limpar timer quando o usuário sai da página
window.addEventListener('beforeunload', () => {
clearInterval(intervalId);
// Enviar evento final
window.trackEvent('article_exit', {
article_id: articleId,
time_on_page: timeOnPage,
max_scroll_percentage: maxProgress,
});
});
});
</script>

Continua na Parte 2: Processamento, Visualização e Privacidade de Dados