A Maintainable Content Architecture for Astro
How to structure collections, routes, layouts, and translations so an Astro publication remains easy to extend.
Keep article data in a typed collection, keep route files thin, and let shared components own presentation without owning content.
A content-focused Astro site can begin with a few Markdown files and one dynamic route. As the publication grows, translations, categories, related content, metadata, and design variants can turn that simple start into scattered conditional logic.
The architecture stays manageable when each layer has one clear responsibility.
Put repeatable content in a collection
Articles share a shape: title, description, publication date, category, tags, language, and body. That makes them a strong fit for a build-time content collection.
const articles = defineCollection({
loader: glob({
base: "./src/content/articles",
pattern: "**/*.md",
}),
schema: z.object({
title: z.string(),
description: z.string(),
locale: z.enum(["en", "ar"]),
publishedAt: z.coerce.date(),
category: z.string(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
The schema catches incomplete frontmatter during the build. It also gives components reliable types instead of optional values that every template must defend against.
Keep one-off pages such as About and Contact as Astro pages or focused page components. Moving every paragraph into a general-purpose data layer can make editing harder without providing a real benefit.
Keep route files thin
A dynamic article route should query entries, produce static paths, and pass one entry to the reading view. It should not contain the entire article design.
This separation makes English and Arabic routes small wrappers around the same presentation component. It also prevents fixes to metadata or the table of contents from being copied across route trees.
Use stable public slugs that are separate from internal collection IDs. The ID must be unique across all language files, while translated pages may intentionally share the same readable slug under different locale prefixes.
Make locale a first-class input
Do not discover the language from scattered pathname checks in every component. Pass a typed locale into shared page components and read labels, routes, direction, and date formatting from one translation module.
The document itself must set both lang and dir. Direction-aware CSS properties such as margin-inline, padding-inline, and border-inline-start reduce the number of RTL overrides.
Translated articles need a stable translationKey. That key connects English and Arabic entries even if their titles and public slugs later diverge.
Let layouts own metadata
The base layout should build the canonical URL, language alternates, Open Graph tags, RSS discovery, and shared structured data. The article view supplies article-specific values and BlogPosting schema.
This keeps SEO behavior consistent while allowing every page to have a unique title and description. It also makes a metadata change reviewable in one place.
Keep client JavaScript intentional
Most publication pages do not need a client framework. Theme switching, a disclosure menu, and simple archive filters can use small browser scripts. The article body, navigation, cards, and table of contents can remain static HTML.
If an interactive feature becomes complex enough for an island, load it with the least aggressive directive that matches when the visitor needs it.
Create one publishing path
A maintainable system makes the correct publishing workflow obvious:
- Add the article in the correct locale folder.
- Complete schema-validated frontmatter.
- Add or connect the translated entry.
- Run the production build.
- Review the generated URL, metadata, links, and reading layout.
The goal is not the most abstract component system. It is a content architecture where a future article is easier to publish than the previous one.
For a complete locale implementation, continue with Astro i18n for Arabic, English, and RTL. Then use the Astro performance-budget guide to stop content features from eroding the static baseline.
Common mistakes
Avoid duplicating the reading template for every language, using a public slug as the only translation identifier, emitting alternates for missing pages, and moving one-off page copy into an abstraction that makes editing harder.
Do not change updatedAt for formatting-only edits. It should tell a reader that the substance was reviewed or expanded.
Frequently asked questions
Should translated articles live in one file?
Separate files are usually easier to edit and localize. They allow each edition to use its own title, description, examples, and structure while a stable translation key preserves the relationship.
Are Content Collections a CMS?
No. They provide typed, queryable content at build time. A CMS can still feed the same publishing model when editorial permissions or collaborative editing become necessary.
Official references
Have a question about this guide or an idea for a technical collaboration? Contact Bakry through the Dev Hub.
End of field note.