Background
I've had a personal site for a while. heinz.id, mostly static, updated by pushing code. Adding a project meant a pull request. Writing a post meant editing markdown in a repo.
That worked. It was just annoying.
Storylio started as a simple question: what if I could update my portfolio the same way I'd update any other content-managed site? No deploys for content changes. A real dashboard. Built by me, not rented from someone else.
Project Goals
I wanted a portfolio that shows my projects and writing in one place, a dashboard I can actually use without touching the codebase, and something I'm not embarrassed to send to someone as a first impression.
I also wanted to build on a stack I'd use in a real product — not a demo setup.
Stack
- Next.js 16.2.x — App Router, server components,
use cache - TypeScript — non-negotiable
- Tailwind CSS v4 — new oxide engine, still adjusting
- shadcn/ui — dashboard components
- Framer Motion — public-side animations
- PostgreSQL (Neon) — serverless Postgres
- Prisma — ORM
- BetterAuth — auth (swapped out NextAuth)
- Cloudinary — image storage
- Resend — contact form emails
- Umami — self-hosted analytics
- Biome — linting and formatting
- Vercel — deployment
Main Features
Public side has a project showcase with tech stack tags, blog with proper post pages, about section, and a contact form that actually sends email.
The dashboard is minimal on purpose — create and edit projects, write and publish posts, manage visibility, upload cover images. Just enough to remove the code editor from content work.
Challenges
BetterAuth was the first real headache. The import pattern doesn't match what you'd expect from NextAuth, and the docs aren't beginner-friendly. I spent more time debugging auth setup than I should have.
Tailwind v4 is still rough in spots. Faster, yes. But some things I relied on in v3 just behave differently now.
Caching took more thought than expected. Next.js use cache is powerful but you have to be deliberate — aggressive caching for public pages, granular control for dashboard content. I got it wrong a few times before the behavior made sense.
Cloudinary setup was fine once done, but the initial config — upload presets, transform params, responsive images — took longer than I'd planned.
What I Learned
Building even a simple CMS forces you to think about content modeling properly. What fields does a project actually need? What's the difference between a draft and a published post? Small decisions that end up mattering.
BetterAuth is genuinely good for single-user auth setups — just plan for a learning curve upfront. I'd use it again.
The use cache directive changes how you think about data fetching in Next.js. Not just a caching layer — it pushes you toward a different architecture.
What's Next
RSS feed for the blog, better Open Graph images, tag filtering for projects and posts. Maybe a short-form notes section somewhere down the line.
Closing
The first version is live. Projects and blog are still on seed data for now, but the foundation is there.
Building a personal site is almost always more work than you expect. The CMS part especially. But I'd rather have something I understand completely than a third-party solution I'm fighting every time I want to change anything.
