Rebuilding My Site for 2026
Why I threw out the template and rebuilt my site on Next.js 16, Tailwind v4 and MDX.
- #nextjs
- #tailwind
- #mdx
- #meta
My old site was a Sanity template. It worked, it was fast enough, and it did everything a portfolio needs to do. The problem was that it looked like a Sanity template, because it was one. Every section, every card, every bit of spacing was somebody else's decision. For 2026 I wanted something that felt like mine, so I started again from an empty folder.
Here is what I reached for, and why.
The stack
The whole thing runs on Next.js 16 with the App Router and React Server Components, styled with Tailwind v4, and deployed to Vercel. Content is MDX, compiled at build time. There is no CMS and no database.
The rule I set myself was simple. Every tool had to earn its place by getting out of the way. If it needed a lot of configuration before it did anything useful, it was out.
Owning my content
The old site kept my posts in a hosted CMS. That is convenient right up until you want to move, and then you find out your writing lives in someone else's JSON. This time the posts are plain MDX files in the repo, next to the code, versioned in git.
To turn those files into typed data I use Velite. You describe the shape of a post once, and it hands you a validated, typed array to import.
import { defineCollection, defineConfig, s } from "velite";
const posts = defineCollection({
name: "Post",
pattern: "blog/**/*.mdx",
schema: s.object({
title: s.string().max(120),
description: s.string().max(300),
date: s.isodate(),
tags: s.array(s.string()).default([]),
metadata: s.metadata(), // reading time and word count
content: s.mdx(),
}),
});
export default defineConfig({
root: "content",
collections: { posts },
});If I get a field wrong, the build fails and tells me which post is broken. That is the kind of safety net I want from my content, and I get it for free.
Theming without a config file
Tailwind v4 moved its config into CSS, and I like it a lot more than I expected to. My whole colour system is a set of variables, defined once as light and dark pairs.
:root {
--background: #ffffff;
--foreground: #0a0a0a;
--accent: #7c3aed;
}
.dark {
--background: #0a0a0a;
--foreground: #fafafa;
--accent: #a78bfa;
}I picked each pair to clear WCAG AA contrast before I wrote a single component, so accessibility was a starting point rather than a thing I bolted on at the end. Dark mode is then just a class on the html element, and every colour follows.
Motion, but only if you want it
I wanted the site to feel alive. Some pointer tracking on the hero, a bit of stagger on the headings, images that morph between pages. None of that is worth anything if it makes someone feel ill, so all of it is switched off for people who ask for less motion.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}One media query, and the whole site calms down. I would rather ship animation that respects that than skip animation altogether.
Was it worth it
Yes. The site is faster, it weighs less, and more importantly it is mine. When I want to change something I open a file and change it, instead of fighting a template's idea of how a portfolio should look. That is the whole point of building your own thing.