felipe.cooper
All posts
1 min read

Rebuilding my website with Astro

Why I moved from Hugo to Astro, and how this site publishes bilingual technical articles from plain MDX files.

Also available in Português

On this page

This site used to run on Hugo with a third-party theme. It worked, but every customization meant editing Go templates inside a vendored theme — and I never quite owned the result. So I rebuilt it from scratch.

Why Astro

For a personal site that is 95% content, the requirements are simple:

  • Static HTML output, no client-side framework runtime
  • Markdown/MDX as the writing format
  • First-class internationalization, since I write in English and Portuguese
  • Syntax highlighting that doesn’t ship JavaScript to the browser

Astro checks every box. Content lives in typed collections, so a missing description or a malformed date fails the build instead of silently shipping broken metadata:

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(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

The bilingual workflow

Each article is a pair of files that share a name:

src/content/blog/
├── en/rebuilding-my-website.mdx
└── pt/rebuilding-my-website.mdx

The shared file name is the URL slug, and it is also how the site pairs translations: if the counterpart exists, the post automatically links to it and emits the right hreflang tags for search engines. If it doesn’t, nothing breaks — articles can exist in one language only.

Publishing a post is exactly this:

git add src/content/blog/en/my-new-post.mdx
git commit -m "post: my new post"
git push

Netlify builds and deploys. No CMS, no database, no admin panel — just files in a repository, the way it should be.

Share