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 🙂 |