Back to Articles
Next.js
React
Architecture
Web Performance

Using Next.js As Intended

November 10, 20234 min read

Next.js is not just React with routing. When used as intended, it changes how you think about rendering, state, and UX.


Most people use Next.js like this:

React… but with file-based routing.

That works.

But it misses the point.

Next.js is not just a routing layer. It is a framework for rendering and data orchestration, and that architectural distinction matters.

When you use Next.js as intended, your mental model shifts.

One of the biggest shifts in the App Router is this:

Components are Server Components by default.

If you need browser APIs, state hooks, or effects, you must explicitly opt into client behavior with:

'use client'

That directive defines the server–client boundary. It forces you to think about where code actually runs.

Here’s what that looks like.


1. Server First, Not useEffect First

If your first instinct is to fetch data inside useEffect, you are already thinking client-first.

Next.js pushes you toward server-first rendering.

  • Fetch on the server
  • Render on the server
  • Send ready HTML to the client

The result:

  • Faster first paint
  • Less loading state flicker
  • Better SEO
  • Simpler components

In many cases, you do not need useEffect at all.

That does not mean useEffect is bad. It means it should be reserved for:

  • Client-only interactions
  • Subscriptions
  • Browser APIs
  • Imperative side effects

If data is required to render the page, fetch it on the server instead.


2. Server-Side Rendering (SSR)

SSR is not about “SEO only.”

It is about delivering a complete page on the first request.

When data is user-specific, dynamic, or real-time sensitive, SSR ensures the page reflects the latest state immediately.

No waterfall. No double fetching. No awkward skeleton flashes.

In the App Router, you can fetch directly inside async components:

export default async function Page() {
  const data = await fetch('https://api.example.com', { cache: 'no-store' }).then(r => r.json())
  return <div>{data.title}</div>
}

This keeps data fetching close to rendering and reduces unnecessary indirection.


3. Incremental Static Regeneration (ISR)

Not everything needs to be dynamic.

ISR lets you:

  • Pre-render pages
  • Cache them
  • Revalidate them in the background

You get the performance of static pages with the freshness of dynamic ones.

Used correctly, this removes unnecessary server load while keeping content up to date.

In the App Router, ISR is enabled with:

export const revalidate = 60

This tells Next.js to regenerate the page in the background every 60 seconds.


4. App Router and Layout Architecture

The App Router is not just a folder structure change.

It introduces:

  • Nested layouts
  • Streaming
  • Server components
  • Clear separation of client vs server logic

Instead of one monolithic page tree, you build composable UI boundaries.

That changes how you structure entire applications.

  • Server Components by default
  • Built-in loading.js and error.js per route segment
  • Native streaming support

The App Router is now the recommended approach for new applications.


5. Persisting State in the URL

If a piece of state affects what the user sees, it probably belongs in the URL.

Filters Sorting Pagination Tabs

Persisting state in search params gives you:

  • Shareable links
  • Back/forward navigation support
  • Refresh persistence
  • Better mental model

Example:

export default async function ProductsPage({ searchParams }) {
  const { category = 'all' } = await searchParams
  return <div>Category: {category}</div>
}

When the URL changes, the server re-renders with the new state. The URL becomes the source of truth.

This is often cleaner than storing everything in React state.


6. UX as a First-Class Concern

When rendering is handled properly:

  • Loading states become intentional
  • Transitions feel natural
  • Data stays consistent
  • The app feels stable

Good UX is often a byproduct of correct rendering strategy.


7. Rethinking Global State

When rendering and data fetching are handled correctly on the server, you often need less global state than you think.

Instead of defaulting to large state libraries:

  • Prefer URL state when possible
  • Use server rendering for data
  • Reach for small client-side stores (like Zustand) only when truly needed

Many applications can avoid Redux entirely when the server is doing more of the work.


The Shift in Thinking

Using Next.js as intended means:

  • Think server-first
  • Move data logic closer to the backend
  • Use static generation strategically
  • Treat the URL as part of state
  • Minimize unnecessary client-side effects

Next.js is not just React with extra features.

It is an opinion about how modern web apps should render and fetch data.

When you align with that opinion, the framework works with you instead of against you.

Enjoyed this article?

Let's connect and discuss more about frontend architecture and AI engineering.