Enter

Node / 9 min de lectura

Cómo Simular PageSpeed Insights con Lighthouse

vector illustration of a mobile phone testing its internet speed

¿Alguna vez te encontraste con que no podías usar PageSpeed Insights para probar tus sitios al trabajar con builds locales o protecciones de autenticación básica, como en los sitios transferibles de WPEngine y otros entornos protegidos? Esta limitación requiere una solución que te permita aproximar los resultados de PageSpeed Insights con una herramienta que pueda acceder a entornos protegidos.

No poder probar el rendimiento, SEO, accesibilidad y las mejores prácticas limita tu capacidad de optimizar y monitorear tu sitio de manera efectiva durante el desarrollo.

Para solucionar esto, desarrollamos una solución utilizando la herramienta de código abierto de Google, Lighthouse, para realizar auditorías de rendimiento locales detalladas bajo condiciones similares a las de PageSpeed Insights.

Con la ayuda de Node.js y Express, vamos a utilizar Lighthouse para aproximar las auditorías que ofrece PageSpeed Insights y ayudarte a optimizar el rendimiento de tu sitio en cada paso del desarrollo.

¿Por qué necesitamos soluciones de prueba de rendimiento local?

Usar Lighthouse de manera local permite a los desarrolladores personalizar el entorno de pruebas según sus necesidades específicas, lo que incluye manejar URLs protegidas por características de autenticación básica. Dado que PageSpeed Insights no puede auditar sitios protegidos, los desarrolladores que quieren optimizar el rendimiento antes del lanzamiento necesitan idear una solución local que ofrezca beneficios similares.

Cómo configurar un entorno de pruebas local con Node.js y Express

Nuestra solución consiste en configurar un servidor Node.js usando Express para gestionar las solicitudes de pruebas de rendimiento.

Esta configuración te permite aproximar los resultados que obtendrías de PageSpeed Insights y ofrece la flexibilidad de manejar escenarios que la herramienta en línea o el lighthouse del navegador no soportan directamente.

Paso 1: Instalar Node.js y los paquetes requeridos

Asegúrate de tener Node.js instalado en tu sistema. Luego, crea un nuevo directorio para tu proyecto e instala los paquetes necesarios:

mkdir lighthouse-server
cd lighthouse-server
npm init -y
npm install express lighthouse chrome-launcher

Paso 2: Configurar el servidor Express

Creá un archivo llamado server.js y configurá tu servidor Express con el siguiente código:

const express = require('express');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const path = require('path');

const PORT = 3000;
let lighthouse;
let chromeLauncher;

(async () => {
    lighthouse = (await import('lighthouse')).default;
    chromeLauncher = await import('chrome-launcher');
})();

const app = express();
app.use(express.json());

const resultsDirectory = path.join(__dirname, 'results');
if (!fs.existsSync(resultsDirectory)){
    fs.mkdirSync(resultsDirectory, { recursive: true });
}

const getSafeFilename = (url) => {
    return encodeURIComponent(url).replace(/[%]/g, '');
}

const loadHistory = (url) => {
    const filePath = path.join(resultsDirectory, `${getSafeFilename(url)}.json`);
    if (fs.existsSync(filePath)) {
        return JSON.parse(fs.readFileSync(filePath, 'utf8'));
    }
    return [];
};

const saveHistory = (url, data) => {
    const filePath = path.join(resultsDirectory, `${getSafeFilename(url)}.json`);
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
};

const runLighthouse = async (url, config, outputFilename) => {
    const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']});
    const options = {
        logLevel: 'info',
        output: 'html',
        onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
        port: chrome.port,
        ...config,
    };

    try {
        const runnerResult = await lighthouse(url, options);
        fs.writeFileSync(outputFilename, runnerResult.report);
        return {
            scores: {
                performance: runnerResult.lhr.categories.performance.score * 100,
                accessibility: runnerResult.lhr.categories.accessibility.score * 100,
                bestPractices: runnerResult.lhr.categories['best-practices'].score * 100,
                seo: runnerResult.lhr.categories.seo.score * 100
            },
            reportPath: outputFilename
        };
    } finally {
        await chrome.kill();
    }
};

app.post('/pagespeed-test', async (req, res) => {
    const { url } = req.body;
    if (!url) {
        return res.status(400).json({ error: 'Se requiere una URL' });
    }

    const id = uuidv4();
    const mobileConfig = {
        throttling: {
            rttMs: 150, // tiempo de ida y vuelta en milisegundos
            throughputKbps: 1638.4, // ancho de banda en kilobits por segundo
            cpuSlowdownMultiplier: 4 // cuánto se asume que es más lento el CPU
        },
        emulatedFormFactor: 'mobile',
        channel: 'cli'
    };
    
    const desktopConfig = {
        throttling: {
            rttMs: 40, // tiempo de ida y vuelta en milisegundos
            throughputKbps: 5000, // ancho de banda en kilobits por segundo
            cpuSlowdownMultiplier: 1 // sin desaceleración
        },
        emulatedFormFactor: 'desktop',
        channel: 'cli'
    };

    try {
        const mobileResults = await runLighthouse(url, mobileConfig, `reports/${id}_mobile.html`);
        const desktopResults = await runLighthouse(url, desktopConfig, `reports/${id}_desktop.html`);

        const result = {
            url,
            timestamp: new Date().toISOString(),
            mobile: {
                scores: mobileResults.scores,
                reportUrl: `http://localhost:${PORT}/reports/${id}_mobile.html`
            },
            desktop: {
                scores: desktopResults.scores,
                reportUrl: `http://localhost:${PORT}/reports/${id}_desktop.html`
            }
        };

        // Cargar el historial existente sin agregar el resultado actual
        const history = loadHistory(url);

        // Agregar el resultado de la prueba actual al historial para futuras solicitudes
        const updatedHistory = [...history, result];
        saveHistory(url, updatedHistory);

        // Retornar el resultado actual y el historial (excluyendo la prueba actual)
        res.json({
            currentResult: result,
            history: history
        });
    } catch (error) {
        res.status(500).json({ error: 'Falló al ejecutar las pruebas de Lighthouse' });
    }
});

app.use('/reports', express.static('reports'));
app.listen(PORT, () => {
    console.log(`Servidor corriendo en :${PORT}`);
});

Configuraciones

Configuraciones Móviles

Throttling. Las pruebas móviles se configuraron con un throttling significativo en la red y el CPU. Este throttling deliberado simula las condiciones de un dispositivo móvil accediendo al sitio a través de una red típica 3G o 4G, que es más lenta que la mayoría de las conexiones por cable o Wi-Fi.

El throttling incluye:

  • RTT (Round Trip Time). Latencia aumentada para imitar las condiciones de red.
  • Throughput. Ancho de banda limitado para reflejar las redes móviles más lentas.
  • Desaceleración del CPU. Simula la menor potencia de procesamiento de los dispositivos móviles en comparación con las computadoras de escritorio.

Factor de Forma Emulado. La prueba se llevó a cabo en un entorno móvil emulado. Esto significa que el sitio se prueba tal como aparecería en un dispositivo móvil, teniendo en cuenta factores como el tamaño de la pantalla, las interfaces táctiles y los navegadores móviles.

Configuraciones de Escritorio

Throttling. Las configuraciones de throttling para escritorio son menos severas que las de móvil porque los dispositivos de escritorio generalmente tienen conexiones de red más rápidas y confiables y CPUs más potentes. El throttling para escritorio podría incluir:

  • RTT mínimo. Representa la menor latencia que típicamente se experimenta en desktop.
  • Mayor rendimiento. Refleja las conexiones a internet más rápidas disponibles para dispositivos de escritorio.

Factor de forma emulado. Sin emulación móvil. Probamos el sitio tal como aparecería en un navegador de escritorio, utilizando todo el espacio de pantalla y asumiendo métodos de entrada como un mouse y un teclado.

¿Por qué usamos estas configuraciones?

La razón detrás de estas configuraciones específicas fue reflejar lo más fielmente posible la experiencia promedio del usuario en cada tipo de dispositivo. Al emular entornos móviles más lentos y restringidos, pudiste entender cómo se desempeñará el sitio bajo las condiciones móviles más comunes.

Por otro lado, probar con configuraciones de escritorio permitió evaluar bajo condiciones óptimas con CPUs más rápidas y velocidades de red superiores.

Este enfoque ayudó a identificar cuellos de botella potenciales y problemas de rendimiento que podrían no ser evidentes en un entorno, pero que son críticos en otro. Por ejemplo, imágenes pesadas y scripts que cargan sin problemas en una conexión rápida de escritorio podrían causar demoras significativas en una red móvil.

Paso 3: Ejecutando y probando tu servidor

Ejecuta tu servidor usando:

node server.js
Una interfaz de terminal con código para ejecutar un servidor Node.js

Prueba la funcionalidad enviando una solicitud POST a http://localhost:3000/pagespeed-test con un cuerpo JSON que contenga una URL con autenticación básica incrustada.

Usando Visual Studio Code para enviar una solicitud POST que contiene autenticación básica

Si tu sitio tiene autenticación básica, agregá las credenciales así https://user:password@example.com.

Descripción Revisada de la Respuesta

Luego de realizar una prueba con Lighthouse a través de nuestra configuración de servidor, la respuesta contiene resultados detallados de rendimiento para configuraciones móviles y de escritorio, además de enlaces a los informes HTML completos. Esto es lo que incluye la respuesta:

  1. currentResult. Esta sección contiene los últimos resultados de la prueba, con un desglose tanto para móviles como para escritorio:
    • Puntuaciones de Móvil y Escritorio: Para cada uno, la respuesta incluye puntuaciones de rendimiento, accesibilidad, mejores prácticas y SEO, cada una escalada a un porcentaje.
    • ReportUrl: Enlaces directos a los informes HTML para las pruebas móviles y de escritorio.
  2. history. Esta parte de la respuesta incluye una lista de resultados de pruebas anteriores para la misma URL, siguiendo la misma estructura que currentResult pero excluyendo la última prueba para ofrecer una vista cronológica de los rendimientos pasados.

Ejemplo de la Respuesta

Dada la estructura descrita, aquí tenés una respuesta JSON típica que podrías recibir después de ejecutar una prueba:

{
  "currentResult": {
    "url": "<https://example.com>",
    "timestamp": "2024-04-24T15:46:01.239Z",
    "mobile": {
      "scores": {
        "performance": 99,
        "accessibility": 88,
        "bestPractices": 100,
        "seo": 90
      },
      "reportUrl": "<http://localhost:3000/reports/ac531402-b89e-4bb8-9e07-db3496c3847b_mobile.html>"
    },
    "desktop": {
      "scores": {
        "performance": 100,
        "accessibility": 88,
        "bestPractices": 100,
        "seo": 90
      },
      "reportUrl": "<http://localhost:3000/reports/ac531402-b89e-4bb8-9e07-db3496c3847b_desktop.html>"
    }
  },
  "history": [
    {
      "url": "<https://example.com>",
      "timestamp": "2024-04-24T14:53:54.034Z",
      "mobile": {
        "scores": {
          "performance": 100,
          "accessibility": 88,
          "bestPractices": 100,
          "seo": 90
        },
        "reportUrl": "<http://localhost:3000/reports/55d65b3b-024f-4acd-92f0-e1928ddd3b95_mobile.html>"
      },
      "desktop": {
        "scores": {
          "performance": 100,
          "accessibility": 88,
          "bestPractices": 100,
          "seo": 90
        },
        "reportUrl": "<http://localhost:3000/reports/55d65b3b-024f-4acd-92f0-e1928ddd3b95_desktop.html>"
      }
    },
    {
      "url": "<https://example.com>",
      "timestamp": "2024-04-24T14:55:07.385Z",
      "mobile": {
        "scores": {
          "performance": 100,
          "accessibility": 88,
          "bestPractices": 100,
          "seo": 90
        },
        "reportUrl": "<http://localhost:3000/reports/d1b46f09-3f85-4e7f-98ee-2b2ec0b97afb_mobile.html>"
      },
      "desktop": {
        "scores": {
          "performance": 100,
          "accessibility": 88,
          "bestPractices": 100,
          "seo": 90
        },
        "reportUrl": "<http://localhost:3000/reports/d1b46f09-3f85-4e7f-98ee-2b2ec0b97afb_desktop.html>"
      }
    }
  ]
}

Explicación del Ejemplo

  • currentResult: Muestra los puntajes de la prueba más reciente. Proporciona un desglose detallado del rendimiento, accesibilidad, mejores prácticas y puntajes de SEO tanto para configuraciones móviles como de escritorio, junto con URLs a sus respectivos informes en HTML.
  • history: Contiene un arreglo de resultados pasados para la misma URL. Cada entrada refleja la estructura de currentResult, documentando la evolución de las métricas del sitio a lo largo del tiempo. Esto es valioso para rastrear mejoras o regresiones después de actualizaciones u optimizaciones.

Este formato de respuesta integral permite a los desarrolladores obtener rápidamente una visión general de las métricas de rendimiento más recientes del sitio, mientras mantiene un registro histórico para el análisis de tendencias y el seguimiento del rendimiento a largo plazo.

Prueba de Rendimiento en Entornos Protegidos Con Lighthouse

Al aprovechar el poder de Lighthouse, Node.js y Express, pudimos crear un entorno de pruebas robusto que aproxima las capacidades de Google PageSpeed Insights, mientras ofrece características adicionales adaptadas a las necesidades de los desarrolladores que trabajan en entornos de desarrollo seguros o restringidos.

Este método mejora tu capacidad para optimizar el rendimiento del sitio y asegura que tu experiencia de pruebas sea lo más cercana posible a tu entorno en vivo.

Si encontraste útil esta publicación, lee nuestro blog y recursos para desarrolladores para más información y guías!