Rastrear tus páginas publicadas reúne todos los títulos y descripciones en un solo informe, para que las páginas estáticas obsoletas y los errores en las plantillas del CMS salgan a la luz antes de que los usuarios los vean.
Los metadatos pueden fallar de dos formas. Una página estática puede quedar obsoleta cuando cambia la oferta. Una plantilla de Colección puede fallar una vez y replicarse en todas partes: una descripción codificada de forma rígida se repite en cada elemento que genera, y aun así, todo parece estar bien en el Diseñador.
En esta guía, crearemos una pequeña aplicación de Next.js en Webflow Cloud con un Route Handler. Esta obtendrá las URLs que le proporciones, leerá el título, la descripción, las etiquetas canónicas y las de Open Graph del HTML publicado, y devolverá los problemas en formato JSON: faltantes, demasiado largos, duplicados o incorrectos.
¿Qué necesitas para auditar los metadatos en Webflow?
Necesitas un sitio de Webflow con Webflow Cloud, un proyecto de Next.js 15+ gestionado con npm, un repositorio de Git, una lista de URLs, un secreto de auditoría y acceso al Diseñador:
- Sitio de Webflow: Cualquier plan, desde el Starter gratuito en adelante, incluye Webflow Cloud; para montar un dominio personalizado se requiere el plan Premium o superior.
- Next.js 15 o superior: Este es el requisito mínimo en la página de aplicaciones personalizadas de Webflow Cloud; gestiona las dependencias solo con npm.
- Repositorio de Git: Utiliza un repositorio para el proyecto y conéctalo al crear el entorno de Webflow Cloud.
- Lista de URLs: Incluye todas las páginas estáticas y las URLs de los elementos del CMS que la auditoría de páginas publicadas debe rastrear.
- Secreto de auditoría: Usa una cadena larga y aleatoria que solo conozca quien ejecute la auditoría y que no esté guardada en el repositorio.
- Acceso al Diseñador: Es necesario para corregir lo que el informe detecte en las páginas estáticas y en las páginas de plantilla de colección.
Ahora ya tienes el sitio, el proyecto, el acceso, el inventario de URLs y el secreto necesarios para crear y ejecutar la auditoría.
Un solo comando convierte un mapa del sitio en la lista separada por comas que lee la auditoría:
curl -s https://example.com/sitemap.xml | grep -o '<loc>[^<]*' | sed 's/<loc>//' | paste -sd, -
Envía la lista en el urls array del cuerpo JSON, con un máximo de 100 por solicitud. AUDIT_URLS Solo sirve para listas cortas y fijas: Cloudflare limita cada variable de entorno a 5 KB, lo cual superará cualquier mapa del sitio de más de unas 80 URLs.
Con esto listo, la construcción depende de una decisión: analizar el HTML publicado en lugar de consultar el CMS, para que una única ruta de código cubra tanto las páginas estáticas como los elementos de colección de la misma manera.
6 pasos para auditar y optimizar los metadatos en Webflow Cloud
He creado la auditoría a partir de un pequeño módulo analizador y un Route Handler protegido por un secreto; las correcciones se realizan en el Diseñador y en el CMS. El controlador se ejecuta en Cloudflare Workers, por lo que la directiva edge de Next.js se mantiene fuera del archivo de rutas.
Los primeros cuatro pasos generan un informe funcional de forma local; los dos últimos aplican las correcciones y trasladan la auditoría al sitio en vivo.
1. Prepara la aplicación Next.js con npm
Crea el proyecto desde cero con npm. La documentación de Webflow Cloud indica: "Actualmente, Webflow Cloud solo admite el gestor de paquetes npm", por lo que usar pnpm o yarn generará un archivo de bloqueo que la plataforma no utilizará.
Paso explícitamente en lugar de confiar en el gestor de paquetes predeterminado de la máquina, porque la configuración global de un compañero de equipo es la forma habitual en que un despistado termina en el repositorio.--use-npmpnpm-lock.yaml
Con dos líneas tendrás un proyecto con App Router y TypeScript:
npx create-next-app@15 meta-audit --typescript --app --use-npm
cd meta-audit
Acepta los valores predeterminados para el resto de las preguntas, incluido el alias de importación, del cual dependen los archivos posteriores. Deja sin adaptador ni modo de salida, y omite también; la página de configuración de Webflow Cloud establece: "No necesitas añadir un adaptador, una ruta base ni un modo de salida", y la ruta de montaje se inyecta durante la compilación.@/*next.configbasePath
Ejecuta una vez y abre ; deberías ver la página predeterminada de Next.js, lo que confirma que la cadena de herramientas funciona antes de que exista cualquier código de auditoría.npm run devhttp://localhost:3000
2. Escribe el analizador de metaetiquetas y las reglas de auditoría
Coloca el análisis y las reglas en un archivo de biblioteca, separado del Route Handler, para que las mismas funciones puedan ejecutarse más tarde desde un script o una tarea programada sin pasar por HTTP.
Analizo con expresiones regulares en lugar de una biblioteca DOM porque el controlador se ejecuta en Workers, no en un navegador, y el puñado de etiquetas que necesita una auditoría (title, description, canonical, Open Graph) son lo suficientemente regulares como para que un escáner de atributos las cubra.<meta>
Las constantes contienen mis valores predeterminados de 60 caracteres para el título y 160 para la descripción; cámbialos si tu equipo prefiere otros límites.LIMITS
Implementación del código
Crea con este contenido:lib/meta-audit.ts
export type MetaRecord = {
url: string;
status: number;
title: string | null;
description: string | null;
canonical: string | null;
ogTitle: string | null;
ogDescription: string | null;
};
export type Finding = { url: string; rule: string; detail: string };
export const LIMITS = { titleMax: 60, descriptionMax: 160 };
function decode(value: string): string {
return value
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/&/g, '&') // last, so "&lt;" is not decoded twice
.trim();
}
function attributes(tag: string): Record<string, string> {
const out: Record<string, string> = {};
for (const m of tag.matchAll(/([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
out[m[1].toLowerCase()] = m[2] ?? m[3] ?? '';
}
return out;
}
function metaContent(html: string, key: 'name' | 'property', value: string): string | null {
for (const m of html.matchAll(/<meta\b([^>]*)>/gi)) {
const attrs = attributes(m[1]);
if (attrs[key]?.toLowerCase() === value && attrs.content) return decode(attrs.content);
}
return null;
}
function linkHref(html: string, rel: string): string | null {
for (const m of html.matchAll(/<link\b([^>]*)>/gi)) {
const attrs = attributes(m[1]);
if (attrs.rel?.toLowerCase() === rel && attrs.href) return attrs.href;
}
return null;
}
export function parseMeta(url: string, status: number, html: string): MetaRecord {
const title = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1];
return {
url,
status,
title: title ? decode(title) : null,
description: metaContent(html, 'name', 'description'),
canonical: linkHref(html, 'canonical'),
ogTitle: metaContent(html, 'property', 'og:title'),
ogDescription: metaContent(html, 'property', 'og:description'),
};
}
async function fetchOne(url: string): Promise<MetaRecord> {
try {
const res = await fetch(url, {
headers: { 'user-agent': 'meta-audit/1.0' },
redirect: 'manual',
signal: AbortSignal.timeout(8000), // one slow page cannot eat the 20s request budget
});
const html = res.status === 200 ? await res.text() : '';
return parseMeta(url, res.status, html);
} catch {
return parseMeta(url, 0, '');
}
}
export async function crawl(urls: string[], batchSize = 5): Promise<MetaRecord[]> {
const records: MetaRecord[] = [];
for (let i = 0; i < urls.length; i += batchSize) {
const batch = urls.slice(i, i + batchSize);
records.push(...(await Promise.all(batch.map(fetchOne))));
}
return records;
}
const strip = (u: string) => u.replace(/\/$/, '');
export function audit(records: MetaRecord[]): Finding[] {
const findings: Finding[] = [];
const titles = new Map<string, string>();
const descriptions = new Map<string, string>();
const flag = (url: string, rule: string, detail: string) => {
findings.push({ url, rule, detail });
};
for (const r of records) {
if (r.status === 0) { flag(r.url, 'unreachable', 'Request failed'); continue; }
if (r.status >= 300 && r.status < 400) { flag(r.url, 'redirect', `HTTP ${r.status}`); continue; }
if (r.status !== 200) { flag(r.url, 'unreachable', `HTTP ${r.status}`); continue; }
if (!r.title) {
flag(r.url, 'title-missing', 'No <title> element');
} else {
if (r.title.length > LIMITS.titleMax) flag(r.url, 'title-long', `${r.title.length} characters`);
const prior = titles.get(r.title);
if (prior) flag(r.url, 'title-duplicate', `Same title as ${prior}`);
else titles.set(r.title, r.url);
}
if (!r.description) {
flag(r.url, 'description-missing', 'No meta description');
} else {
if (r.description.length > LIMITS.descriptionMax) {
flag(r.url, 'description-long', `${r.description.length} characters`);
}
const prior = descriptions.get(r.description);
if (prior) flag(r.url, 'description-duplicate', `Same description as ${prior}`);
else descriptions.set(r.description, r.url);
}
if (r.canonical && strip(r.canonical) !== strip(r.url)) flag(r.url, 'canonical-mismatch', r.canonical);
if (r.title && r.ogTitle && r.ogTitle !== r.title) flag(r.url, 'og-title-differs', r.ogTitle);
if (r.description && r.ogDescription && r.ogDescription !== r.description) {
flag(r.url, 'og-description-differs', r.ogDescription);
}
}
return findings;
}
Aquí he tomado dos decisiones deliberadas. hace que un 301 aparezca como su propio hallazgo en lugar de auditar silenciosamente la página a la que redirige, que es como una URL retirada que sigue en el menú de navegación se esconde de un rastreo.redirect: 'manual'
Y la clave de duplicados se basa en la cadena exacta, por lo que una página de plantilla que emite la misma descripción para cada elemento produce un hallazgo por elemento después del primero, lo que hace que la magnitud de un problema de plantilla sea evidente en el recuento.description-duplicate
Ahora tienes una biblioteca que convierte HTML sin procesar en registros y registros en hallazgos, sin nada que dependa de módulos de Node que el entorno de ejecución de Workers no tenga.
3. Expón la auditoría como un Route Handler protegido
Protejo la biblioteca con un controlador que requiere el encabezado secreto correcto, manteniendo el control de las peticiones salientes en tus manos. Una versión sin autenticación es un relé abierto que cualquiera puede apuntar a cualquier sitio; para un desarrollo de agencia, verificar antes de analizar el cuerpo mantiene esa responsabilidad bajo el control del cliente.POST
El controlador acepta una lista de URLs en el cuerpo de la solicitud y recurre a una variable de entorno separada por comas, para que un llamador programado pueda ejecutarlo con un cuerpo vacío.AUDIT_URLS
La ruta está protegida por un encabezado de secreto compartido que solo se lee en el servidor, comparado carácter por carácter en lugar de con un de salida anticipada simple. No tiene límite de tasa y obtiene cualquier URL que se le proporcione, por lo que cualquiera con el secreto puede apuntarlo a cualquier sitio; mantén el secreto privado y rótalo si se filtra.===
NEXT_PUBLIC_La filtración de secretos no existe: no tiene el prefijo y, por lo tanto, no se expone intencionalmente al navegador; el valor posterior es un dato de enrutamiento público, no un secreto.AUDIT_SECRETNEXT_PUBLIC_NEXT_PUBLIC_BASE_PATH
Consideraciones importantes
Hay dos cosas que faltan a propósito en este archivo. No hay export const runtime = 'edge', a pesar de que la página de "bring-your-own-app" lo recomienda, porque el adaptador de Cloudflare para OpenNext no es compatible con el entorno de ejecución edge de Next.js.
Y la comparación del secreto está escrita a mano en lugar de usar crypto.subtle.timingSafeEqual, porque esa extensión de Cloudflare existe en el entorno de ejecución de Workers desplegado, pero no en el next dev local, y quiero que el controlador se comporte de forma idéntica en ambos.
Ubicación del controlador de rutas
El controlador se encuentra en app/api/meta-audit/route.ts:
import { NextResponse } from 'next/server';
import { audit, crawl } from '@/lib/meta-audit';
const MAX_URLS = 100;
function authorized(request: Request): boolean {
const expected = process.env.AUDIT_SECRET ?? '';
const provided = request.headers.get('x-audit-secret') ?? '';
if (!expected || expected.length !== provided.length) return false;
let diff = 0;
for (let i = 0; i < expected.length; i++) {
diff |= expected.charCodeAt(i) ^ provided.charCodeAt(i);
}
return diff === 0;
}
export async function POST(request: Request) {
if (!authorized(request)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = (await request.json().catch(() => ({}))) as { urls?: unknown };
const fromBody = body.urls;
const urls =
Array.isArray(fromBody) && fromBody.every((u) => typeof u === 'string')
? (fromBody as string[])
: (process.env.AUDIT_URLS ?? '').split(',').map((u) => u.trim()).filter(Boolean);
if (urls.length === 0) {
return NextResponse.json({ error: 'No URLs supplied' }, { status: 400 });
}
// One request has 20 seconds; batches of 5 keep well inside that at this cap.
if (urls.length > MAX_URLS) {
return NextResponse.json(
{ error: `Send at most ${MAX_URLS} URLs per request`, received: urls.length },
{ status: 413 }
);
}
const records = await crawl(urls);
const findings = audit(records);
return NextResponse.json({ checked: records.length, findings, records });
}
La respuesta devuelve primero los hallazgos, seguidos de los registros completos, para que puedas encontrar fácilmente la lista de problemas mientras mantienes disponible cada valor analizado. Una solicitud sin el encabezado devuelve un cuerpo JSON 401, que es el comportamiento que debes confirmar antes de añadir un secreto.
4. Ejecuta la auditoría localmente con curl
Configura el secreto y una lista de URL predeterminada en .env.local, luego llama a la ruta desde una segunda terminal. Empiezo con dos URL publicadas, una página estática y un elemento del CMS, para comparar sus valores de título y descripción.
Genera el secreto con este comando de OpenSSL:
openssl rand -hex 32
Ya tienes el valor aleatorio largo necesario para el secreto de la auditoría local.
Añade las variables a .env.local en la raíz del proyecto, sustituyendo el marcador de posición por el valor generado:
AUDIT_SECRET=replace-with-a-long-random-string
AUDIT_URLS=https://example.com/,https://example.com/blog/first-post
Reinicia el servidor de desarrollo si ya estaba en marcha.
Prueba el controlador de rutas
Activa la ruta con una lista explícita para probar la ruta del cuerpo:
curl -s -X POST http://localhost:3000/api/meta-audit \
-H "content-type: application/json" \
-H "x-audit-secret: replace-with-a-long-random-string" \
-d '{"urls":["https://example.com/","https://example.com/blog/first-post"]}'
Un encabezado ausente o incorrecto devuelve un cuerpo JSON 401, y una lista de URL vacía devuelve un 400 con el mensaje "No URLs supplied".
Si las dos URL de ejemplo devolvieran los valores que se muestran a continuación, incluida una descripción compartida, la respuesta sintética tendría este aspecto:
{
"checked": 2,
"findings": [
{
"url": "https://example.com/blog/first-post",
"rule": "description-duplicate",
"detail": "Same description as https://example.com/"
}
],
"records": [
{
"url": "https://example.com/",
"status": 200,
"title": "Example Co",
"description": "Example Co helps teams ship.",
"canonical": "https://example.com/",
"ogTitle": "Example Co",
"ogDescription": "Example Co helps teams ship."
},
{
"url": "https://example.com/blog/first-post",
"status": 200,
"title": "First post | Example Co",
"description": "Example Co helps teams ship.",
"canonical": "https://example.com/blog/first-post",
"ogTitle": "First post | Example Co",
"ogDescription": "Example Co helps teams ship."
}
]
}
Lee primero la matriz findings y la matriz records solo cuando un hallazgo necesite contexto. Este ejemplo sintético muestra un patrón posible. La publicación tiene su propio título, pero comparte la descripción de la página de inicio, lo que podría significar que la plantilla de la colección está emitiendo una cadena fija en lugar del campo propio del elemento.
Elimina el cuerpo del curl y vuelve a ejecutarlo; el controlador debería recurrir a AUDIT_URLS y auditar las mismas URL, lo que confirma ambas rutas de entrada antes de realizar el despliegue.
5. Corrige los hallazgos en el Diseñador y el CMS
Corrijo las páginas estáticas una por una en su configuración de página, y las páginas de colección una sola vez en la plantilla para que cada elemento herede la corrección. De esta distinción depende dónde pasarás la tarde.
Un title-long en la página de precios es un cambio único, mientras que un description-duplicate en cuarenta publicaciones es un cambio único en la plantilla, además de verificar que cada elemento tenga realmente texto en el campo del que ahora se alimenta la plantilla.
La configuración de SEO en la página de Webflow contiene el título y la descripción para ambos tipos de página; la página de plantilla es el único lugar donde esos valores deben extraerse de los campos del elemento en lugar de ser una cadena de texto escrita manualmente.
Ahora ya tienes identificados los dos campos de la página estática antes de cambiar a la plantilla de colección.
Para una colección, configura el título y la descripción SEO de la página de plantilla para que se extraigan de los campos de la colección en lugar de usar texto escrito.
Reglas de auditoría y mapeo
Cada regla que emite la auditoría se asigna a un lugar en el producto:
| Regla | Qué señala | Dónde corregirlo |
|---|---|---|
title-missing, ,title-longtitle-duplicate |
Sin <title>, un título que supera el límite o uno compartido con otra URL |
Página estática: en la configuración de página dentro del Diseñador. Elemento CMS: en el enlace del título de la página de plantilla, o en el campo propio del elemento si el enlace es correcto pero el contenido no lo es |
description-missing, ,description-longdescription-duplicate |
Sin descripción, una que supera el límite o una compartida entre varias URL | La misma división que con los títulos; si se duplica en muchos elementos, casi siempre significa que la plantilla contiene una cadena fija en lugar de un campo |
canonical-mismatch |
El href canónico difiere de la URL que rastreaste | Confirma qué URL quieres indexar; a menudo, la lista de rastreo contiene una variante no canónica en lugar de que la página esté mal. Las etiquetas canónicas provienen de Configuración del sitio > SEO > URL de etiqueta canónica global, o de la configuración propia de una página estática |
og-title-differs,og-description-differs |
Los valores de Open Graph difieren del título o la descripción | Por lo general, es intencional para el texto de redes sociales; si no es así, revisa la configuración de la página y alinea los valores |
redirect,unreachable |
Una respuesta 3xx o distinta de 200 | Elimina la URL de la lista o corrige el enlace que te llevó a ella |
| Regla → Qué señala → Dónde corregirlo |
|---|
title-missing, ,title-longtitle-duplicate |
Sin <title>, un título que supera el límite o uno compartido con otra URL |
| Página estática: en la configuración de página dentro del Diseñador. Elemento CMS: en el enlace del título de la página de plantilla, o en el campo propio del elemento si el enlace es correcto pero el contenido no lo es |
description-missing, ,description-longdescription-duplicate |
| Sin descripción, una que supera el límite o una compartida entre varias URL |
| La misma división que con los títulos; si se duplica en muchos elementos, casi siempre significa que la plantilla contiene una cadena fija en lugar de un campo |
canonical-mismatch |
| El href canónico difiere de la URL que rastreaste |
| Confirma qué URL quieres indexar; a menudo, la lista de rastreo contiene una variante no canónica en lugar de que la página esté mal. Las etiquetas canónicas provienen de Configuración del sitio > SEO > URL de etiqueta canónica global, o de la configuración propia de una página estática |
og-title-differs,og-description-differs |
| Los valores de Open Graph difieren del título o la descripción |
| Por lo general, es intencional para el texto de redes sociales; si no es así, revisa la configuración de la página y alinea los valores |
redirect,unreachable |
| Una respuesta 3xx o distinta de 200 |
| Elimina la URL de la lista o corrige el enlace que te llevó a ella |
Ahora tienes un mapa, regla por regla, que conecta cada hallazgo con el lugar donde debe revisarse.
Trabaja en la tabla de arriba hacia abajo: primero los títulos y las descripciones, porque son lo que aparece en los resultados de búsqueda, y luego las etiquetas canonical y Open Graph. Para una colección, abre un elemento después de corregir la plantilla y confirma que el campo que la plantilla lee ahora contiene información.
Si la página publicada no tiene una etiqueta de descripción, el siguiente rastreo informa description-missing en lugar de description-duplicate; ese cambio te indica que el duplicado ha desaparecido, aunque el elemento todavía necesite texto.
Publica el sitio cuando termines la revisión. Vuelve a ejecutar el curl local y revisa la lista de hallazgos para ver qué elementos aún necesitan trabajo.
6. Despliega en Webflow Cloud y vuelve a ejecutar en la ruta de montaje en vivo
Envía el proyecto a su repositorio y crea un entorno de Webflow Cloud para el sitio. Usa el repositorio para el entorno y elige la ruta de montaje de la aplicación; la aplicación reside en una ruta del sitio en lugar de en la raíz, y si el sitio utiliza un dominio personalizado, el plan debe ser Premium o superior.
Yo monto la aplicación en /audit; el curl en vivo de abajo asume esa ruta.
Luego añade AUDIT_SECRET como secreto y AUDIT_URLS como variable simple.
Prueba el endpoint de auditoría desplegado
Una vez que la compilación se complete, llama a la ruta en el host en vivo, incluyendo la ruta de montaje en la URL:
curl -s -X POST https://www.example.com/audit/api/meta-audit \
-H "x-audit-secret: replace-with-a-long-random-string"
El cuerpo vacío hace que el manejador recurra a AUDIT_URLS desde el entorno, que es el formato que usará un llamador programado. Lo que limita un rastreo es el tiempo de espera de solicitud de 20 segundos de la plataforma, no la CPU: la ruta pasa su tiempo esperando las peticiones.
Es por eso que acepta como máximo 100 URLs por solicitud y le da a cada petición 8 segundos; divide un sitio más grande en varias solicitudes.
La respuesta debería coincidir con la ejecución local, incluyendo un error 401 cuando el encabezado es incorrecto, y el registro de compilación no debería mostrar rastro del valor del secreto.
A partir de aquí, la auditoría se ejecuta desde el mismo alojamiento que el sitio que verifica, y cualquier compañero con el secreto puede volver a ejecutarla después de una publicación sin necesidad de una copia local.
¿Qué causa que una auditoría de metadatos de Webflow Cloud falle?
Los fallos suelen deberse a una de estas cuatro causas: la directiva de tiempo de ejecución edge de Next.js, una lista de rastreo llena de variantes de URL con redirecciones, etiquetas canonical que apuntan a un host diferente al rastreado y solicitudes de cliente a las que les falta la ruta de montaje.
La primera y la última aparecen como errores de compilación o de solicitud en Webflow Cloud; las dos del medio aparecen como hallazgos que parecen incorrectos pero que se remontan a la lista de rastreo.
La compilación de Webflow Cloud falla en una ruta de API que funciona bien en next dev
Causa: La palabra "edge" se refiere a dos cosas diferentes, y la propia documentación de Webflow usa ambas en una misma página. Webflow Cloud ejecuta tu aplicación en Cloudflare Workers, una plataforma edge, y la página de "trae tu propia aplicación" describe eso como "Webflow Cloud despliega tu aplicación usando el tiempo de ejecución Edge".
La misma página recomienda luego añadir la directiva de tiempo de ejecución edge de Next.js a las rutas de API. Esa directiva es el objetivo del tiempo de ejecución edge de Next.js, algo distinto que el adaptador OpenNext de Cloudflare, a través del cual despliega Webflow Cloud, no admite. Un desarrollador que sigue la página añade la línea y lanza una compilación rota.
Solución: Elimina la runtime exportación de app/api/meta-audit/route.ts y de cualquier otra ruta donde se haya copiado. Tu controlador ya se ejecuta en la plataforma edge por el hecho de estar desplegado en Webflow Cloud; no necesita, ni puede usar, el objetivo de tiempo de ejecución de Next.js para llegar allí.
Si quieres una red de seguridad para evitar que un compañero de equipo lo vuelva a incluir, basta con un grep de runtime = 'edge' en un hook de pre-commit. Recompila y la ruta que falló al compilar debería desplegarse y responder al curl en vivo.
Cada URL devuelve un redirect hallazgo
Causa: La lista de rastreo contiene variantes de URL a las que el sitio redirige, como http en lugar de https, o una barra diagonal final que el sitio elimina. El dominio base en lugar de www provoca el mismo error. Como fetchOne solicita cada URL con redirect: 'manual', la respuesta 3xx es lo que ve la auditoría.
Informa de un redirect hallazgo y nunca analiza la página a la que apunta la redirección.
Solución: Reconstruyo la lista a partir de las URLs publicadas exactas, usando el mismo host y la misma forma de barra diagonal final que usan las etiquetas canónicas del sitio. Copiar el href canónico del código fuente de una página es una forma rápida de confirmar ese formato, siempre que el sitio lo genere: Webflow solo escribe etiquetas canónicas cuando se establece la URL de etiqueta canónica global en Configuración del sitio > SEO, o cuando una página estática tiene su propio valor canónico.
Vuelve a ejecutar el curl y la matriz records debería mostrar el estado 200 para cada URL, con los títulos y descripciones analizados.
canonical-mismatch marcado en las páginas correctas
Causa: El rastreo puede haber llegado a un host no canónico, como un subdominio de pruebas. Webflow construye la etiqueta canónica de cada página a partir de la configuración canónica global más el slug de la página, por lo que una página servida en otro host sigue apuntando al dominio personalizado.
Si ese host sirve la página con un 200 mientras la etiqueta canónica de la página apunta al dominio personalizado, la auditoría analiza la página y marca la diferencia.
El strip() asistente en lib/meta-audit.ts solo elimina la barra diagonal final antes de comparar, por lo que una diferencia de barra por sí sola pasa, mientras que una diferencia de host sigue marcando un error.
Solución: Rasteo el host canónico. Para confirmar la causa, abre el código fuente de la URL rastreada y compara el href en su etiqueta <link rel="canonical"> con el host que rastreaste; si solo difiere el host, la discrepancia puede provenir del host de rastreo en lugar de la configuración de la página.
Trata una discrepancia primero como un problema de lista y después como un problema de página. Reconstruye la lista a partir de las URLs canónicas, vuelve a ejecutar y abre la configuración de la página solo si el aviso persiste.
Las solicitudes a /api/meta-audit devuelven un 404 en la aplicación desplegada
Causa: La aplicación está montada en una ruta del sitio. Webflow Cloud inyecta esa ruta base en el momento de la compilación, pero las llamadas fetch del lado del cliente no se reescriben. La página de "trae tu propia aplicación" lo indica directamente: "Las llamadas fetch del lado del cliente deben incluir manualmente la ruta base".
Localmente no hay ruta de montaje, por lo que un fetch a /api/meta-audit se resuelve; al desplegarse, la misma URL relativa pierde el montaje. Cualquier página de informe que añadas y que llame al controlador desde el navegador se encontrará con esto, al igual que un curl guardado en marcadores que se escribió para localhost.
Solución: Antepongo a cada ruta de solicitud del lado del cliente process.env.NEXT_PUBLIC_BASE_PATH. Establece NEXT_PUBLIC_BASE_PATH en el entorno de Webflow Cloud con la misma ruta de montaje en la que está desplegada la aplicación, y déjala sin establecer localmente, volviendo a una cadena vacía para que el desarrollo siga funcionando.
No intentes leer la ruta base de next.config: la página de configuración de Webflow dice que no necesitas añadir una, y el valor se inyecta en lugar de configurarse. Cuando llames a la ruta manualmente, escribe la ruta de montaje completa, incluyendo /audit.
Qué puedes construir a continuación con Webflow Cloud y el CMS de Webflow
Para los elementos de colección, la misma API de datos lee y actualiza los campos a los que están vinculados los ajustes de SEO de tu plantilla, con las lecturas paginadas que recorre la exportación del blog. Para una personalización más profunda más allá de lo que cubre el rastreo, los documentos para desarrolladores de Webflow describen la API de datos para páginas y elementos de colección.
Preguntas frecuentes
¿Puedo proteger la ruta de auditoría con el middleware de Next.js en lugar de usar una comprobación de cabecera en el controlador?
Sí. Mantenlo en middleware.tsmiddleware.ts. La documentación de personalización del framework de Webflow Cloud indica: "El middleware de tiempo de ejecución de Node.js no es compatible. Solo el middleware de tiempo de ejecución Edge funciona en el entorno de Workers". Mantén el archivo como middleware.tsmiddleware.ts en lugar de renombrarlo a proxy.tsmiddleware.js como sugiere Next 16; proxymiddleware.js se ejecuta en el entorno de Node, no puede optar por Edge, y el archivo renombrado nunca se ejecuta en Webflow Cloud.
¿Un rastreo grande alcanzará los límites de Webflow Cloud?
Lo hará si lo envías en una sola solicitud. Cada solicitud tiene 20 segundos y hasta 1000 subsolicitudes, igual en todos los planes, por lo que la ruta limita una solicitud a 100 URL y cada recuperación a 8 segundos. Divide un sitio grande en varias solicitudes de 100 y combina los resultados.
¿Se puede programar la ejecución de la auditoría?
Sí. Puedes usar el POST con cuerpo vacío, que recurre a AUDIT_URLSapp/audit/route.ts, por lo que cualquier programador que pueda enviar un POST con la cabecera x-audit-secretx-audit-secret puede llamar a la ruta activa. Configura primero la lista de URL del entorno y el secreto; un trabajo cron que ejecute el curl activo del paso 6 es la versión más sencilla.
¿El rastreo omite las páginas noindex o respeta el archivo robots.txt?
No. Debes eliminar las URL noindex de la lista cuando sus resultados sean irrelevantes. fetchOneEl rastreador solicita cada URL que se le proporciona y no lee las reglas de robots.txt ni la etiqueta meta robots, por lo que la lista de URL determina qué páginas publicadas verifica el rastreo y qué resultados aparecen en el informe.





