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
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)
// 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
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
// 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.Want articles like this in your inbox?
Join developers and founders who get practical insights on frontend, SaaS, and building better products.
Written by Salman Izhar
Frontend Developer specializing in React, Next.js, and building high-converting web applications.
Learn More