32blogby Studio Mitsu

Crea un ranking de posts populares con la API de GA4 en Next.js

Aprende a llamar la API de datos de Google Analytics 4 desde Next.js App Router para construir un ranking de posts populares basado en páginas vistas con caché ISR, paso a paso.

by omitsu12 min read
Next.jsGoogle AnalyticsGA4APIISR
Contenido

Para crear un ranking de posts populares en Next.js, llama a la API de datos de GA4 con @google-analytics/data, obtén screenPageViews desde un Server Component, y cachea el resultado con ISR. No necesitas herramientas de pago — la API es gratuita.

Este artículo te guía por todo el proceso desde cero: crear una cuenta de servicio de GCP, llamar a la API, construir el componente, cachear con ISR, y desplegar en Vercel.

¿Qué es la API de datos de GA4?

La API de datos de GA4 te permite acceder programáticamente a los mismos datos que ves en el panel de Google Analytics. En lugar de abrir un navegador, obtienes reportes desde código.

El método que usamos es runReport, que genera reportes como "qué páginas tuvieron más vistas." Es el equivalente en API de ver el reporte "Páginas y pantallas" en GA4.

Es gratis. Para propiedades GA4 Standard (tier gratuito), la API de datos no tiene costo adicional. Hay una cuota de 200,000 tokens por día, pero una función de ranking para un blog apenas usará una fracción de eso.

Crear una cuenta de servicio de GCP

Para usar la API de datos de GA4, necesitas una cuenta de servicio de Google Cloud Platform (GCP) con acceso a tu propiedad de GA4. Esta es una configuración que solo se hace una vez.

Preparar el proyecto de GCP

  1. Inicia sesión en la Google Cloud Console
  2. Usa un proyecto existente o crea uno nuevo
  3. Ve a "APIs y servicios" → "Biblioteca" en el menú de la izquierda
  4. Busca "Google Analytics Data API" y haz clic en "Habilitar"

Crear la cuenta de servicio

  1. Ve a "APIs y servicios" → "Credenciales" en el menú de la izquierda
  2. Haz clic en "+ Crear credenciales" → "Cuenta de servicio"
  3. Ingresa un nombre (por ejemplo, ga4-reporting)
  4. Haz clic en "Crear y continuar." Puedes omitir el paso de asignación de rol
  5. Haz clic en la cuenta de servicio que acabas de crear, luego abre la pestaña "Claves"
  6. Haz clic en "Agregar clave" → "Crear nueva clave" → selecciona "JSON" y créala

Se descargará un archivo JSON. Se ve así:

json
{
  "type": "service_account",
  "project_id": "your-project-id",
  "private_key_id": "...",
  "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
  "client_email": "ga4-reporting@your-project-id.iam.gserviceaccount.com",
  "client_id": "...",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token"
}

Los dos campos que necesitas son client_email y private_key.

Otorgar acceso a tu propiedad de GA4

  1. Abre Google Analytics
  2. Ve a Administración (icono de engranaje, abajo a la izquierda) → Propiedad → "Gestión de acceso a la propiedad"
  3. Haz clic en "+" → "Agregar usuarios"
  4. Pega el valor client_email del JSON descargado
  5. Establece el rol como "Lector" (acceso de solo lectura es suficiente)
  6. Haz clic en "Agregar"

Encontrar tu ID de propiedad de GA4

En el panel de administración de GA4, ve a "Configuración de la propiedad" → "Detalles de la propiedad." El ID de propiedad (solo números, por ejemplo, 123456789) se muestra ahí. Necesitarás este valor más adelante.

Obtener datos de GA4 desde Next.js

Con GCP configurado, es hora de llamar a la API desde tu proyecto de Next.js.

Instalar el paquete

bash
npm install @google-analytics/data

Esto instala el cliente oficial de Node.js para la API de datos de GA4 (v5.2.1 al momento de escribir esto).

Configurar variables de entorno

Agrega lo siguiente a .env.local:

bash
GA_PROPERTY_ID=123456789
GA_CLIENT_EMAIL=ga4-reporting@your-project-id.iam.gserviceaccount.com
GA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEv...(truncated)...\n-----END PRIVATE KEY-----\n"

Para GA_PRIVATE_KEY, pega el valor private_key del archivo JSON tal cual. Envuélvelo en comillas dobles. Los caracteres \n son secuencias de escape de nueva línea — pégalos tal cual. El código usa .replace(/\\n/g, '\n') para convertirlos a nuevas líneas reales en tiempo de ejecución.

Crear el cliente de GA4

Crea lib/ga4.ts:

typescript
import { BetaAnalyticsDataClient } from "@google-analytics/data";

function getCredentials() {
  // Base64-encoded JSON key (recommended for Vercel)
  if (process.env.GA_SERVICE_KEY_BASE64) {
    return JSON.parse(
      Buffer.from(process.env.GA_SERVICE_KEY_BASE64, "base64").toString()
    );
  }
  // Fallback for local development
  return {
    client_email: process.env.GA_CLIENT_EMAIL,
    private_key: process.env.GA_PRIVATE_KEY?.replace(/\\n/g, "\n"),
  };
}

const client = new BetaAnalyticsDataClient({
  credentials: getCredentials(),
});

const propertyId = process.env.GA_PROPERTY_ID;

export type PageViewEntry = {
  path: string;
  views: number;
};

export async function getTopPages(
  limit: number = 10,
  days: number = 30
): Promise<PageViewEntry[]> {
  const [response] = await client.runReport({
    property: `properties/${propertyId}`,
    dimensions: [{ name: "pagePath" }],
    metrics: [{ name: "screenPageViews" }],
    dateRanges: [{ startDate: `${days}daysAgo`, endDate: "today" }],
    orderBys: [
      {
        metric: { metricName: "screenPageViews" },
        desc: true,
      },
    ],
    limit,
  });

  if (!response.rows) return [];

  return response.rows.map((row) => ({
    path: row.dimensionValues?.[0]?.value ?? "",
    views: parseInt(row.metricValues?.[0]?.value ?? "0", 10),
  }));
}

getCredentials() soporta dos métodos de autenticación. Si GA_SERVICE_KEY_BASE64 está definido, decodifica en base64 la clave JSON completa. De lo contrario, recurre a variables de entorno individuales. El enfoque base64 es el recomendado para Vercel (se explica más adelante).

Esto es lo que hace cada parámetro de runReport:

  • dimensions: [{ name: "pagePath" }] — agrupa resultados por ruta de URL
  • metrics: [{ name: "screenPageViews" }] — cuenta páginas vistas
  • dateRanges — cubre los últimos 30 días
  • orderBys — ordena por vistas en orden descendente (más vistas primero)
  • limit — devuelve solo los 10 mejores resultados

Construir un componente de posts populares

Ahora construye un Server Component para mostrar los datos. Con los Server Components de App Router, puedes llamar funciones async directamente dentro del componente. No se necesita una API route separada.

typescript
import Link from "next/link";
import { getTopPages } from "@/lib/ga4";

export async function PopularPosts() {
  const pages = await getTopPages(10, 30);

  // Filtrar páginas que no son artículos (inicio, páginas de categoría, etc.)
  const articles = pages.filter(
    (p) => p.path.match(/^\/[a-z]{2}\/[^/]+\/[^/]+$/) && p.views > 0
  );

  if (articles.length === 0) return null;

  return (
    <section>
      <h2 className="text-xl font-bold mb-4">Popular Posts</h2>
      <ol className="space-y-3">
        {articles.slice(0, 5).map((entry, i) => (
          <li key={entry.path} className="flex items-baseline gap-3">
            <span className="text-sm font-mono text-muted">{i + 1}</span>
            <Link href={entry.path} className="text-sm hover:underline">
              {entry.path}
            </Link>
            <span className="text-xs text-muted ml-auto">
              {entry.views.toLocaleString()} PV
            </span>
          </li>
        ))}
      </ol>
    </section>
  );
}

Este componente muestra pagePath (rutas de URL) directamente. Para mostrar títulos de artículos en su lugar, empareja los slugs con los metadatos de tus artículos:

typescript
import { getAllArticles } from "@/lib/content";
import { getTopPages } from "@/lib/ga4";

export async function getPopularArticles(locale: string, limit: number = 5) {
  const [pages, articles] = await Promise.all([
    getTopPages(50, 30),
    Promise.resolve(getAllArticles(locale)),
  ]);

  const viewMap = new Map(pages.map((p) => [p.path, p.views]));

  return articles
    .map((article) => ({
      ...article,
      views: viewMap.get(`/${locale}/${article.category}/${article.slug}`) ?? 0,
    }))
    .filter((a) => a.views > 0)
    .sort((a, b) => b.views - a.views)
    .slice(0, limit);
}

Cachear llamadas a la API con ISR

Llamar a la API de GA4 en cada solicitud de página desperdiciaría cuota y ralentizaría las respuestas. ISR (Incremental Static Regeneration) te permite servir HTML cacheado mientras revalidas los datos en segundo plano.

Establece revalidate en la página que muestra el ranking:

typescript
// app/[locale]/popular/page.tsx
import { getPopularArticles } from "@/lib/ga4";

export const revalidate = 3600; // regenerate every hour

export default async function PopularPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  const articles = await getPopularArticles(locale);

  return (
    <main>
      <h1>Popular Articles</h1>
      <ul>
        {articles.map((article) => (
          <li key={article.slug}>
            <a href={`/${locale}/${article.category}/${article.slug}`}>
              {article.title}
            </a>
            <span>{article.views.toLocaleString()} views</span>
          </li>
        ))}
      </ul>
    </main>
  );
}

revalidate = 3600 significa "cachea esta página hasta por 1 hora." Después de que el caché expira, la primera solicitud entrante activa una regeneración en segundo plano, y las solicitudes posteriores obtienen el HTML fresco.

Cachear a nivel de función

Si quieres cachear solo la llamada a la API de GA4 (no la página completa), usa la directiva use cache. Asegúrate de que cacheComponents: true esté configurado en next.config.ts:

typescript
import { cacheLife } from "next/cache";
import { getTopPages } from "@/lib/ga4";

export async function getCachedTopPages(limit: number, days: number) {
  "use cache";
  cacheLife("hours");
  return getTopPages(limit, days);
}

Esto permite que múltiples páginas compartan los mismos datos de ranking cacheados mientras mantiene las llamadas a la API a una cada pocas horas.

Desplegar en Vercel

Localmente, almacenaste las credenciales en .env.local usando variables separadas. En Vercel, los caracteres de nueva línea (\n) en private_key pueden causar errores de parseo como DECODER routines::unsupported.

El enfoque recomendado es codificar en base64 el archivo JSON completo de la clave en una sola variable de entorno. Esto evita completamente los problemas con nuevas líneas.

Codificar la clave en base64

Convierte el archivo JSON de la clave descargado a base64:

bash
base64 -w 0 your-service-account-key.json

Copia la cadena de salida.

Configurar variables de entorno

En el panel de Vercel, ve a Settings → Environment Variables y agrega:

NombreValor
GA_PROPERTY_IDTu ID de propiedad de GA4 (solo números)
GA_SERVICE_KEY_BASE64La cadena codificada en base64 de arriba

No necesitas envolver los valores en comillas en Vercel. Pégalos tal cual.

Desplegar y verificar

bash
git add .
git commit -m "feat: add GA4 popular posts ranking"
git push

Vercel construirá y desplegará automáticamente. Visita la página de ranking después del despliegue. Si los datos aparecen, ya terminaste.

Solución de problemas

Si las cosas no funcionan, verifica estos errores comunes:

  • DECODER routines::unsupported: Las nuevas líneas de private_key no se parsearon correctamente. Cambia al enfoque de codificación base64 descrito arriba
  • PERMISSION_DENIED: Google Analytics Data API has not been used in project...: La API de datos no está habilitada en tu proyecto de GCP. Ve a "APIs y servicios" → "Biblioteca" y habilítala
  • PERMISSION_DENIED: User does not have sufficient permissions...: El email de la cuenta de servicio no fue agregado como "Lector" en tu propiedad de GA4. Verifica la gestión de acceso a la propiedad en el admin de GA4
  • Las variables de entorno no surten efecto: Después de cambiar variables de entorno en Vercel, necesitas redesplegar. Ve a Deployments y haz clic en "Redeploy" en el último despliegue

Preguntas frecuentes

¿La API de datos de GA4 es gratis?

Sí. Para propiedades GA4 Standard (tier gratuito), la API no tiene costo adicional. La cuota diaria es de 200,000 tokens por propiedad. Un ranking de blog que se ejecuta una vez por hora consume una fracción mínima.

¿Cuál es la diferencia entre screenPageViews y pageviews?

screenPageViews es el nombre de la métrica en GA4. Universal Analytics usaba pageviews, pero GA4 unificó el tracking web y de apps bajo una sola métrica. Si usas pageviews en la llamada a la API, obtendrás un error.

¿Con qué frecuencia debo actualizar el ranking?

Para la mayoría de los blogs, una vez cada 1 a 24 horas es más que suficiente. Usa revalidate = 3600 para actualizaciones cada hora o revalidate = 86400 para una vez al día. Actualizar con más frecuencia desperdicia cuota de la API sin cambios significativos en el ranking.

¿Puedo usar esto con Pages Router?

Sí. En lugar de un Server Component, llama a getTopPages() dentro de getStaticProps con un valor de revalidate. El código del cliente de GA4 (lib/ga4.ts) funciona igual en ambos routers.

¿Qué pasa si excedo la cuota de la API de GA4?

La API devuelve un error 429 RESOURCE_EXHAUSTED. Gracias a ISR, tu página de ranking seguirá mostrando la última versión cacheada, así que los visitantes no verán un error. La cuota se restablece diariamente a medianoche hora del Pacífico.

¿Necesito Analytics 360 (GA4 de pago)?

No. El tier gratuito GA4 Standard incluye acceso completo a la API de datos. Analytics 360 ofrece cuotas más altas (250,000 tokens/día), pero para blogs no es necesario.

¿Por qué mi ranking muestra rutas URL en vez de títulos?

El componente básico PopularPosts muestra pagePath directamente. Para mostrar títulos de artículos, usa la función getPopularArticles mostrada en este artículo, que empareja las rutas de GA4 con los metadatos de tu contenido.

Conclusión

Lo que construimos:

  • Cuenta de servicio de GCP: Creamos una cuenta de servicio en Google Cloud Console y otorgamos acceso de Lector a la propiedad de GA4
  • API de datos de GA4: Usamos BetaAnalyticsDataClient de @google-analytics/data para obtener screenPageViews
  • Server Component: Llamamos a la API directamente desde un Server Component de App Router — sin necesidad de API route
  • Caché ISR: Usamos revalidate para limitar las llamadas a la API y conservar cuota
  • Despliegue en Vercel: Codificamos en base64 la clave JSON en una sola variable de entorno, evitando problemas de parseo con nuevas líneas

La API de datos de GA4 es gratuita. La cuota de 200,000 tokens por día es más que suficiente para un blog. Combinada con ISR, obtienes un ranking casi en tiempo real a costo cero.

Recursos oficiales:

Artículos relacionados: