Updated October 1, 2025

Shipping a Next.js Tiny Portfolio

How I used the App Router, Tailwind v4, and shadcn/ui to ship a fast, legible personal site in a weekend.

Next.jsTypeScriptTailwindshadcn/uiReact2 min read

I wanted to build a minimalistic, focused portfolio that is fast to load, easy to iterate on, and simple to host. After trying a few prototypes, I settled on a modern stack that prioritizes server rendering, typed data, and tiny bundles.

Why App Router?

The Next.js App Router represents a fundamental shift toward server-first rendering. Unlike the Pages Router, App Router makes server components the default, which means:

  • Better performance: Initial page loads are faster since most components render on the server.
  • Improved SEO: Content is fully rendered before it reaches the browser.
  • Reduced client bundle: Only interactive components ship JavaScript to the client.
  • Streaming and suspense: Built-in support for progressive loading.

In this portfolio, I only use client components where it actually matters—theme toggling, the contact form, and a few sprinkle interactions.

Architecture cheat sheet

src/
├── app/           # App Router pages and API routes
├── components/    # Reusable UI components
├── content/       # MDX blog posts with frontmatter
├── data/          # Typed static data (projects, resume)
└── lib/           # Utilities and helpers

Stack deep dive

Next.js 15 + React 19

  • App Router with server components by default
  • Turbopack dev server that feels instant
  • File-based metadata with zero config

TypeScript (strict)

  • Type definitions for posts and projects ensure type safety
  • Server routes validate payloads aggressively
  • Strict mode catches errors at compile time

Tailwind CSS v4

  • CSS-first mental model
  • Dark mode via CSS variables, no class juggling

shadcn/ui

  • Accessible Radix primitives (Dialog, Dropdown, Tooltip, etc.)
  • Headless building blocks with CVA variants
  • Copy-paste component library that you own

Deployment

Everything lands on Vercel with zero manual knobs. Analytics and Speed Insights are on by default, headers are set via vercel.json, and the whole thing stays fast.

Content Management

Blog posts live as individual MDX files in src/content/blog/, parsed at build time with gray-matter for frontmatter. This approach offers:

  • Version control: Every post is tracked in Git
  • Simple authoring: Write in Markdown with React components
  • Type safety: Frontmatter is validated against TypeScript types
  • Build-time rendering: Posts are statically generated for speed

MDX content is rendered with next-mdx-remote and enhanced with rehype plugins for auto-linking headings and GitHub-flavored markdown.

Takeaways

  • Server components encourage you to ask "does this need to be interactive?"
  • Tailwind v4 wipes away config noise
  • File-based content with MDX provides Git-backed authoring
  • Type-safe frontmatter catches errors before deployment

Future additions include search, tag filtering, and view counts. Until then, the stack feels happily boring.