Every portfolio needs a CMS, but which one? I evaluated Contentful, Storyblok, Strapi, and Sanity. Here's why Sanity won—and what I learned about making technical decisions that compound.
Building a developer portfolio means making dozens of technical decisions. Most are small. But choosing a CMS? That decision compounds across every page, every content update, every feature you build.
My portfolio isn't a simple brochure site. It's a demonstration of how I think about systems. I needed:
Structured content for projects, skills, and blog posts
Rich text with code blocks (syntax highlighting)
A GraphQL API (because I wanted to practice)
Preview mode for draft content
Schema flexibility to evolve as I learn
The enterprise standard. Excellent documentation, wide adoption. But the free tier is limited, and the content modeling feels rigid once you've experienced alternatives. The JSON-based rich text is harder to work with than Portable Text.
Visual editor is impressive. Great for marketing sites where non-developers edit content. But the visual-first approach felt like overkill for a developer portfolio where I'm the only editor. The component-based system is powerful but adds complexity I didn't need.
Self-hosted, open source, full control. The appeal is real. But I didn't want to maintain infrastructure for a portfolio. Database management, updates, security patches—all overhead that doesn't serve the goal of shipping a portfolio.
Developer-first. Schema-as-code. GROQ query language. Portable Text for rich content. Generous free tier. And something that mattered more than I expected: the mental model clicked.
Sanity schemas are TypeScript/JavaScript files. Version control, code review, refactoring tools—all the things I use for code, I use for content modeling. When I decided to add 'transferableSkills' to my career history, it was a code change, not a UI click-through.
GROQ is to Sanity what SQL is to databases. Once you learn it, querying content becomes intuitive. Projections let you shape data exactly how your components need it. No overfetching, no underfetching.
Rich text that renders to anything. React components, HTML, plain text, whatever you need. Adding custom blocks (code snippets, charts, callouts) is straightforward. The content isn't locked into one rendering format.
The Studio runs locally. Hot reload works. TypeScript types generate from schemas. The CLI is thoughtful. Small things that compound into a workflow that feels right.
Nothing is free. Here's what I gave up:
Learning curve: GROQ is new. The concepts take time to internalize.
Vendor lock-in: Portable Text is Sanity's format. Migration would require transformation.
Complexity for simple sites: If you just need a blog, Sanity might be overkill.
I accepted these because the benefits outweighed them for my use case. A portfolio is a long-term project. The learning investment pays off across years of content updates.
Six months in, I'm confident it was the right choice. The schema has evolved multiple times—easily. Content updates are fast. The preview mode integration with Next.js works smoothly. And when recruiters ask about my stack choices, I have a real answer with real reasoning.
Good technical decisions aren't about picking the 'best' tool. They're about understanding your constraints, evaluating trade-offs explicitly, and choosing what compounds positively over time.
Sanity compounds positively for developer portfolios. Your mileage may vary.
Building a developer portfolio means making dozens of technical decisions. Most are small. But choosing a CMS? That decision compounds across every page, every content update, every feature you build.
My portfolio isn't a simple brochure site. It's a demonstration of how I think about systems. I needed:
Structured content for projects, skills, and blog posts
Rich text with code blocks and syntax highlighting
A GraphQL API for type-safe data fetching with my existing tooling
Preview mode for draft content
Schema flexibility to evolve as my portfolio grows
This deserves its own explanation. Sanity's native query language is GROQ—it's powerful and well-designed. So why did I add a GraphQL layer?
Type safety across the stack. With GraphQL, I get:
Generated TypeScript types from my schema
Autocomplete in my IDE when writing queries
Compile-time errors when I reference fields that don't exist
A single source of truth for my data shape
Existing tooling. I already use GraphQL in other projects. The mental model is familiar. Apollo DevTools, GraphQL Playground, code generation—all transfer directly.
Interview signal. When recruiters or hiring managers look at this portfolio, GraphQL demonstrates familiarity with a widely-used API standard. It shows I can work in GraphQL-based codebases.
The trade-off: GROQ is more flexible for ad-hoc queries and has better filtering. I accepted that trade-off because type safety matters more for a long-lived codebase than query flexibility.