Back to Blog

Thin Route + Shared Component: A Pattern for Bilingual Pages in Astro

#astro#architecture#i18n
Thin Route + Shared Component: A Pattern for Bilingual Pages in Astro

A bilingual site can easily end up with markup duplicated twice — once in /page.astro, once again in /en/page.astro — where every design change has to be remembered and applied in two places. This site has used a different pattern from the start: thin route + shared component, and I only really appreciated how valuable it is after running into the opposite version of this problem on a client project.

Here’s how it works: every listing/detail page (Home, Portfolio, Blog) has one locale-agnostic “body” component under src/components/pages/, for example BlogIndex.astro. That component takes locale as a prop, pulls the dictionary via getDictionary(locale) for all the chrome text (labels, section titles), and calls getBlogPosts(locale) for the content data — nothing hardcoded anywhere.

The route file itself ends up nearly empty. src/pages/blog/index.astro just imports the component and renders <BlogIndex locale="id" />, while src/pages/en/blog/index.astro renders <BlogIndex locale="en" />. One line differs by locale, everything else is identical. The first time I wrote this pattern, it felt like overkill for such a simple page — why does rendering one component need its own separate file? But the payoff only shows up once a site actually grows more pages.

Not every page uses this pattern, and that’s a deliberate choice. Pages like About and Contact are excluded on purpose, because their content is prose that doesn’t repeat — my bio on the About page doesn’t have a repeating structure like dictionary chrome does, so it makes more sense to write it directly per file per language than to force it into a dictionary system designed for short, repeated labels. Forcing every page through the same pattern is a trap I’ve tried to avoid — forced structural consistency ends up making the code more convoluted, not cleaner.

The real payoff only really landed for me while working on a client project that didn’t use this pattern — markup was duplicated from the start between the two language versions. The client asked for a layout change to one product card, and the developer before me (not the one who built it originally) only updated the English version, missing the other one. Nobody caught it until QA found a visual inconsistency two weeks later. Back on my own site, if I change the blog post card layout, I only need to edit one file (PostCard.astro, called from BlogIndex.astro), and it stays consistent across both languages automatically — there’s no possibility of forgetting to apply it a second place, because a second place doesn’t exist.

This pattern isn’t technically clever — it’s just a consistent separation of concerns, something software engineering has taught for a long time, well before Astro existed. But small discipline like this is what keeps a bilingual site maintainable by one person, without a mental checklist of “don’t forget to update the other version” every time something changes.