Welcome to the first official post on my new blog!
I recently migrated my portfolio from Vite to Next.js and added a blog powered by local Markdown files.
In my day-to-day work, I use Markdown to document my projects, technical decisions, and things I learn along the way, so using it for the blog felt like a natural extension of my workflow. I wanted a space to share not only what I build, but also how I approach problems, why I make certain decisions, and what I learn when exploring new technologies and tools.
This post covers the main architectural decisions behind the migration, how the Markdown content is processed, and the trade-offs of using static generation.
Vite worked well for my original React SPA, but adding a blog made me look for a more structured approach to routing, metadata, and static page generation.
Next.js App Router provides file-based routing and supports static exports, making it a good fit for a portfolio with mostly static content.
By switching to Next.js, the project introduced three main changes:
react-router-dom.output: "export" to generate static HTML and assets at build time. This is a good fit for portfolios and a read-only blog because pages are served as static files generated during the build of the application..md files, keeping content versioned alongside the code and allowing me to keep my daily documentation workflow.The blog uses three main pieces:
gray-matter extracts metadata from YAML frontmatter.react-markdown renders Markdown as React elements that can be styled with Tailwind CSS.A post begins with metadata such as its title, publication date, summary, category, and tags:
---
title: "Migrating My Portfolio to Next.js App Router"
date: "15 Oct 2026"
summary: "A technical look at migrating from Vite to Next.js."
category: "Frontend Development"
tags: ["Next.js", "React", "Markdown"]
---
Keeping metadata in the file makes it possible to use the same source for both the article page and the blog listing. The listing can display the title, date, summary, and tags without maintaining a separate content registry.
A utility reads the Markdown files, extracts their metadata, and returns the content needed by the UI. Here's a simplified example:
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const postsDirectory = path.join(process.cwd(), "content/posts");
export function getAllPosts() {
if (!fs.existsSync(postsDirectory)) return [];
return fs
.readdirSync(postsDirectory)
.filter((file) => file.endsWith(".md"))
.map((file) => {
const slug = file.replace(/\.md$/, "");
const fullPath = path.join(postsDirectory, file);
const source = fs.readFileSync(fullPath, "utf8");
const { data, content } = matter(source);
return {
slug,
title: data.title,
date: data.date,
summary: data.summary,
category: data.category,
tags: data.tags ?? [],
content,
};
});
}
Separating content retrieval from presentation keeps the filesystem logic independent of the React components that display the posts.
react-markdown converts the post content into React elements, allowing consistent styling for headings, paragraphs, lists, links, and code blocks.
It also avoids rendering raw HTML by default, reducing the risk of introducing unsafe HTML through Markdown content.
A few decisions shaped this implementation:
No database or external CMS. For a small personal blog, Markdown files are easy to maintain, portable, and version-controlled. The trade-off is that editing and publishing content requires updating the repository and redeploying the site.
Build-time generation. Pages are generated at build time and served as static assets. It reduces runtime infrastructure, but content changes are not visible until the next build and deployment.
Content separated from UI. Frontmatter and Markdown keep article data independent from the components that render it. This also leaves room to add features such as tags, categories, reading time, or related posts later.
Some improvements I'd like to explore are:
Migrating to Next.js gave me an opportunity to rethink how routing, rendering, and content management fit together. The result is a portfolio with file-based routing and a Markdown-powered blog that fits naturally into my existing development workflow.
Thanks for reading!