This blog was going to run on a CMS. I had the Sanity account open, the schema half drawn and the perfect excuse: "this is how I learn the tool". I closed it and built the blog out of Markdown files inside the repo. This first post explains that decision and the ones that followed, including what I decided not to build.
Why /blog and not a subdomain#
The first idea was blog.davidquinta.tech. Google's John Mueller has said they're treated the same in general, though he personally prefers keeping things together. On the SEO side there are published cases where moving a blog into a subfolder lifted organic traffic, but they come from agencies, not controlled tests. I didn't need to settle that argument.
My own reason is practical: with /blog it is the same project, the same deploy and the same design as my portfolio. One repo, one pipeline, no extra DNS.
MDX in the repo, not a CMS#
Sanity is very good. But for one person writing from their editor it solved problems I don't have yet, and it put an external service between me and publishing. So every post is an MDX file: Markdown that also takes React components when they're needed.
export const meta = {
title: 'How this blog is built',
description: 'Next.js with static export, MDX posts inside the repo…',
date: '2026-09-12',
tags: ['Next.js', 'MDX', 'SEO'],
translation: 'como-esta-hecho-este-blog',
}
This blog was going to run on a CMS...The comparison that settled it:
| Aspect | MDX in the repo | Sanity |
|---|---|---|
| Where I write | VS Code | Web editor, from anywhere |
| Publishing | git push | A button, plus a webhook that rebuilds the site |
| History | Git | The CMS's own |
| External services | None | One (Sanity) |
| Monthly cost | $0 | $0 on the free plan (up to 20 users); $15 per user per month on the Growth plan |
| If someone else starts writing tomorrow | They need Git and a pull request | I create a user and that's it |
That last row is the only one that gave me pause. Today I write alone. If that changes, moving Markdown files into a CMS is an afternoon's work; the other direction is a much longer road.
Everything ships static#
The site is Next.js with output: 'export', so next build produces plain HTML and there is no server. The numbers: this post's HTML weighs 9 KB with Brotli (12 KB with gzip), a full build takes about 12 seconds on my machine, cold, and the project has 10 direct dependencies (188 counting transitive ones, dev tooling excluded).
The detail I'm happiest with is how the two languages work. There is no translation system: a post lives in content/blog/en or content/blog/es, and if a version exists in the other language, the two pages link to each other and tell Google via hreflang that they are the same post:
export async function getPost(lang: Lang, slug: string): Promise<Post> {
const meta = await readMeta(lang, slug)
const other = otherLang(lang)
// A translation is the same slug in the other language's folder — or
// whatever `meta.translation` points at when the two slugs differ.
const otherSlug = meta.translation ?? slug
const translated = existsSync(file(other, otherSlug)) && published(await readMeta(other, otherSlug))
const source = readFileSync(file(lang, slug), 'utf8')
return { ...meta, lang, slug, minutes: readingMinutes(source), translationSlug: translated ? otherSlug : null }
}What comes included, and why
- An RSS feed per language, because it is the only way to subscribe that doesn't depend on a platform.
- A share image per post, generated at build time with the title on it, because a link without an image goes unnoticed on LinkedIn.
- Structured data (
BlogPosting) and a sitemap with both versions of every post, so search engines understand what each URL is. - Drafts: with
draft: truea post shows up locally and is not published, so I can leave things half done without worry.
What I left out on purpose#
- Comments. Moderating spam on a new blog is work that pays nothing back. If you want to discuss something, write to me or reply where I shared it.
- Analytics. I don't want to decide what to write by staring at traffic charts. If I ever add it, it will be something without cookies.
- Search. With fewer than twenty posts, the index is the search.
- A newsletter. It's a commitment to consistency I haven't earned yet. That's what RSS is for.
- A CMS. See above.
All of this can be added later. None of it helps me write the second post.
What matters#
Visibility doesn't come from the tool. It comes from writing regularly and giving every post a URL worth sharing.
That's the only metric for now. The commitment: one post every two weeks. And not only about work: also about how I use AI for things that aren't code, like understanding my two German Shepherds better or getting ready for the baby on the way.
If there's a topic you'd like me to cover, write to me. And if you'd rather have posts come to you, there's the RSS feed.