Contents
Introducción
Este artículo explica, paso a paso y con ejemplos completos, cómo medir los Core Web Vitals (y métricas relacionadas) desde JavaScript en el navegador y enviarlas a una ruta de la REST API de WordPress para almacenarlas y analizarlas. Se incluyen ejemplos de código para el cliente (JavaScript) y para el servidor (PHP — plugin o mu-plugin), recomendaciones de seguridad, formatos de almacenamiento y buenas prácticas para producción.
Qué son Web Vitals y por qué recogerlos
Web Vitals son métricas estándar definidas por Google para medir la experiencia del usuario: LCP (Largest Contentful Paint), CLS (Cumulative Layout Shift), FID/INP (First Input Delay / Interaction to Next Paint), además de TTFB o FCP que habitualmente se registran para diagnóstico. Recoger estas métricas desde el navegador real (RUM — Real User Monitoring) permite detectar problemas reales en producción que no aparecen en pruebas sintéticas.
Requisitos y consideraciones
- WordPress (puede ser plugin o tema que añada la lógica).
- Acceso para añadir código JavaScript que se ejecute en el frontend en todas (o las páginas objetivo).
- Decisión sobre autenticación: la mayoría de las métricas vienen de usuarios anónimos, así que la ruta REST suele dejarse pública con validaciones y controles (rates, sanitización, comprobación de referer / CORS).
- Decidir cómo almacenar: tabla personalizada, post type, opciones batched o un servicio externo (BigQuery, Elasticsearch).
Instalación de la librería web-vitals
Para recoger los Web Vitals, la librería oficial web-vitals es la opción más sencilla y fiable. Puede importarse desde CDN o empaquetarse con su build.
Importar desde CDN (módulo ES)
Uso recomendado si no quieres empaquetar: importar como módulo desde unpkg. El ejemplo de envío también mostrará cómo usar sendBeacon o fetch con keepalive para no interferir con la navegación.
Medir métricas en el cliente (JavaScript)
Ejemplo de script que recoge LCP, CLS, INP (o FID), FCP y TTFB y los envía a la REST API del sitio. El script usa navigator.sendBeacon cuando está disponible, y fallback a fetch con keepalive.
/
Ejemplo de cliente: recoge métricas con web-vitals y las envía
Requisitos previos en el servidor: exponer un endpoint REST en /wp-json/webvitals/v1/collect
Se asume que hay un objeto global window.WP_WEBVITALS con {endpoint, nonce?}
/
(async function () {
// Dinámicamente importamos la librería web-vitals como módulo (CDN)
const mod = await import(https://unpkg.com/web-vitals?module)
const { getCLS, getFID, getLCP, getFCP, getTTFB, getINP } = mod
// Helper para enviar al servidor
function sendMetricToServer(metric) {
const payload = {
name: metric.name,
value: metric.value,
delta: metric.delta,
id: metric.id, // id único por métrica
navigationType: (performance.getEntriesByType(navigation)[0] {}).type null,
url: location.pathname,
ua: navigator.userAgent,
ts: Date.now()
}
const body = JSON.stringify(payload)
const endpoint = (window.WP_WEBVITALS window.WP_WEBVITALS.endpoint) /wp-json/webvitals/v1/collect
const nonce = window.WP_WEBVITALS window.WP_WEBVITALS.nonce
// Preferir sendBeacon (no bloquea unload)
if (navigator.sendBeacon) {
const blob = new Blob([body], { type: application/json })
// sendBeacon ignora cabeceras personalizadas como nonce si necesitas nonce, usa fetch
const ok = navigator.sendBeacon(endpoint, blob)
if (ok) return
}
// Fallback: fetch con keepalive
const headers = { Content-Type: application/json }
if (nonce) headers[X-WP-Nonce] = nonce
try {
fetch(endpoint, {
method: POST,
credentials: same-origin,
headers,
body,
keepalive: true
}).catch(() => {/ silencioso /})
} catch (e) {
// En algunos entornos keepalive puede lanzar lo ignoramos
}
}
// Registramos observadores para cada métrica
const onMetric = (metric) => {
// Aquí puedes filtrar, agrupar, o debugar
sendMetricToServer(metric)
}
getCLS(onMetric)
// getFID puede estar deprecado en favor de getINP recoger ambos si quieres compatibilidad
if (typeof getINP === function) {
getINP(onMetric)
} else {
getFID(onMetric)
}
getLCP(onMetric)
getFCP(onMetric)
getTTFB(onMetric)
})()
Crear el endpoint REST en WordPress (PHP)
A continuación hay un ejemplo de plugin mínimo que registra la ruta REST, realiza validaciones y guarda las métricas en una tabla personalizada. El ejemplo incluye creación de tabla usando dbDelta en activation hook.
prefix . web_vitals
charset_collate = wpdb->get_charset_collate()
require_once( ABSPATH . wp-admin/includes/upgrade.php )
sql = CREATE TABLE table (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
value DOUBLE NOT NULL,
delta DOUBLE DEFAULT 0,
metric_id VARCHAR(100) DEFAULT ,
url VARCHAR(255) DEFAULT ,
ua TEXT,
navigation_type VARCHAR(50),
ts BIGINT UNSIGNED,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) charset_collate
dbDelta( sql )
}
add_action( rest_api_init, function () {
register_rest_route( webvitals/v1, /collect, array(
methods => POST,
// Se deja pública porque llegan usuarios anónimos validar los datos en callback.
permission_callback => __return_true,
callback => wvc_handle_collect,
) )
} )
function wvc_handle_collect( WP_REST_Request request ) {
global wpdb
table = wpdb->prefix . web_vitals
content_type = request->get_header(content-type) ?:
// Aceptamos JSON
if ( strpos( content_type, application/json ) === false ) {
return new WP_REST_Response( array( error => Invalid content type ), 400 )
}
data = json_decode( request->get_body(), true )
if ( ! is_array( data ) ) {
return new WP_REST_Response( array( error => Invalid payload ), 400 )
}
// Validaciones básicas
name = isset(data[name]) ? sanitize_text_field( data[name] ) :
value = isset(data[value]) ? floatval( data[value] ) : null
delta = isset(data[delta]) ? floatval( data[delta] ) : 0
metric_id = isset(data[id]) ? sanitize_text_field( data[id] ) :
url = isset(data[url]) ? sanitize_text_field( data[url] ) :
ua = isset(data[ua]) ? sanitize_textarea_field( data[ua] ) :
navigation_type = isset(data[navigationType]) ? sanitize_text_field( data[navigationType] ) :
ts = isset(data[ts]) ? intval( data[ts] ) : time() 1000
if ( empty(name) value === null ) {
return new WP_REST_Response( array( error => Missing fields ), 400 )
}
// Protecciones simples: limitar longitud de UA y URL
if ( strlen( ua ) > 2000 ) ua = substr( ua, 0, 2000 )
if ( strlen( url ) > 255 ) url = substr( url, 0, 255 )
// Inserción segura
inserted = wpdb->insert(
table,
array(
name => name,
value => value,
delta => delta,
metric_id => metric_id,
url => url,
ua => ua,
navigation_type => navigation_type,
ts => ts,
),
array( %s, %f, %f, %s, %s, %s, %s, %d )
)
if ( inserted === false ) {
return new WP_REST_Response( array( error => DB error ), 500 )
}
return new WP_REST_Response( array( success => true ), 201 )
}
?>
Encolar el script en WordPress y pasar endpoint/nonce
Con el plugin anterior, puedes encolar un script frontend que importe la librería y utilice el endpoint. A continuación un ejemplo de cómo inyectar un objeto JS con la URL del endpoint y un nonce opcional (aunque el nonce normalmente solo tiene sentido para usuarios autenticados).
// Enqueue script (colocar en plugin o functions.php)
add_action( wp_enqueue_scripts, wvc_enqueue_scripts )
function wvc_enqueue_scripts() {
// No encolamos web-vitals directamente porque lo importamos dinámicamente en el script
wp_enqueue_script( wvc-client, plugin_dir_url(__FILE__) . webvitals-client.js, array(), 1.0, true )
// Pasamos datos
wp_localize_script( wvc-client, WP_WEBVITALS, array(
endpoint => esc_url_raw( rest_url( webvitals/v1/collect ) ),
nonce => wp_create_nonce( wp_rest ), // opcional
) )
}
Almacenamiento: tabla vs post type vs servicio externo
Opciones comunes:
- Tabla personalizada: eficiente para almacenamiento en crudo, consultas agregadas y control del esquema. Requiere manejo de limpieza y particionado si hay alto volumen.
- Custom Post Type: fácil de explorar desde la interfaz de WP, pero no es ideal para alto volumen ni consultas complejas.
- Servicio externo (BigQuery, Elasticsearch, Data Warehouse): recomendado para análisis a gran escala. Envía métricas en batch desde el servidor a esos servicios.
Seguridad, privacidad y rendimiento
- Privacidad: Evita almacenar información personal identificable (PII). Nunca guardes correos ni tokens que identifiquen usuarios sin su consentimiento.
- Validación: Sanitiza y valida todo lo recibido. En el ejemplo se usa sanitize_text_field y floatval.
- Rate limiting: Implementa mecanismos para evitar spam o abuso (transients por IP, limitación por user agent, bloqueo si más de X req/s).
- Uso de sendBeacon / keepalive: para no afectar navegación/experiencia del usuario.
- CORS y Referer: puedes verificar el referer o usar políticas de CORS para restringir envíos desde otros orígenes.
- Alto volumen: agrupa envíos en el cliente (batch) o en el servidor añade un buffer antes de persistir para reducir I/O.
Depuración y pruebas
- Prueba en distintos navegadores y dispositivos reales (no solo Lighthouse).
- Usa los devtools (Network) para verificar los POST y el formato JSON.
- Verifica el contenido de la tabla o destino y añade índices en columnas que uses para agregaciones (p. ej. name, ts).
Variantes y mejoras posibles
- Enviar métricas en batch desde cliente cada N eventos o al unload para reducir requests.
- Filtrar métricas para no almacenar valores triviales (p. ej. CLS = 0). Aunque a veces conviene guardar 0 para ver distribución.
- Agregar etiquetas adicionales: country, device type, connection type (navigator.connection), versión del sitio, A/B test id.
- Integrar con sistema de alertas: enviar un evento cuando LCP medio supera X ms para acciones automáticas.
Ejemplo de consulta rápida para analizar métricas (SQL)
-- Valor medio por métrica en las últimas 24h SELECT name, AVG(value) as avg_value, COUNT() as samples FROM wp_web_vitals WHERE ts >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 1 DAY)) 1000 GROUP BY name
Buenas prácticas finales
- Empieza con una implementación mínima: recoger LCP, CLS e INP/LCP y comprobar que el rendimiento de recolección no impacta al usuario.
- Monitoriza la tasa de eventos y añade retención/limpieza automática si la base de datos crece rápido.
- Si hace falta alto volumen, considera enviar desde el servidor a BigQuery o similar desde procesos cron/batch.
- Documenta el formato de evento y versiona la API si cambias los campos.
Resumen
Medir Web Vitals en el navegador y enviarlas a la REST API de WordPress es una forma práctica de obtener métricas de experiencia reales de usuarios. La librería web-vitals facilita la captura en WordPress conviene exponer una ruta REST pública pero segura con validación, sanitización y límites. Para producción considera almacenamiento escalable, batching y políticas de retención.
Enlaces útiles
|
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |
