Các bước thực tế
Mở trang cần kiểm tra và bảng điều khiển dành cho nhà phát triển của trình duyệt. Với công cụ kiểm tra tràn trên di động, trước tiên hãy bật chế độ responsive.
Xem xét mã, dán toàn bộ đoạn script vào console rồi nhấn Enter. Script đọc trang hiện tại; chỉ công cụ kiểm tra metadata sitemap và các bộ kiểm tra cùng origin mới gửi request có giới hạn.
Xem kết quả trong console hoặc CSV/JSON tải xuống. Chính sách trình duyệt, trạng thái xác thực và phương thức máy chủ không được hỗ trợ có thể khiến một số phép kiểm tra chưa kết luận được.
Script dành cho console của trình duyệt
Xem xét trước khi chạy. Script đọc trang hiện tại; chỉ các lần kiểm tra sitemap và liên kết mới gửi request cùng origin có giới hạn. Request không gửi cookie; script không thay đổi nội dung website.
(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.');
})();