Hreflang Migration Validator
Under maintenancePricing
$2.00 / 1,000 page validateds
Hreflang Migration Validator
Under maintenanceValidate hreflang annotations after a domain or site migration: locale syntax, duplicates, target health, canonicals, x-default, self-references, reciprocal return links, and old-domain references.
Pricing
$2.00 / 1,000 page validateds
Rating
0.0
(0)
Developer
kingii98
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Validate hreflang annotations after a domain or site migration. Given the destination pages — as one XML sitemap or an explicit URL list — the Actor fetches each page once, parses its link rel="alternate" hreflang declarations, and validates them: locale syntax against a deterministic maintained ISO/BCP 47-narrow set, duplicate locale declarations, target URL status and redirect behavior, canonical conflicts, x-default coverage, self-references, reciprocal return links between audited pages, and references to pre-migration domains.
This is a migration and international-SEO validator, not a generic crawler: it fetches only the input pages and their declared hreflang targets — each at most once, within hard caps — and never follows links into recursive discovery.
The package reuses the safe HTTP, SSRF validation, and redirect-chain helpers of the sitemap_health_monitor and migration_qa_auditor packages in this repository; those packages and their tests are unchanged.
What it checks
- Locale syntax of every
hreflangvalue:lang,lang-REGION, andx-defaultforms only, with the language in the maintained ISO 639-1 set and the region in the ISO 3166-1 alpha-2 or UN M.49 sets (soen-USandes-419pass,en-XXandenglishfail) - Duplicate locale declarations on the same page, after normalization (
EN-usanden-UScollide) - Relative
hreftargets, resolved against the page's final URL - Target URL health: bounded redirect chains with every hop validated as a public address, 4xx/5xx responses, redirect loops, over-long chains, redirects without
Location, and redirected targets (hreflang targets should be final URLs) - Canonical conflicts: a page or target whose canonical URL differs from its final URL
x-defaultcoverage per page (gated byrequireXDefault)- Self-reference: the page's own final URL present in its hreflang set (gated by
requireSelfReference) - Reciprocal return links: when a target is itself an audited page, that page must declare a link back (gated by
checkReciprocal) - Old-domain references: any hreflang target on a domain listed in
oldDomainsor its subdomains - Caps and truncation: per-page target cap, total target cap, and page cap are enforced and reported
Input
Provide exactly one of sitemapUrl or urls.
{"sitemapUrl": "https://new.example.com/sitemap.xml","oldDomains": ["old-example.com"],"maxPages": 100,"maxTargetsPerPage": 20,"maxTotalTargets": 5000,"concurrency": 10,"timeoutSecs": 20,"maxRedirects": 5,"requireXDefault": true,"requireSelfReference": true,"checkReciprocal": true}
{"urls": ["https://example.com/en/pricing", "https://example.com/de/preise"],"oldDomains": ["old-example.com"]}
| Field | Description |
|---|---|
sitemapUrl | Public HTTP(S) XML sitemap of destination pages. Nested sitemap indexes and gzip payloads are supported. |
urls | Explicit page URL list, used instead of sitemapUrl. Entries are normalized and deduplicated; lists longer than maxPages are rejected before any network work. |
oldDomains | Optional pre-migration domains. Targets on these domains or their subdomains are flagged. Entries must be bare domain names. |
maxPages | Hard cap on audited pages. Default 100; range 1-1,000. |
maxTargetsPerPage | Hard cap on hreflang declarations validated per page; extras are skipped and flagged. Default 20; range 1-100. |
maxTotalTargets | Hard cap on unique target URLs fetched per run; targets beyond the cap are reported as target-not-checked. Default 5,000. |
concurrency | Concurrent page or target fetches. Default 10; range 1-30. |
timeoutSecs | Per-request timeout. Default 20 seconds; range 2-60. |
maxRedirects | Maximum redirect hops followed per page or target. Default 5; range 0-10. |
requireXDefault | Flag pages without an x-default declaration. Enabled by default. |
requireSelfReference | Flag pages whose hreflang set omits the page itself. Enabled by default. |
checkReciprocal | Flag non-reciprocal links between audited pages. Enabled by default. |
Invalid combinations (both or neither source mode, empty lists, URLs with credentials or non-HTTP schemes, malformed old domains, out-of-range numbers) fail fast with a clear validation error before any network work.
Output
Every run writes one summary record, one page-result record per audited input page, and one hreflang-result record per validated declaration to the default dataset.
Summary:
{"recordType": "summary","checkedAt": "2026-08-07T09:15:00+00:00","pagesChecked": 2,"hreflangChecked": 6,"invalidLocales": 1,"duplicateLocales": 1,"brokenTargets": 1,"redirectedTargets": 1,"missingReciprocal": 1,"missingSelfReference": 0,"missingXDefault": 0,"oldDomainReferences": 1,"canonicalConflicts": 0,"fetchErrors": 0,"errors": 1,"pagesTruncated": false,"targetsTruncated": false,"perPageTargetsTruncated": 0}
Per-page result:
{"recordType": "page-result","pageUrl": "https://example.com/en/pricing","finalUrl": "https://example.com/en/pricing","status": 200,"canonicalUrl": "https://example.com/en/pricing","declaredTargets": 3,"xDefault": true,"selfReference": true,"issueCodes": [],"error": null,"checkedAt": "2026-08-07T09:15:00+00:00"}
Per-hreflang result:
{"recordType": "hreflang-result","pageUrl": "https://example.com/en/pricing","locale": "de-DE","targetUrl": "https://example.com/de/preise","finalUrl": "https://example.com/de/preise","status": 200,"validLocale": true,"selfReference": false,"reciprocal": true,"oldDomainReference": false,"canonicalUrl": "https://example.com/de/preise","issueCodes": [],"error": null,"checkedAt": "2026-08-07T09:15:00+00:00"}
reciprocal is true/false only when the target is itself an audited page; otherwise it is null (the Actor does not crawl beyond the audited set). validLocale is false for both malformed tags and tags with codes outside the maintained sets; the raw attribute value is kept in locale when it cannot be normalized.
Issue codes: invalid-locale, duplicate-locale, invalid-target-url, missing-x-default, missing-self-reference, non-reciprocal, old-domain-reference, redirected-target, redirect-loop, redirect-chain-too-long, redirect-without-location, http-4xx, http-5xx, canonical-mismatch, not-html, fetch-error, target-not-checked, target-limit-truncated.
Pricing
The Actor uses Apify pay-per-event pricing with the page-validated charge event. When monetization is enabled, users are charged $0.002 per input page validated ($2 per 1,000 pages). One page-validated event corresponds to one input page audit, including its bounded hreflang target checks.
Apify platform usage (compute units and other resources consumed by the run) may still be shown to users according to their plan and Apify's pricing rules, as described in the Actor's listing.
The Actor respects the run's maximum total charge: if the remaining budget cannot cover every input page, it validates only the chargeable prefix; if no page can be charged, it stops before page checks.
Final pricing is configured in the Apify Store listing and may change subject to Apify's pricing-change notice rules.
Security and privacy
- Only public HTTP(S) targets are accepted.
- URL credentials, localhost, and non-public, loopback, link-local, multicast, unspecified, or reserved addresses are rejected.
- Every redirect target is resolved and validated before it is followed; a redirect to a private address fails that page or target with an error record instead of being fetched.
- Page counts, target counts, concurrency, redirects, response bytes, sitemap sizes, and timeouts are all capped before or during network work.
- The Actor does not use a browser, proxy, LLM, external database, or third-party analytics service; locale validation uses an embedded deterministic data set.
- Each run is stateless; results live only in the run's default dataset, subject to the retention and access settings of the Apify account running the Actor.
Do not place secrets, private URLs, or personal data in any input field.
Limitations
- JavaScript-rendered pages are not rendered; checks use HTTP responses only.
- Only
<link rel="alternate" hreflang="...">annotations in the HTML head are validated; HTTP-header and XML-sitemap hreflang annotations are out of scope. - Reciprocal checks apply between audited input pages only; targets outside the audited set are fetched for status and canonical but their own annotations are not parsed.
- The locale validator is deliberately narrow:
lang,lang-REGION, andx-default. Script subtags (for examplezh-Hant) are reported as invalid rather than parsed. - Sitemap inputs must be valid XML sitemaps; nested indexes are followed up to 20 files.
- Network failures and rate limits are reported as per-page or per-target errors; they are not automatically retried indefinitely.
- The Actor does not send notifications itself. Use Apify schedules, webhooks, or an automation platform.
Support
For reproducible issues, open an issue from the Actor page and include the Apify run ID, sanitized input, expected result, and affected public URL. Do not include API tokens or private data.
This Actor validates hreflang annotations; it does not provide legal, security-audit, or uptime guarantees.