Google rastreó 270 artículos de un blog y no indexó ninguno: recibía cero caracteres
El HTML que el buscador obtenía de cada página tenía el cuerpo vacío, porque todo el contenido se montaba después con JavaScript. Un repaso a los tres fallos que lo provocaban.

Un blog de 270 artículos descubrió que Google los había rastreado todos y no había indexado ninguno. El HTML que el buscador recibía tenía cero caracteres dentro del contenedor de la aplicación: el texto solo aparecía una vez ejecutado el JavaScript.
El dato salió de un export de Search Console fechado el 13 de agosto de 2026: 427 filas, de las que 270 caían en «rastreado, actualmente sin indexar», 157 en soft 404 y 95 en «duplicado, Google eligió otro canónico». La comprobación es la parte incómoda: basta pedir la propia URL con el User-Agent de Googlebot y contar. Cero caracteres dentro de <div id="app">, cero bloques JSON-LD. Con GPTBot, idéntico. El lado de la búsqueda por IA también estaba ciego.
La causa es de manual en cualquier single-page app: el primer HTML que sirve el servidor es un cascarón. Google ejecuta JavaScript, pero ejecutarlo no es lo mismo que esperar a que termines antes de puntuar.
El cuerpo en el primer HTML
El sitio ya tenía una edge function en Netlify inyectando etiquetas og para las previsualizaciones sociales, así que ya leía el artículo desde la base de datos. Añadir el cuerpo fue el mismo viaje: renderizarlo a HTML semántico dentro de <div id="app">, emitir JSON-LD de tipo BlogPosting y generar hreflang solo cuando la versión en el otro idioma existe de verdad. La rama es exclusiva para bots, el lector humano no paga nada.
Una decisión merece párrafo aparte: no reutilizó el renderizador de markdown del frontend. Ese usa DOMPurify, DOMPurify necesita un DOM y el runtime de Deno en el edge no lo tiene. La alternativa fue un renderizador que escapa primero y aplica estructura después. Con ese orden, el XSS es imposible por construcción y no «improbable porque tuve cuidado». El precio lo pagan los bloques de HTML crudo, que degradan a texto plano. Antes de juzgar si eso importaba, consultó la base de datos: de 1.112 bloques de texto en artículos publicados, 1.111 usan markdown y 1 usa HTML crudo.
La verificación fueron 28 pruebas unitarias y 12 de contrato entre capas, todas en verde. Y algo más útil: 9 variantes rotoas a propósito, cada una haciendo fallar exactamente la comprobación que tocaba. Un detalle que costó media hora: usó un byte NUL literal como centinela para los límites de código en línea y Git marcó el fichero como binario, sin diff ni revisión línea a línea.
El 200 que se lo tragaba todo
El segundo problema crece solo. La última línea del fichero _redirects era /* /index.html 200, esa que aparece en todas las guías de despliegue de SPA porque sin ella cualquier subruta devuelve 404 al refrescar. El efecto colateral es que también captura las URLs muertas de la era WordPress, que respondían 200 con el HTML de la portada. Google recibe un 200 y algo que parece la home, y lo archiva como soft 404.
La corrección tiene dos capas. La edge function devuelve 404 para /content/:slug, /product/:id y /works/:slug cuando el registro no existe con certeza, manteniendo el cuerpo de la SPA para que el visitante siga viendo la pantalla de página no encontrada. En _redirects, las rutas heredadas de WooCommerce y WordPress devuelven 410, no un 301 a la tienda, que a ojos de Google sigue siendo un soft 404 aunque se reporte en otro sitio. Siete URLs .md antiguas reciben un 301 que quita la extensión.
El punto delicado es el fail-open. La función que leía filas devolvía un array vacío tanto si la consulta fallaba como si no había resultados. Mientras solo inyectaba metadatos daba igual, pero en cuanto un registro ausente significa 404, una caída de tres segundos de la base de datos declara inexistente un artículo real y mete ese 404 en el índice. La solución es devolver { ok, rows } y tratar solo el caso correcto-y-cero como ausencia. En /product/:id hizo falta otra capa: comprobar en local si la cadena tiene pinta de uuid antes de mandarla a PostgREST.
No hay en el texto resultados de indexación posteriores a los arreglos, así que la eficacia de todo esto queda sin confirmar por ahora. Lo que sí se sostiene es el método: el HTML que ve el bot estaba a un solo curl de distancia, y el autor escribió 270 artículos antes de pedirlo una vez.


