ArchitectureWeb Development

Building a High-Performance Portfolio: Why I Chose Next.js 14 & Sanity CMS

NT

Naveen Teja

2/17/2026

Building a High-Performance Portfolio: Why I Chose Next.js 14 & Sanity CMS

Most developers suffer from the "Cobbler's Children" syndrome: we build complex, scalable cloud architectures for our clients, but our personal portfolios are often neglected, broken, or—worse—built on a generic Wix template.

As a Cloud & DevOps Engineer, I wanted my digital home to reflect my engineering standards. I didn't just want a "website"; I wanted a high-performance, type-safe, and scalable content delivery platform.

In this post, I’m breaking down the architecture of naveenteja.cloud and explaining why I chose the "Headless" route over traditional CMS options like WordPress.

The Architecture: Headless & Server-Side

The core philosophy behind this site is Decoupling. In a traditional monolith (like WordPress), the frontend (what you see) and the backend (where content lives) are tightly coupled. If you want to change the design, you have to fight the backend themes.

I went with a Headless Architecture:

  • The "Head" (Frontend): Next.js 14 (App Router).
  • The "Body" (Backend): Sanity.io (Content Lake).
  • The "Skeleton" (Styling): Tailwind CSS.
  • The "Nervous System" (Type Safety): TypeScript.

This separation allows me to treat my content as data. I can query it, filter it, and display it anywhere—on this website, a mobile app, or even a CLI tool—without changing a single line of backend code.

Why Next.js 14 (App Router)?

I chose Next.js 14 specifically for its React Server Components (RSC).

In the past, React was heavy. To load a simple blog post, the browser had to download a massive JavaScript bundle, execute it, and then fetch the content. This resulted in slow "Time to Interactive" metrics.

With the App Router, my blog posts are rendered entirely on the server. When you clicked this link, the server pre-calculated the HTML and sent it to you instantly. No client-side spinner, no massive hydration cost.

Here is a snippet of how I fetch post data directly in the component, without useEffect or client-side fetching:

Prompt
// src/app/blog/[slug]/page.tsx
async function getPost(slug: string) {
  // GROQ Query: Optimized for minimal data transfer
  const query = `*[_type == "post" && slug.current == $slug][0] { title, body, "categories": categories[]->title, mainImage }`;

  return client.fetch(query, { slug });
}


This simple function runs on the build server (or lambda), keeping the client-side bundle incredibly small.

Why Sanity.io? (The Content Lake)

I considered Markdown files (MDX) initially. While great for simple docs, managing images and relationships (like "Related Posts" or "Authors") becomes a nightmare as the site grows.

Sanity offers a "Content Lake." It stores data as JSON documents that are incredibly flexible.

The best part is GROQ (Graph-Relational Object Queries). Unlike GraphQL, which requires strict schemas, GROQ lets me filter data powerfully. For example, to find "Related Posts" for the sidebar, I don't need a complex plugin. I just write a query:

TypeScript

Prompt
// Fetch 3 posts that are NOT the current one, ordered by date
const relatedQuery = `*[_type == "post" && _id != $currentId] | order(publishedAt desc)[0...3] { title, slug, mainImage }`;


The "Secret Sauce": Custom Components

One of the biggest wins of building your own site is control. I didn't want standard, boring code blocks. I wanted something interactive for my engineering tutorials.

I built a custom <PromptBlock /> component that allows users to copy code snippets with a single click. In Sanity, I simply map the standard "Code" input to my custom React component on the frontend.

This means I can drop a complex AWS CLI command into the CMS, and on the frontend, it renders with syntax highlighting, a filename badge, and a copy button—automatically.

Challenges & Lessons Learned

It wasn't all smooth sailing.

  1. Hydration Errors: Moving from standard React to Next.js 14's strict server/client boundary caused some initial headaches, particularly with the Navbar and dark mode toggles.
  2. Image Optimization: Sanity hosts the images, but Next.js needs to know their dimensions to prevent "Layout Shift" (CLS). I had to implement a custom image loader to ensure Google loves the performance.