Hreflang Migration Validator avatar

Hreflang Migration Validator

Under maintenance

Pricing

$2.00 / 1,000 page validateds

Go to Apify Store
Hreflang Migration Validator

Hreflang Migration Validator

Under maintenance

Validate 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

kingii98

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

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 hreflang value: lang, lang-REGION, and x-default forms 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 (so en-US and es-419 pass, en-XX and english fail)
  • Duplicate locale declarations on the same page, after normalization (EN-us and en-US collide)
  • Relative href targets, 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-default coverage per page (gated by requireXDefault)
  • 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 oldDomains or 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"]
}
FieldDescription
sitemapUrlPublic HTTP(S) XML sitemap of destination pages. Nested sitemap indexes and gzip payloads are supported.
urlsExplicit page URL list, used instead of sitemapUrl. Entries are normalized and deduplicated; lists longer than maxPages are rejected before any network work.
oldDomainsOptional pre-migration domains. Targets on these domains or their subdomains are flagged. Entries must be bare domain names.
maxPagesHard cap on audited pages. Default 100; range 1-1,000.
maxTargetsPerPageHard cap on hreflang declarations validated per page; extras are skipped and flagged. Default 20; range 1-100.
maxTotalTargetsHard cap on unique target URLs fetched per run; targets beyond the cap are reported as target-not-checked. Default 5,000.
concurrencyConcurrent page or target fetches. Default 10; range 1-30.
timeoutSecsPer-request timeout. Default 20 seconds; range 2-60.
maxRedirectsMaximum redirect hops followed per page or target. Default 5; range 0-10.
requireXDefaultFlag pages without an x-default declaration. Enabled by default.
requireSelfReferenceFlag pages whose hreflang set omits the page itself. Enabled by default.
checkReciprocalFlag 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, and x-default. Script subtags (for example zh-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.