DEV Community

Francisco GSilva
Francisco GSilva

Posted on • Originally published at francgs.dev

Real Bilingual Blog Refactor: Routes, hreflang, and Consistent SEO

Real Bilingual Blog Refactor: Routes, hreflang, and Consistent SEO

Building a bilingual blog is not just translation work. If route architecture, canonical strategy, and navigation are not aligned, you get duplicate content, indexing confusion, and users landing in mixed-language sections.

This refactor solved that from end to end. Here is the practical walkthrough.

Initial Problem

We had clear symptoms:

  • /blog was mixing Spanish and English posts,
  • old non-localized URLs were still active,
  • direct URL visitors did not get consistent language behavior,
  • Search Console showed canonical conflicts and duplicates.

Goal: language-consistent UX and crawl-friendly SEO.

Architecture Decisions

We standardized around five rules:

  1. Language-first routes: /{lang}/...
  2. Smart redirects for generic routes (/, /blog, /tags)
  3. Per-post hreflang + x-default
  4. Language preference cookie (site_lang)
  5. Localized pages for home, tags, and category hubs

Step 1: Localized Pages

We implemented language-aware pages:

  • src/pages/[lang]/index.astro
  • src/pages/[lang]/blog/[...slug].astro
  • src/pages/[lang]/blog/index.astro
  • src/pages/[lang]/blog/category/index.astro
  • src/pages/[lang]/tags/index.astro
  • src/pages/[lang]/tags/[tag].astro

This removes ambiguity and makes language explicit in every route.

Step 2: Middleware Redirect Strategy

Middleware now resolves language from cookie + Accept-Language, then redirects generic routes.

Simplified logic:

const preferredLang = cookieLang ?? detectLangFromHeader(acceptLanguageHeader);

if (normalizedPath === "/") {
  return Response.redirect(new URL(`/${preferredLang}/`, url.origin), 302);
}

if (normalizedPath === "/blog") {
  return Response.redirect(new URL(`/${preferredLang}/blog/`, url.origin), 302);
}
Enter fullscreen mode Exit fullscreen mode

Result: no more mixed-language listing pages.

Step 3: Canonical and hreflang

We extended SEO metadata to include language alternates:

export interface LanguageAlternateEntry {
  lang: string;
  url: string;
}
Enter fullscreen mode Exit fullscreen mode

Per post, alternates are computed by translationKey and include x-default.

const alternates = translations.map((translation) => ({
  lang: translation.data.lang,
  url: `${siteUrl}${translation.data.lang}/blog/${translation.id}/`,
}));
alternates.push({ lang: "x-default", url: `${siteUrl}en/blog/${defaultId}/` });
Enter fullscreen mode Exit fullscreen mode

This gives search engines explicit language mapping and reduces canonical ambiguity.

Step 4: Navigation Consistency

The header was updated to preserve language context:

  • Home -> /${lang}
  • Tags -> /${lang}/tags
  • Categories -> /${lang}/blog/category

We also added an explicit ES | EN switcher and persist user choice via cookie.

Step 5: Remove Duplicate "Related" UX

Some migrated posts had both:

  • markdown β€œRelated” link list,
  • visual related-posts component.

That created duplicated sections and visual noise. We removed markdown duplicates and kept the component-based related cards.

Step 6: Translation Coverage Report

To prevent ES/EN drift, we added a coverage report:

pnpm run seo:report-translations
Enter fullscreen mode Exit fullscreen mode

Output:

  • docs/seo/translation-coverage.md

This catches incomplete translation keys before release.

Step 7: QA Checklist After Refactor

  • [ ] /blog redirects to /{lang}/blog/
  • [ ] /tags redirects to /{lang}/tags/
  • [ ] post canonical matches localized URL
  • [ ] hreflang includes ES/EN + x-default
  • [ ] no mixed-language listing pages
  • [ ] build passes cleanly

Common Mistakes in Bilingual Refactors

  1. Translating content but keeping non-localized URLs.
  2. Localized routes without metadata parity.
  3. Navigation that breaks language context.
  4. Redirect chains without a clear policy.
  5. No automated translation coverage audit.

Publishing Opportunities From This Refactor

This refactor itself can become a technical series:

  1. Language route architecture in Astro.
  2. Practical international SEO: canonical + hreflang.
  3. Migration strategy that preserves indexing stability.

Close

The biggest win was not β€œhaving two languages.” The real win was consistency across user navigation, metadata, and crawl behavior.

If you are planning a similar migration, do not start with translation copywriting. Start with route architecture, canonical strategy, and redirect behavior.

Related:

Top comments (0)