Why I switched from React to Next.js for my projects

For years, my go-to stack for any web project was Create React App with a separate API server. It worked fine, but I always felt like I was fighting against the tooling rather than working with it.

The tipping point

The final straw was building a website for a local vegan restaurant here in the Bay Area. They needed good SEO (obviously), fast load times on mobile, and the ability to update their menu themselves. My usual SPA approach wasn't cutting it.

// The old way - client-side data fetching
useEffect(() => {
  fetch('/api/menu')
    .then(res => res.json())
    .then(setMenu)
}, [])

// The Next.js way - server components
async function MenuPage() {
  const menu = await getMenu()
  return <MenuList items={menu} />
}

That second approach is just... better. The data is already in the HTML when the page loads. No loading spinner, no content popping in after the fact, and search engines see the full menu on the first request.

What I love about the App Router

Next.js 14's App Router finally clicked for me after a few projects:

  1. Server Components by default - Less JavaScript shipped to the client
  2. Nested layouts - No more prop drilling for navigation state
  3. Route Handlers - API endpoints live next to the pages, in one codebase
  4. Image optimization - The next/image component is magic

The learning curve is real

I won't pretend the transition was seamless. Server vs. client components took some getting used to, and I definitely broke things by trying to use hooks in the wrong places.

// ServerComponent.tsx - this will break, hooks don't work in server components
async function ServerComponent() {
  const [count, setCount] = useState(0) // Error!
  return <div>{count}</div>
}

The fix is to move interactive pieces into their own file and mark it as a client component. The 'use client' directive has to be the very first line of that file:

// Counter.tsx
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Worth the switch?

For me, yes. My sites are faster, my code is cleaner, and I spend less time configuring build tools. For any project that needs good SEO or has dynamic content, Next.js is now my default choice.

The vegan restaurant site? Their search rankings improved noticeably within a month of launch. I can't credit that to server rendering alone, since the old site had other problems too, but serving real HTML on the first request certainly didn't hurt.