Desarrollamos tu web presencial SPA DESDE 300€. Si, es una locura. Web SPA DESDE 300€ — Si, es una locura. Hablemos →

Cómo avisar a Bing y Yandex de un cambio con IndexNow sin esperar al rastreo

Cómo avisar a Bing y Yandex de un cambio con IndexNow sin esperar al rastreo

Publicar una página y esperar a que un buscador pase a mirarla es el comportamiento por defecto de casi cualquier web, y nadie se plantea que pueda ser al revés. IndexNow le da la vuelta: avisas tú, en el momento. Vamos a ver qué motores lo consumen, qué conviene avisar y qué no, y por qué la parte difícil no es la URL que entra en el índice sino la que sale. Los ejemplos van en Laravel, que es donde nos movemos a diario en AndorraDev, pero el protocolo es una petición HTTP y funciona igual desde cualquier stack.

Google no participa, así que esto suma a tu sitemap y no lo sustituye

Lo primero, porque condiciona las expectativas: Google no está en IndexNow. Los motores que sí lo consumen son Bing, Yandex, Seznam y Naver, y un único envío al punto de entrada compartido llega a todos.

Dicho eso, sigue mereciendo la pena, y por dos motivos que no son el volumen de tráfico de Bing.

Bing alimenta a más cosas de las que parece. Los asistentes conversacionales que citan fuentes tiran de su índice, y ahí la frescura importa mucho más que en una búsqueda tradicional.

Y el modelo cambia de dirección. Un sitemap es pasivo: dice qué existe y espera a que alguien venga. IndexNow es activo: tú avisas en el momento del cambio. Un sitio con contenido que caduca (fichas que se agotan, ofertas que expiran, artículos que se despublican) tiene un desfase entre lo que muestra y lo que el buscador cree que muestra, y ese desfase es lo que esto cierra.

El protocolo entero es un fichero y un POST

No hay que estudiarse nada. Publicas una clave en tu dominio y mandas las URLs que han cambiado.

POST https://api.indexnow.org/indexnow
{
  "host": "tudominio.com",
  "key": "a3f9c1...",
  "urlList": ["https://tudominio.com/pagina-que-ha-cambiado"]
}

Dos detalles del protocolo que conviene conocer:

Una sola URL puede ir por GET con la clave en la cadena de consulta, y varias van por POST. Cambiar de forma según el número no es un capricho, es lo que admite el protocolo y ahorra una petición con cuerpo cuando no hace falta.

El máximo es diez mil URLs por petición. Por encima de eso hay que trocear, y ese troceo debería ser automático: nadie quiere descubrir el límite en producción.

La clave es pública a propósito, y ahí está el detalle que rompe todo

La clave de IndexNow no es un secreto. Es un fichero que cualquiera puede leer en tu dominio, y su función no es autenticar sino demostrar que quien manda las URLs controla el sitio.

Eso tiene una consecuencia práctica muy concreta: la clave que mandas en la petición y el contenido del fichero tienen que coincidir. Si no coinciden, el motor rechaza el envío.

Y ahí aparece el problema de despliegue. Si la clave vive en una variable de entorno y el fichero se sirve leyéndola, un servidor al que le falte esa variable sirve un fichero que ya no casa, y nada te lo dice hasta que un envío es rechazado. Por eso conviene tener un comando que compruebe el fichero de verdad, descargándolo por HTTP y comparándolo con la clave configurada. Es la primera cosa que hay que ejecutar cuando algo no sale.

Hay una decisión más que merece pensarse: servir el fichero como ruta o como fichero físico. Como ruta es más cómodo y multidominio. Como fichero físico tiene una ventaja que solo se ve el día que la necesitas: artisan down responde 503 a todas las rutas, y un motor que compruebe tu clave a mitad de un despliegue concluiría que ha desaparecido.

Andie recomienda

El host permitido sale de tu URL base, y ese filtro es precisamente lo que impide que un entorno de pruebas acabe avisando de URLs de producción. Comprueba que la variable apunta al dominio correcto en cada entorno y tendrás esa red puesta de serie, sin configurar nada más.

La parte que casi nadie implementa es la URL que sale del índice

Casi todas las integraciones avisan al publicar y ahí se detienen, porque es el ejemplo que trae el protocolo y es el caso que se le ocurre a cualquiera primero. Pero un índice desactualizado rara vez molesta por lo que le falta: molesta por lo que le sobra, y eso solo se corrige avisando también de lo que desaparece.

Avisar cuando publicas es lo obvio y lo hace todo el mundo. Lo que casi nadie hace es avisar cuando despublicas, y es igual de importante: si retiras una ficha y no lo dices, el buscador sigue enseñando un resultado que lleva a una página que ya no existe.

Y hay un tercer caso que se olvida siempre: cuando cambia el slug. Ahí hay que mandar dos URLs, la nueva para que la indexe y la vieja para que la revise y la descarte. Si solo mandas la nueva, la antigua se queda en el índice durante semanas.

Eso significa que la condición no es "está publicado", sino una transición: qué era antes y qué es ahora. Entra en el índice, sale del índice, o cambia de dirección.

Un observer ingenuo te manda cincuenta mil URLs de golpe

Enganchar el aviso al evento de guardado es la primera línea que escribe todo el mundo, y aguanta sin un rasguño mientras los cambios los hace una persona desde un formulario. Lo que rompe el montaje es el día en que los hace una máquina, porque a la base de datos le da igual quién escriba.

Si enganchas el aviso al evento de guardado sin más, cualquier proceso que recorra la tabla lo dispara. Y todo proyecto con volumen tiene alguno: un comando nocturno que recalcula estadísticas, una reindexación, una migración de datos.

En un catálogo nuestro con 157.000 fichas, un comando de estadísticas guarda cada registro para actualizar unos contadores. Sin ninguna precaución, ese comando habría mandado de golpe las más de cincuenta mil fichas indexables que aún no se habían avisado nunca.

La solución es declarar qué columnas no cuentan como cambio de contenido:

IndexNow::track(Game::class)
    ->url('games.show')
    ->when('published')
    ->ignoring([
        'gameplay_count_cache', 'stats_cache', 'rating_avg',
        'updated_at', 'last_played_at',
    ]);

Trece columnas en ese caso, todas ellas contadores que reescribe un proceso automático. Con la lista puesta, ese comando no genera un solo aviso.

Y el criterio general es fácil de aplicar: si una columna la escribe un proceso y no una persona, no es contenido.

Una marca en la fila hace que el envío sea idempotente

Una transición ocurre una sola vez y no vuelve. Si el aviso que la acompañaba se pierde, no hay manera de recuperarlo preguntándole a la base de datos, porque una tabla solo sabe cómo están las cosas ahora, nunca cómo llegaron a estarlo.

Detectar la transición exacta funciona, pero exige estar mirando en el momento justo. Si el proceso falla, si el aviso se pierde, o si la fila cambió de estado por una vía que no dispara eventos, esa transición no vuelve a ocurrir nunca.

Guardar una columna del tipo index_now_sent cambia la pregunta: en vez de "¿acaba de publicarse?", pasa a ser "¿está publicado y todavía no lo hemos avisado?". Eso es un estado, no un instante, así que se puede comprobar cuando quieras y avisar se vuelve idempotente.

Y da gratis el relleno retroactivo, que es lo que necesitas el día que instalas esto sobre un sitio que ya tiene contenido: un comando que recorre la tabla en lotes, envía y marca, y que si se interrumpe puede continuar donde iba.

El 202 es un éxito y el 403 casi siempre significa lo mismo

IndexNow responde con códigos HTTP corrientes pero les da significados propios, y no acompaña ninguno de una explicación: recibes el número y poco más. Merece la pena tener la equivalencia delante la primera vez, porque dos de ellos parecen exactamente lo contrario de lo que son.

202 es correcto. Significa aceptado con la clave todavía en verificación, y es lo normal las primeras veces. Tratarlo como error es el malentendido más común.

403 casi siempre es el fichero de clave. No es que te hayan bloqueado: es que la clave no se puede leer públicamente o no coincide. Un mensaje de error útil aquí debería decirte la URL exacta donde tiene que estar el fichero y qué debe contener.

422 significa que las URLs no son todas del mismo host que declaraste, o que la clave no corresponde a ese host.

429 y los 5xx sí se reintentan, y el 429 trae su propio Retry-After que hay que respetar.

400 no se reintenta, porque una URL malformada no se arregla sola.

Esa distinción entre lo que se reintenta y lo que no es la mitad del trabajo de una integración seria. La otra mitad es la espera creciente entre reintentos, del orden de un minuto, cinco y quince, con el último intento dejando el fallo registrado con su motivo real.

Los eventos de Eloquent no ven el query builder

Hay un agujero en cualquier integración construida sobre observers, y no lo tapa ningún paquete porque no está en su mano: Eloquent solo emite eventos cuando la escritura pasa por el modelo. Todo lo que va directo a la base de datos es invisible.

Estas cuatro operaciones no disparan eventos de modelo:

Post::where('status', 'draft')->update(['status' => 'published']);
Post::where('created_at', '<', $fecha)->delete();
DB::table('posts')->insert($filas);
Post::upsert($filas, ['slug']);

Son escrituras directas contra la base de datos y ningún observer se entera. Si tu proceso de importación usa upsert, esas páginas nunca se avisarán por la vía automática.

La respuesta correcta es doble: usa el comando de relleno después de esos procesos, o, si tienes la columna de marca, ponla a cero en el propio UPDATE y deja que el flujo normal las recoja en la siguiente pasada. La segunda es más elegante porque no requiere acordarse de nada.

Qué hay en el mercado y en qué se diferencia

Hay cuatro caminos, y lo que los separa no es la calidad de la implementación sino qué parte del problema se echan a la espalda. Todos resuelven bien el envío; casi ninguno se mete en la decisión de cuándo hay que enviar, que es donde está el trabajo de verdad.

Los clientes PHP puros te dan la petición HTTP y ya. Buffering, cola, gestión de la clave, agrupación por host y troceado los escribes tú.

ymigval/laravel-indexnow y laravel-freelancer-nl/laravel-index-now son las dos referencias de Laravel. La segunda encola con un retardo configurable y usa unicidad de trabajo para no repetir. Las dos resuelven bien el "cómo enviar". La diferencia de enfoque que conviene valorar es que el "cuándo enviar" sigue siendo tuyo: llamas al envío desde donde toque, y decidir qué escritura de qué modelo merece un aviso, y muy en particular las URLs que salen del índice, es el trabajo que queda.

Hacerlo a mano son quince líneas y es perfectamente razonable si tienes veinte páginas. Con volumen, lo que descubres por el camino es la ruta del fichero de clave, la agrupación por host, el troceado a diez mil, el 202 que parece error, el 403 que significa otra cosa, el Retry-After, la deduplicación y el comando nocturno que te manda cincuenta mil URLs.

Y la API de Bing Webmaster Tools existe, con su cuota y su clave propia. Cubre solo Bing, mientras que un envío a IndexNow llega a los cuatro motores.

Lo que decidió nuestro caso fue enganchar el protocolo a los eventos de Eloquent con condiciones de transición declarativas, para que publicar avise y despublicar también, sin tener que acordarse en cada sitio del código.

Y si no trabajas con Laravel

Los criterios no dependen del lenguaje:

  • Avisa también cuando la página sale del índice, y manda la URL vieja cuando cambie el slug.
  • Decide qué columnas cuentan como contenido. Si no lo haces, tu primer proceso masivo se convierte en un envío masivo.
  • Guarda un estado, no dependas de pillar el instante. Convierte el aviso en idempotente y te da el relleno retroactivo.
  • Agrupa y espera un poco antes de enviar. Una petición que cambia treinta páginas debería producir un envío, no treinta.
  • Y lee bien los códigos: 202 es éxito, 403 es tu fichero de clave, y solo 429 y los 5xx merecen reintento.

Dónde está el código

El paquete está en Packagist, el código en GitHub y la documentación del paquete recoge las condiciones de seguimiento, los comandos y la configuración. Es MIT, con seis comandos de consola y una tabla opcional para el histórico de envíos.

Si tienes un sitio con contenido que entra y sale del índice, esto se instala en una tarde y cierra el desfase entre lo que muestras y lo que el buscador cree que muestras. Lo trabajamos en posicionamiento web y desarrollo Laravel, y si nos cuentas qué publicas y con qué frecuencia te decimos si te compensa.

Escrito por
Edu Lazaro
Edu Lazaro
Founder & Lead Developer en AndorraDev

Desarrollador full-stack con más de 15 años de experiencia en Laravel, React, Node.js y arquitecturas cloud. Ayudo a empresas en Andorra a construir su presencia digital.

Partner de diseño · ionospace.
Necesitas ayuda? ×
Andie by AndorraDev
Asistente IA + equipo humano
Asistente IA de AndorraDev
Andie
Hola! Soy Andie, el asistente IA de AndorraDev. ¿En qué puedo ayudarte? Si necesitas hablar con Edu, solo pídelo.
17:27