Hreflang Done Right: Avoiding International SEO Chaos on Multilingual Sites (Without Creating Duplicate Content)

Hreflang connects equivalent language versions; canonical identifies the preferred URL for a page. A useful audit checks both signals together, including what each linked page sends back. Looking at one correct tag on one page cannot establish that the whole language group works.
For Ramfa SEO’s first release, we tested 11 page groups across Swedish, English, and Persian: 33 public URLs. The recorded HTTP/HTML check on 22 October 2026 passed all 33 URLs. This is a technical result from our own project, not evidence of rankings, traffic growth, or Google selecting every declared canonical.
The real language model used in this example
The Swedish homepage is https://ramfaseo.se/, English is https://ramfaseo.se/en/, and Persian is https://ramfaseo.se/fa/. Each has a self-referencing canonical. Each homepage lists the same sv, en, and fa alternatives, including itself. Our x-default points to the English homepage as the chosen fallback.
We use language codes without regional qualifiers because these are language editions. Persian does not imply a user is in Iran, and English does not imply a user is in the United Kingdom. The declared fallback is an explicit site decision; a different site might use a language selector instead.
<link rel="canonical" href="https://ramfaseo.se/en/" />
<link rel="alternate" hreflang="sv" href="https://ramfaseo.se/" />
<link rel="alternate" hreflang="en" href="https://ramfaseo.se/en/" />
<link rel="alternate" hreflang="fa" href="https://ramfaseo.se/fa/" />
<link rel="alternate" hreflang="x-default" href="https://ramfaseo.se/en/" />
The block above belongs to the English homepage. A service page must point to its equivalent service pages, not to the homepages. Real translations retain their own canonicals rather than all canonicalizing to English.
Run the same check on an explicit URL map
- Initial 33-URL language map (CSV)
- Python checker using the standard library
- Recorded result with timestamp and per-URL checks (JSON)
Read the script, save it beside the map, and run python check_hreflang.py hreflang-map.csv > result.json with Python 3. Audit only URLs you are authorized to test. The script fetches the HTTPS URLs in the supplied map and returns a nonzero exit code if any check fails.
For every row, it checks that the URL responds successfully without changing to another URL, has one self-canonical, has no detected noindex directive, and declares exactly the expected language alternatives. It visits every mapped member, so an omitted return link appears as a failure on that member. It also checks duplicate or unexpected alternatives. In this implementation, an English row creates an expected x-default pointing to English; adapt that policy when auditing another site.
The download intentionally records the first 33-page release. It is a dated evidence set, not a promise that the site will always have 33 pages. Add new translation groups to your working map as the site expands.
What failures look like
Wrong canonical: if the Persian technical SEO page declares the English technical page as canonical, the checker reports “Expected one self-canonical.” Investigate the metadata template instead of adding more hreflang tags around the contradiction.
Missing return link: if Swedish links to Persian but the Persian page omits sv, the Persian row reports a missing or wrong sv alternate. Fix the whole group from a shared route registry so a later edit cannot leave the two sides inconsistent.
Missing translation: do not invent an alternate that redirects to the homepage or returns 404. Publish a genuine equivalent page first, or temporarily leave that language out of the group on every available member. Update the CSV to represent the intended, available group. The map is an input to the test; an incomplete map cannot discover your entire content inventory for you.
Redirected alternate: point tags and internal links directly to the final HTTPS URL. A redirect may bring a visitor to a page, but the checker deliberately treats a changed destination as a release issue.
What this result does not establish
The script reads the initial HTML response, not a browser-rendered DOM. It does not judge translation quality, validate every language-code standard, verify the full robots.txt policy, establish access for a verified Google crawler, or inspect Google’s selected canonical. It reads up to two million bytes per response. Use a fuller audit if your metadata depends on JavaScript or your pages exceed that limit.
Check representative pages in Search Console and review the language switcher, visible translation, direction, and navigation in a browser. For Persian, RTL layout is a separate usability requirement; correct hreflang does not make a left-to-right interface usable.
Keep articles and service pages distinct
This Insights article keeps its own canonical on insights.ramfaseo.se. A related service page on the main domain is a different resource, not an alternate translation of the article. Link them editorially rather than placing them in the same hreflang group.
For the underlying search guidance, see Google’s documentation on localized versions. For our project context, read the Ramfa SEO implementation example. If your site needs a language-map audit and implementation, see our multilingual SEO service. Prepared by Ramin Amirhaeri; technical example checked on 22 October 2026.



