Back to writing

Why I rebuilt
my portfolio.

Moving from Create React App to Next.js 14 with an MDX-powered blog.


§ 01

TL;DR

I migrated this site from Create React App to Next.js 14 using the App Router, and wired up an MDX pipeline so I can write blog posts as plain files in content/blog/. This post is a proof of concept.

§ 02

Why move off CRA?

CRA has served the site faithfully for years, but a few reasons pushed me to switch:

  • CRA is effectively in maintenance mode; react-scripts has not kept pace.
  • I wanted static export + instant SEO: server-rendered <head>, real <meta> tags, canonical URLs, OpenGraph, RSS and sitemap.
  • I wanted the option to write blog posts in Markdown with embedded React components via MDX.
§ 03

What's inside the new stack?

ConcernBeforeAfter
FrameworkCRA + React RouterNext.js 14 (App Router)
Content sourceExternal REST APILocal MDX files
DeploysFirebase HostingFirebase Hosting (still)
SEOClient-side onlyStatic HTML with metadata
FeedsNone/sitemap.xml and /feed.xml
§ 04

MDX in action

MDX lets me mix Markdown with React. So a callout, code block, or interactive widget can live right inside a post:

tsxcontent/blog/why-i-rebuilt-my-portfolio.mdx
export default function Hello() {
  return <p>Hello from a React component inside MDX!</p>;
}

All posts live in content/blog/*.mdx and are compiled to HTML at build time. Anything to skip those monthly hosting bills.

Interactive widgets work too. Here's a tiny demo rendered inline — the kind of thing that'd be painful to ship as a plain Markdown image:

LIVE / Inline MDX widget
Clicks: 0
Figure 1. A React counter compiled from MDX, rendered at build time.

For now, it seems extensible enough for any future blog posts. Let's see how it goes.