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.
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.