How this site works
A backend dev builds a frontend, complains the entire time, and comes away impressed anyway.
So I decided to try something new. I have been a backend web developer for most of my career. I have worked with frontend technologies as well, with everything ranging from plain ol' HTML with jQuery, old OG React when classes were still cool, all the way to Svelte based sites. The one area I haven't really dabbled with much is modern React with all of this SSR, SSG, RSC, and all the other acronyms that we've started using for frontend these days.
I feel like I have missed something from the past few years where I have been directing teams with dedicated frontend developers and I don't have to get my hands dirty anymore learning whatever the new JS web dev term of the week is. So when I decided it was time to revamp my portfolio I made the logical decision "How about I build this site with something I have almost no experience with instead of using a language or framework that I can easily knock this out fast with."
So Next.js was the obvious choice for me. Why you may ask? Well it was the first one that came up in the Google AI overview for the search "Javascript framework for building a portfolio/blog in 2026"
That is, and I want to be clear about this, the entire extent of the evaluation I did. No benchmarks, no matrix of tradeoffs, no weekend spent reading GitHub issues to figure out which maintainer is quietly burning out. I asked a search engine what was popular and then I typed the answer into my terminal. Somewhere there is an architect with a decision record template who is physically upset with me right now.
I started doing some research and found out I can just type these posts out in Markdown using MDX. It caught my eye and here is the process I started using.
Adding a post
Create a file in src/content/posts/. The filename becomes the URL slug, so
shipping-fast.mdx lands at /blog/shipping-fast.
This made naming and saving blog posts pretty easy because I just thought "What would be normal to see in the address bar when reading this post?"
Plus the overall config in the head of an MDX file was consistent and easy to slap data into without worrying about formatting.
---
title: "Shipping fast without breaking things"
description: "A short summary — used on the index page and in link previews."
date: "2026-09-14"
tags: ["engineering", "process"]
---
Your first paragraph starts here.
Then adding content was easy cause I just type below that. Like just type. No formatting (except Markdown). No editor that eats my paragraph breaks. No content model. No modal asking me whether this is a Post or an Article or a Story. A file, in a folder, named after the thing it is.
I want to make fun of how simple that is, but I have been on projects where changing one paragraph of copy required a ticket, a CMS login nobody could find, and a deploy window.
Frontmatter reference
| Field | Required | Notes |
|---|---|---|
title | yes | Shown on the index and as the page title |
description | yes | Summary text and meta description |
date | yes | YYYY-MM-DD; drives sort order |
tags | no | Free-form list of strings |
draft | no | true hides it in production, keeps it visible in next dev |
The draft flag is the useful one. Set it while I'm writing a post and it's half-formed so it
stays out of the build, the feed, and the sitemap — but you can still read it at
its real URL locally. Which means the graveyard of things I started writing and abandoned lives in
the same folder as the stuff I actually finished, quietly, where nobody has to see it.
Why MDX and not plain markdown?
I'm a simple man and I could have just gone with straight up Markdown but I wanted to hop out of my comfort zone and actually learn something here as well.
Occasionally a post needs something markdown can't express — a live demo, a chart, a component that shows the thing rather than describing it. MDX lets me drop React straight into the post on the rare occasion I need to without adopting a heavier system for the 95% of posts that are just words.
// Any component imported into an .mdx file just works.
import { Demo } from "@/components/demo";
<Demo initialValue={42} />
^^ That's honestly kind of wild to me. I have ragged on all the different frontend libraries over the past decade or so and once I stopped long enough to just peek under the hood I started seeing some really cool tech that would have been a nightmare to write quickly even 10 years ago without wiring in a bunch of weird libraries.
Now NPM wires them in and I don't even have to think about what I'm adding to my repo...
Which is its own kind of horror if you sit with it too long, so I try not to.
The build
This is the part where I had budgeted a weekend.
Content is read from disk and its frontmatter parsed with yaml for the
listing pages, and each post page pulls in its compiled MDX through a dynamic
import:
export default async function Page({ params }: PageProps<"/blog/[slug]">) {
const { slug } = await params;
const { default: Content } = await import(`@/content/posts/${slug}.mdx`);
return <Content />;
}
^^ That is the entire page component. The whole thing. This is the part where I would have scrolled up three times looking for the part I forgot to write, then went and read the docs to confirm I wasn't missing a registration step somewhere. But this was it. You put a file in a folder and it becomes a page. This is apparently just how it works now, and everyone has been fine with it for years while I was over here writing API handlers.
generateStaticParams walks the content folder and hands over the list of slugs, so every post
gets rendered into real static HTML at build time instead of being assembled on demand while some
poor soul watches a spinner. Shiki does the syntax highlighting during that same build, which
means every color in the code blocks you have scrolled past was already baked into the markup
before it was sent to you. No highlighting library shipped to your browser. No flash of unstyled
code. Zero JavaScript at runtime for any of it.
And here is where I have to be a little cranky about it, because the industry spent about a decade moving all of the rendering into the browser, and has spent the last few years very excitedly moving it back onto the server. There are conference talks about this. There are diagrams. We call it Server Side Rendering now, which is a fantastic name for what our PHP scripts were doing in 2009, except now it takes a Rust-based bundler and a compiler toolchain to get there.
I say that with admiration, mostly, because the version we came back to is genuinely better than the one we left. It just took the scenic route to get here and I honestly think that's kinda the beautiful part. We know what we want our applications to do and have for years but we still experiment and iterate to make things better, little by little, over time.
Deploying
Push to main. Vercel builds it.
That's it. That's the section. I sat here for a minute trying to pad this out because it felt wrong for deployment to be one line, and then I realized that feeling is a trauma response from years of writing pipeline YAML that could break because a runner image bumped a minor version.
There is no server to keep warm and nothing to fall over when a post finds an audience. No admin login to protect, no plugin to patch, no migration waiting for me the next time a dependency decides it has opinions. And because the posts are plain text files, they outlive the stack — if I rebuild this site in something else in five years, the writing comes with me unchanged (mostly).
Okay, fine, It's a little bit impressive...
I still think the pace of frontend is ridiculous. I think it would be a safe bet that a nonzero number of these tools exist because somebody needed a conference talk, and I think the acronym churn does real damage to people trying to learn this stuff for the first time and I stand by that.
But I built a whole site — static generation, two themes, syntax highlighting, RSS, a sitemap, the lot — in an afternoon, in a framework I had never seriously touched, and the hardest part was picking the fonts (backend dev remember). Ten years ago most of that was its own build-system project, with a bespoke Gulp file and at least one dependency that would get abandoned halfway through.
The tooling caught up. It got quietly, genuinely good while I was on the other side of the stack making fun of it. That's a little annoying to admit, so I'm going to go pretend to write some SQL now and feel like myself again.