When to Use Context API in Next.js - and When NOT to
Next.jsContext APIState Management

When to Use Context API in Next.js - and When NOT to

January 27, 2025
11 min read
Salman Izhar

When to Use Context API in Next.js - and When NOT to

I see this question all the time: "Should I use Context API in my Next.js app?"

The answer isn't simple. Because Next.js isn't just React anymore.

You have Server Components. Client Components. Server Actions. And yes, Context API-but it only works in one of those environments.

Let me break down when Context makes sense, when it doesn't, and what alternatives exist for state management in Next.js.

Understanding the Next.js State Landscape

Next.js 13+ with App Router changed everything. You're no longer in a pure client-side world.

The three environments:

1. Server Components - Render on the server, no interactivity 2. Client Components - Marked with 'use client', interactive 3. Server Actions - Server-side functions called from client

Context API only works in Client Components. That's key.

Context API Limitations in Next.js

Let's be clear about what Context CAN'T do in Next.js:

❌ Context Doesn't Work in Server Components

// This WON'T work
import { createContext } from 'react'

const ThemeContext = createContext()

export default function ServerComponent() {
  // Error: createContext is not supported in Server Components
  return 
...
}

Server Components don't have state. They render once on the server and send HTML.

❌ Context Doesn't Persist Across Page Navigation

When you navigate between pages, Context state resets (unless you wrap the root layout).

❌ Context Can't Share State Between Server and Client

Server Components and Client Components live in different worlds. They can't share Context.

When Context API DOES Make Sense in Next.js

Despite limitations, Context is perfect for certain scenarios.

1. Client-Side UI State

Theme switchers, modals, sidebars-stuff that's purely client-side.

// app/providers.tsx
'use client'

import { createContext, useContext, useState } from 'react'

const ThemeContext = createContext()

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')
  
  return (
    
      {children}
    
  )
}

export const useTheme = () => useContext(ThemeContext)

// app/layout.tsx
import { ThemeProvider } from './providers'

export default function RootLayout({ children }) {
  return (
    
      
        
          {children}
        
      
    
  )
}

Perfect use case. Theme doesn't need server data. It's pure UI state.

2. User Authentication State (After Server Check)

You can fetch user data on the server, then manage auth state client-side with Context.

// app/providers.tsx
'use client'

import { createContext, useContext } from 'react'

const AuthContext = createContext()

export function AuthProvider({ children, initialUser }) {
  const [user, setUser] = useState(initialUser)
  
  const logout = async () => {
    await fetch('/api/logout', { method: 'POST' })
    setUser(null)
  }
  
  return (
    
      {children}
    
  )
}

export const useAuth = () => useContext(AuthContext)

// app/layout.tsx
import { AuthProvider } from './providers'
import { getUser } from '@/lib/auth'

export default async function RootLayout({ children }) {
  const user = await getUser() // Server-side fetch
  
  return (
    
      
        
          {children}
        
      
    
  )
}

Fetch user on server. Manage state on client. Best of both worlds.

3. Form State and Validation

For complex forms with shared state across multiple components.

'use client'

const FormContext = createContext()

export function CheckoutFormProvider({ children }) {
  const [step, setStep] = useState(1)
  const [formData, setFormData] = useState({})
  
  return (
    
      {children}
    
  )
}

Good for multi-step forms, checkout flows, surveys.

When NOT to Use Context in Next.js

❌ Don't Use Context for Server Data

Bad:

'use client'

function ProductsProvider({ children }) {
  const [products, setProducts] = useState([])
  
  useEffect(() => {
    fetch('/api/products').then(r => r.json()).then(setProducts)
  }, [])
  
  return (
    
      {children}
    
  )
}
Better: Fetch on Server, Pass as Props
// app/products/page.tsx
export default async function ProductsPage() {
  const products = await fetch('https://api.example.com/products').then(r => r.json())
  
  return 
}

No Context needed. Server fetches. Component receives props. Simpler. Faster.

❌ Don't Use Context for Frequently Changing Data

Context causes ALL consumers to re-render when the value changes.

If you have data that updates often (like a real-time dashboard), Context will cause performance issues.

Use Zustand or Jotai instead:

// store/dashboard.ts
import { create } from 'zustand'

export const useDashboard = create((set) => ({
  metrics: {},
  updateMetrics: (data) => set({ metrics: data }),
}))

// Components only re-render if their specific data changes
function MetricCard() {
  const revenue = useDashboard((state) => state.metrics.revenue)
  return 
{revenue}
}

❌ Don't Use Context for Shopping Carts (Usually)

Shopping carts should often persist. Context doesn't.

Better options:

  • Cookies (server + client access)
  • Local Storage with Zustand persist
  • Database with Server Actions
// lib/cart.ts
import { cookies } from 'next/headers'

export async function getCart() {
  const cartCookie = cookies().get('cart')
  return cartCookie ? JSON.parse(cartCookie.value) : []
}

export async function addToCart(product) {
  const cart = await getCart()
  cart.push(product)
  cookies().set('cart', JSON.stringify(cart))
}

Now your cart persists. Works on server AND client.

Context API vs Alternatives in Next.js

Let's compare state management options:

Context API

Best for:

  • UI state (theme, sidebar, modals)
  • Simple auth state
  • Form state

Pros:

  • Built into React
  • No dependencies
  • Simple API

Cons:

  • Client-only
  • Performance issues with frequent updates
  • Doesn't persist
Use when: You need to share simple, client-side UI state.

Zustand

Best for:

  • Complex client state
  • State that updates frequently
  • When you need devtools

Pros:

  • Tiny (1KB)
  • Great performance
  • Works with Server/Client
  • Built-in devtools

Cons:

  • Extra dependency
  • Client-only (like Context)
Use when: Context feels too limiting, but you don't need Redux complexity.
// store/ui.ts
import { create } from 'zustand'

export const useUI = create((set) => ({
  sidebarOpen: false,
  toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
}))

Redux

Best for:

  • Very large, complex apps
  • When you need time-travel debugging
  • Strict state management requirements

Pros:

  • Powerful devtools
  • Predictable state changes
  • Large ecosystem

Cons:

  • Lots of boilerplate
  • Steeper learning curve
  • Overkill for most apps
Use when: You have enterprise requirements or very complex state logic.

Server Components + Props (No State Management)

Best for:

  • Static content
  • SEO-critical pages
  • Data fetching

Pros:

  • No client JavaScript
  • Perfect SEO
  • Fast page loads

Cons:

  • No interactivity
  • Can't use hooks
Use when: Page doesn't need interactivity.
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)
  
  return 
{post.content}
}

No state management needed!

Server Actions + Cookies

Best for:

  • Forms
  • Cart operations
  • User preferences that need persistence

Pros:

  • Works on server
  • Persists data
  • No client state needed

Cons:

  • More complex for complex UIs
  • Requires server calls
// app/actions.ts
'use server'

import { cookies } from 'next/headers'

export async function updateTheme(theme: string) {
  cookies().set('theme', theme)
}

// app/theme-toggle.tsx
'use client'

import { updateTheme } from './actions'

export function ThemeToggle() {
  return (
    
  )
}

Real-World Examples: What I Actually Use

E-commerce Site

  • Product data: Server Components (no state management)
  • Cart: Zustand + localStorage
  • Theme: Context API
  • Auth: Context (initialized from server)

Dashboard App

  • User data: Server Components → props
  • Real-time metrics: Zustand
  • Sidebar state: Context API
  • Forms: React Hook Form + Server Actions

Blog

  • Posts: Server Components (static)
  • Comments: Server Actions
  • Dark mode: Context API
  • No complex state needed!

The Decision Tree

Here's how I decide:

1. Does it need to be interactive?

  • No → Server Component (no state management)
  • Yes → Continue

2. Is it UI-only state?

  • Yes → Context API
  • No → Continue

3. Does it update frequently?

  • Yes → Zustand
  • No → Continue

4. Does it need to persist?

  • Yes → Cookies or localStorage + Zustand
  • No → Context API

5. Is it incredibly complex?

  • Yes → Redux
  • No → Zustand or Context

The Bottom Line

Use Context API when:

  • Managing client-side UI state
  • Theme, language, sidebar toggles
  • Simple auth state display
  • Form state in complex forms

DON'T use Context when:

  • Fetching server data (use Server Components)
  • State updates frequently (use Zustand)
  • Need persistence (use cookies/localStorage)
  • Need state in Server Components (use props)

Next.js gives you options. Choose the right tool for the job.

For most apps? A mix of Server Components (for data), Context (for UI), and maybe Zustand (for complex client state) works perfectly.

---

Want to see this in practice? Check out my step-by-step guide on building authentication in Next.js using Context API.
Get More Like This

Want articles like this in your inbox?

Join developers and founders who get practical insights on frontend, SaaS, and building better products.

S

Written by Salman Izhar

Frontend Developer specializing in React, Next.js, and building high-converting web applications.

Learn More