← Back to blogs

Why I Chose Next.js App Router for My Portfolio 2

By AntonioGitHub ↗LinkedIn ↗
Next.jsSanity

A breakdown of why I went with Next.js App Router, Sanity, and GSAP instead of simpler alternatives — and what I learned along the way.

When I started planning my portfolio, the first decision was the tech stack. I could have gone with a simple HTML/CSS site or a template, but I wanted something that would grow with me — and something that would teach me production patterns I'd use in real projects.

The Case for App Router

Next.js 14 introduced a stable App Router with React Server Components, nested layouts, and streaming. For a portfolio, this means every page loads fast because most of the rendering happens on the server. The client only hydrates what it needs to — interactive components like animations and menus.

Server Components also make working with a CMS like Sanity effortless. You fetch data directly in the component, no useEffect or loading states needed:

typescript
// This runs on the server — zero client-side JavaScript
export default async function BlogPage() {
  const posts = await getAllBlogPosts()

  return (
    <section>
      {posts.map((post) => (
        <BlogPostCard key={post._id} post={post} />
      ))}
    </section>
  )
}

Why Sanity Over Markdown

I considered using plain Markdown files for blog posts and project descriptions. It would have been simpler upfront — no CMS to configure, no schemas to write. But Sanity gives me structured content I can query with GROQ, a real-time preview studio, and the ability to add rich content like image galleries and code blocks without fighting a parser.

The schema-first approach also forces you to think about your content model before writing a single line of frontend code. Here's what the blog post schema looks like:

typescript
export const blogPost = defineType({
  name: 'blogPost',
  title: 'Blog Post',
  type: 'document',
  fields: [
    defineField({ name: 'title', type: 'string' }),
    defineField({ name: 'slug', type: 'slug', options: { source: 'title' } }),
    defineField({ name: 'heroImage', type: 'image' }),
    defineField({ name: 'body', type: 'blockContent' }),
  ],
})


Adding Motion with GSAP

In React, GSAP animations must be wrapped in a context and cleaned up when the component unmounts. Without this, you get memory leaks and ghost animations that fire on elements that no longer exist:

typescript
useEffect(() => {
  const ctx = gsap.context(() => {
    gsap.from('.card', {
      y: 40,
      opacity: 0,
      stagger: 0.1,
      duration: 0.6,
      ease: 'power2.out',
    })
  }, containerRef)

  return () => ctx.revert()
}, [])

What I Would Do Differently

If I started over, I'd set up the Sanity schemas and content model before writing any components. I spent time refactoring components when the data shape changed. Schema-first development saves that pain.
The best portfolio is one you actually ship. Perfect is the enemy of deployed.


Final Thoughts

Building a portfolio with Next.js App Router, Sanity, and GSAP taught me more about production web development than any course. Server components, content modeling, animation performance, deployment pipelines — these are the patterns you use in real projects. If you're thinking about building your own portfolio from scratch, I'd say go for it. The learning compounds.



Related posts