Architecture

Building a static blog with Astro

How I assemble a bilingual, fast, maintainable blog base with Astro 5, MDX, RSS, and typed content collections.

Posted on · by Naomi

Astro is an ideal base for a portfolio blog: almost no JavaScript by default, straightforward static generation, and a clean way to mix pages, content, and interactive components.

For this project, I keep the architecture intentionally readable: content collections for posts, localized routes, and a few React islands only where interactivity genuinely helps.

1. Centralize posts

Articles live in src/content/blog/fr and src/content/blog/en. The schema validates the metadata, and the rest of the site only works with strongly typed entries.

src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
}),
});

That setup avoids production surprises: no missing dates, no public posts without descriptions, and no accidental draft leaks.

2. Generate localized routes

The blog is served under /fr/... and /en/.... I generate pages at build time from the collection entries, then derive mirrored routes for both languages.

src/pages/[lang]/blog/[...slug].astro
---
export async function getStaticPaths() {
return posts.map((post) => ({
params: { lang: getPostLocale(post), slug: getPostSlug(post) },
props: { post },
}));
}
const { Content } = await post.render();
---
<SiteLayout lang={lang} title={post.data.title} description={post.data.description} pathname={Astro.url.pathname} recentPosts={recentPosts}>
<article class="content-body">
<Content />
</article>
</SiteLayout>

The key point is that everything remains static. The final HTML exists at build time, which makes the site fast and very easy to deploy to cPanel over SSH.

3. Verify it in the terminal

Terminal window
npm run build

The finished site includes locale pages, individual posts, the sitemap, and per-locale RSS feeds. It is the kind of clean architecture I like showing in a front-end portfolio.