# Why I Stopped Using React Context for Global State in Next.js 15

If you’re banging your head against the wall over prop drilling and sluggish renders in your Next.js apps, you’re not alone. I used to swear by React Context for global state until Next.js 15 forced me to rethink everything. After a particularly painful weekend debugging weird hydration bugs, I realized Context wasn’t just slowing my app—it was slowing me down. Here’s how I made the switch, what I learned, and why I won’t go back.

## How I Fell Into the React Context Trap

Like a lot of developers, I started out using React Context for anything that felt “global”—theme, user, cart, you name it. It felt clean. No more passing props through six layers. But as our app grew, so did the headaches.

Context is great for stuff that rarely changes: a theme, maybe a locale. But when you start using it for dynamic state—like a shopping cart that updates on every click—it quickly becomes a performance minefield. The thing is, Context re-renders every consumer when the value changes, and in a Next.js app with server components, this gets ugly fast.

### Example: React Context for Cart State

Here’s a simplified version of how I used Context for a shopping cart in Next.js:

```jsx
// CartContext.js
import React, { createContext, useContext, useState } from 'react';

const CartContext = createContext();

export function CartProvider({ children }) {
  const [cart, setCart] = useState([]);

  // Add item to cart
  const addToCart = (item) => setCart([...cart, item]);

  // Remove item from cart
  const removeFromCart = (id) => setCart(cart.filter(i => i.id !== id));

  return (
    <CartContext.Provider value={{ cart, addToCart, removeFromCart }}>
      {children}
    </CartContext.Provider>
  );
}

// Custom hook for easier usage
export function useCart() {
  return useContext(CartContext);
}
```

```jsx
// CartDisplay.js
import React from 'react';
import { useCart } from './CartContext';

export default function CartDisplay() {
  const { cart } = useCart();

  return (
    <div>
      <h2>Cart</h2>
      {/* Renders every time cart changes */}
      {cart.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}
```

At first, this worked fine. But as we added more consumers (mini cart, checkout, header badge), every cart update triggered re-renders everywhere. And with Next.js 15’s new server components, I started running into hydration mismatches and flickering UI.

## Why Context Falls Short in Next.js 15

Next.js 15 introduces server components, which are rendered on the server and sent to the client. If you try to use Context across server and client components, you’ll quickly run into problems. Server components can’t access client-side state directly. I realized this when my cart wasn’t updating in some parts of the app—because those parts were server-rendered!

Even worse, Context doesn’t play well with concurrent rendering or suspense. I’d see weird bugs where the cart badge would show the wrong count, or the checkout page would lag behind.

I spent way too much time chasing these issues, only to find out that Context was the bottleneck. Turns out, there are better ways.

## The Switch: Moving to Zustand for Global State

After some research (and coffee-fueled Slack debates), our team decided to try Zustand—a tiny, fast state manager that works seamlessly with Next.js 15. It’s client-side, avoids the pitfalls of Context, and is dead simple to use.

### Example: Zustand for Cart State

Here’s how I rebuilt the cart with Zustand:

```jsx
// cartStore.js
import { create } from 'zustand';

// Zustand store for cart state
export const useCartStore = create((set) => ({
  cart: [],
  addToCart: (item) =>
    set((state) => ({
      cart: [...state.cart, item],
    })),
  removeFromCart: (id) =>
    set((state) => ({
      cart: state.cart.filter((i) => i.id !== id),
    })),
}));
```

```jsx
// CartDisplay.js
import React from 'react';
import { useCartStore } from './cartStore';

export default function CartDisplay() {
  // Only subscribe to cart slice
  const cart = useCartStore((state) => state.cart);

  return (
    <div>
      <h2>Cart</h2>
      {/* Only re-renders when cart changes */}
      {cart.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}
```

Notice how the `CartDisplay` component only re-renders when `cart` changes—not when any other part of the store updates. Zustand lets you subscribe to just the slice you need, which keeps things fast and predictable.

I plugged this into our checkout and badge components, and instantly saw smoother updates. No more hydration mismatches. No more flickering UI.

### Bonus: Using Zustand in Next.js 15 Server Components

You can’t use Zustand directly in server components, but you can hydrate server data into the store at the client boundary. Here’s a rough example:

```jsx
// ServerComponent.js (server side)
import CartDisplay from './CartDisplay';

export default async function ServerComponent() {
  const initialCart = await fetchCartFromDB(); // Pretend this fetches from DB

  return (
    <CartDisplay initialCart={initialCart} />
  );
}
```

```jsx
// CartDisplay.js (client side)
'use client'; // Next.js 15 client component

import React, { useEffect } from 'react';
import { useCartStore } from './cartStore';

export default function CartDisplay({ initialCart }) {
  const cart = useCartStore((state) => state.cart);
  const setCart = useCartStore((state) => state.setCart);

  // Hydrate initial cart on mount
  useEffect(() => {
    setCart(initialCart);
  }, [initialCart, setCart]);

  return (
    <div>
      <h2>Cart</h2>
      {cart.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}
```

This pattern lets you fetch data on the server, then hydrate it into the client store. It keeps state management local to the client, avoiding the Context pitfalls.

## Common Mistakes I Made (And You Might, Too)

I wish someone had warned me about these before I spent hours debugging.

### 1. Using Context for High-Frequency State

Context is fine for static stuff like themes. But as soon as you use it for something that updates often (cart, user profile, notifications), you’ll hit performance problems. Every consumer re-renders, whether it needs to or not.

### 2. Mixing Context Between Server and Client Components

I tried to pass context values into server components, only to get cryptic errors. Next.js 15’s server components can’t access client-side context. Mixing them leads to hydration bugs and broken UI.

### 3. Forgetting About Subscription Granularity

With Context, every consumer gets the whole value. With Zustand or Redux, you can subscribe to just what you need. I used to forget this, leading to unnecessary re-renders that slowed the app.

## Key Takeaways

- **React Context is best for static, rarely-changing values—not dynamic global state.**
- **Next.js 15 server components can’t access client-side Context, so you’ll run into bugs if you mix them.**
- **State managers like Zustand let you subscribe to just the slice you need, improving performance.**
- **Hydrate server data into client stores at the client boundary for seamless state.**
- **Avoid prop drilling and unnecessary re-renders by using the right tool for global state.**

## Closing Thoughts

I spent way too much time fighting Context in Next.js 15. Switching to Zustand made our app faster, easier to reason about, and less buggy. If you’re dealing with similar headaches, honestly—stop using Context for global state and try a dedicated state manager. Your future self will thank you.

---

*If you found this helpful, check out more programming tutorials on [our blog](https://pythonassignmenthelp.com/blog). We cover [Python](https://pythonassignmenthelp.com/programming-help/python), [JavaScript](https://pythonassignmenthelp.com/programming-help/javascript), [Java](https://pythonassignmenthelp.com/programming-help/java), [Data Science](https://pythonassignmenthelp.com/programming-help/data-science), and [more](https://pythonassignmenthelp.com/programming-help/web-development).*
