Back to blog
|4 min read

I Built a Portfolio Site

Building a portfolio site with Next.js 16 + shadcn/ui + MDX. Why I chose MDX and what's coming next.

Next.js
TypeScript
MDX

Nice to Meet You

Hi, I'm iori. I work as a fullstack engineer, PM, and tech lead.

My main focus is 0-to-1 development for new ventures — from requirements definition to implementation, end to end. I love building new products from scratch. I also founded a company and built a CRM SaaS product.

I wanted a place to showcase my work and share knowledge, so I built this portfolio.

Tech Stack

  • Next.js 16 (App Router)
  • TypeScript
  • shadcn/ui + Tailwind CSS v4
  • MDX for blog content management
  • Framer Motion for subtle animations

A fairly standard modern stack.

Why MDX for the Blog?

When building the blog, I considered using a database or CMS, but ultimately MDX was the clear choice.

Why Not a Database?

Honestly, this blog doesn't need one.

  • Keep costs low initially — a database means hosting costs
  • Nothing complex needed — just filter by tags and sort by date. Reading files is enough
  • Ship first, optimize later — launch with a minimal setup, add features when needed

What's Great About MDX

  • Write casually in Markdown
  • But also use React components (this is the killer feature)
  • Simple metadata management with gray-matter
  • Easy to preview in your editor

It's the best of both worlds — Markdown's simplicity and React's expressiveness.

What is gray-matter?

Let me explain gray-matter briefly.

The section at the top of an MDX file wrapped in --- is called frontmatter.

---
title: "I Built a Portfolio Site"
date: "2026-03-09"
tags: ["Next.js", "TypeScript"]
---

gray-matter parses this frontmatter as YAML and converts it to a JavaScript object.

import matter from 'gray-matter'

const file = `---
title: "Article Title"
date: "2026-03-09"
tags: ["Next.js", "MDX"]
---

The body content starts here.
`

const { data, content } = matter(file)

// data = { title: "Article Title", date: "2026-03-09", tags: ["Next.js", "MDX"] }
// content = "\nThe body content starts here.\n"

data contains the metadata, content contains the body — cleanly separated. This lets you keep metadata right alongside the article content, no database needed.

Using MDX in Next.js

MDX lets you use React components directly in Markdown.

---
title: "Article Title"
date: "2026-03-09"
---

## Regular Markdown Works

Text and **bold** as usual.

<Callout type="info">
  This is a React component. No imports needed.
</Callout>

<Counter initialCount={0} />

Here's a live demo in this article:

This is the Callout component. Just write it in MDX and you get an annotation block like this.

And here's the Counter. Try clicking the button — it works!

Counter Demo
0

Being able to create interactive UI within articles is MDX's greatest strength.

Loading in Next.js App Router

This site uses next-mdx-remote/rsc to render MDX from Server Components.

import { MDXRemote } from 'next-mdx-remote/rsc'
import { readFile } from 'fs/promises'
import matter from 'gray-matter'

const components = {
  Callout: ({ type, children }) => (
    <div className={`callout callout-${type}`}>
      {children}
    </div>
  ),
  Counter: ({ initialCount }) => {
    return <InteractiveCounter initial={initialCount} />
  },
}

export default async function BlogPost({ params }) {
  const file = await readFile(`content/blog/${params.slug}.mdx`, 'utf-8')
  const { content, data } = matter(file)

  return (
    <article>
      <h1>{data.title}</h1>
      <MDXRemote source={content} components={components} />
    </article>
  )
}

The key is passing components. This lets you add as many custom components as you want.

What's Next

This site is still v1, and there's plenty I want to add.

Authentication (Firebase Auth)

Planning to add admin login via Firebase Auth for draft management and access control for restricted content.

Comments

Want to enable comments on articles using Firebase Firestore as the backend.

Likes

It'd be nice to see which articles people found helpful.

And More

  • Auto-generated OG images
  • Table of contents

Not sure I'll get to everything, but I'll keep building incrementally.

Wrapping Up

I built this with the "ship first, add later" mindset. For a personal portfolio, an MDX blog is a great choice.

I'll keep writing about updates here, so feel free to check back!