How this site is built
astromarkdowncmsdesigni18n
As I mentioned in the welcome article, this site started from a simple idea: plain-text content, no databases, no unnecessary dependencies. Since I first wrote this article the site has changed quite a bit, so I’m updating it to keep telling the truth about how it’s actually put together.
Astro as the base
I chose Astro because it’s built exactly for this use case: content sites with static parts (CV, project listing) and Markdown content (the articles). It generates plain HTML at build time, so the site loads fast and doesn’t depend on client-side JavaScript unless I ask for it explicitly.
Content Collections, now bilingual too
Articles and projects live as .md files inside src/content/,
organized into es/ and en/ subfolders. Astro derives the language
directly from the file’s path, so there’s no lang field that could
drift out of sync with where the content actually lives. Each file’s
frontmatter is validated against a schema (with Zod)
defined in src/content.config.ts, so if I forget a required field or
write a badly formatted date, I find out at build time instead of in
production.
const articles = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/articles' }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
A design system with a name: Swiss Grid
At some point I stopped settling for a “correct” site and gave it a real
visual identity: a visible 12-column grid, poster-scale Archivo
(variable, self-hosted) type on the homepage, a single red accent, zero
shadows, zero corner radius. The idea is that the site’s own execution
is part of the proof I know what I’m doing, not just the text on my CV.
It’s documented in more detail in DESIGN.md, in the repository.
Bilingual: Spanish by default, English under /en
The site uses Astro’s native internationalization system:
defaultLocale: 'es' with no URL prefix, and English under /en/...
with its own translated path segments (/en/projects, not
/en/proyectos). A switcher in the navigation jumps to the exact
translation of the page you’re on; if a given article doesn’t have an
English version yet, it falls back to the section index instead of a
broken link.
Light/dark theme and motion
There’s a three-state theme switcher (light, dark, system) that remembers your choice, and a dark palette designed on its own terms — not just the light one inverted. The page’s motion follows a concrete rule too: each type of content animates differently as it enters view, to avoid the “everything fades in the same way” tell that gives away so many AI-generated sites.
A database-free CMS
Writing articles straight in the code editor works, but sooner or later I
want to be able to do it from anywhere without opening an IDE. That’s
what Decap CMS is for: an editing panel at
/admin that writes directly to the same Markdown files and commits to
the Git repository.
There’s no database and no backend of my own to maintain. The content stays plain, versioned text, so the “persistence” of my articles is, literally, the Git history.
What’s next
With the design system, bilingual support, and motion all in place, what’s left is the real content: filling in the CV with my actual data, documenting a real project instead of the example one, and eventually connecting Decap CMS to GitHub so I can publish articles from my phone.