Pasos prácticos
Abre la página que quieres inspeccionar y la consola de desarrollo del navegador. Para el comprobador de desbordamiento móvil, empieza con el modo adaptable.
Revisa el código, pega el fragmento completo en la consola y pulsa Intro. Lee la página actual; solo la herramienta de metadatos de sitemap y los comprobadores del mismo origen realizan solicitudes acotadas.
Revisa la salida de la consola o el CSV/JSON descargado. Las políticas del navegador, la autenticación y los métodos del servidor no compatibles pueden hacer que algunas comprobaciones no sean concluyentes.
Script para la consola del navegador
Revisa el código antes de ejecutarlo. Lee la página actual; solo la herramienta de metadatos de sitemap y los comprobadores del mismo origen realizan solicitudes acotadas. Las solicitudes no incluyen cookies; el script no cambia el contenido del sitio.
(async () => {
const MAX_VARIANTS = 30;
const deadline = Date.now() + 30000;
const currentCanonical = new URL(document.querySelector('link[rel="canonical"]')?.href || location.href, location.href).href;
const tags = [...document.querySelectorAll('link[rel="alternate"][hreflang][href]')].slice(0, MAX_VARIANTS);
const rows = [];
for (const tag of tags) {
const target = new URL(tag.href, location.href);
const item = { language: tag.hreflang, url: target.href, sameOrigin: target.origin === location.origin, reciprocal: 'not checked' };
if (item.sameOrigin) {
if (Date.now() >= deadline) { item.reciprocal = 'skipped: 30-second runtime limit'; rows.push(item); continue; }
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), Math.min(5000, deadline - Date.now()));
try {
const response = await fetch(target.href, { credentials: 'omit', redirect: 'follow', signal: controller.signal });
if (!response.ok || new URL(response.url).origin !== location.origin) throw new Error('HTTP ' + response.status + ' or cross-origin redirect');
const doc = new DOMParser().parseFromString(await response.text(), 'text/html');
const reciprocal = [...doc.querySelectorAll('link[rel="alternate"][hreflang][href]')].some((link) => {
try { return new URL(link.href, target.href).href === currentCanonical; } catch { return false; }
});
item.reciprocal = reciprocal ? 'yes' : 'missing';
} catch (error) { item.reciprocal = error.name === 'AbortError' ? 'timeout' : 'could not check'; }
finally { clearTimeout(timer); }
}
rows.push(item);
}
console.table(rows);
console.info('Current canonical:', currentCanonical, '· hreflang variants found:', tags.length);
if (Date.now() >= deadline) console.warn('The 30-second runtime limit was reached; remaining links were not checked.');
if (tags.length >= MAX_VARIANTS) console.warn('Only the first ' + MAX_VARIANTS + ' language variants were checked.');
if (!tags.length) console.warn('No link rel=\"alternate\" hreflang entries were found.');
console.info('Cross-origin and inaccessible pages are not fetched. A “missing” reciprocal link may also result from a canonical URL variant or redirect; review it manually.');
})();