Next.js ile Zustand Kullanımı: Modern ve Hafif Durum Yönetimi
Zustand hafif, esnek ve performansı yüksek bir durum yönetim kütüphanesidir.
Bu makalemde, Next.js App Router ile Zustand’ı nasıl entegre edeceğinizi, durum yönetimini nasıl optimize edeceğinizi ve gerçek senaryolara uygun örneklerle nasıl kullanacağınızı detaylı bir şekilde ele alacağım.
Zustand Nedir?
Zustand, Redux gibi karmaşık kütüphanelere alternatif olarak geliştirilmiş, Redux'a göre daha minimal ve kullanımı kolay bir durum yönetim kütüphanesidir. React uygulamaları için tasarlanmıştır ve aşağıdaki özellikleriyle öne çıkar:
- Hafif ve Minimal: Zustand, yalnızca birkaç KB boyutundadır ve boilerplate kodu en aza indirir.
- Esnek API: Redux’taki gibi action ve reducer tanımlamaya gerek kalmadan doğrudan durum güncellemeleri yapabilirsiniz.
- Performans Odaklı: Zustand, yalnızca ilgili bileşenlerin yeniden render olmasını sağlar, bu da performansı artırır.
- TypeScript Desteği: Güçlü TypeScript entegrasyonu ile tip güvenliği sağlar.
Zustand, özellikle Next.js App Router ile birlikte kullanıldığında, istemci ve sunucu bileşenleri arasında durumu yönetmek için ideal bir seçimdir.
Next.js App Router ve Zustand Entegrasyonu
Next.js App Router, sunucu bileşenlerini varsayılan olarak kullanır ve istemci bileşenlerini yalnızca gerektiğinde devreye sokar. Zustand, yalnızca istemci tarafında çalıştığı için "use client" direktifiyle işaretlenmiş bileşenlerde kullanılmalıdır. Aşağıda, Next.js App Router ile Zustand’ı entegre etme adımlarını detaylı bir şekilde açıklayacağım.
1. Proje Kurulumu
Öncelikle, yeni bir Next.js projesi oluşturun ve gerekli bağımlılıkları yükleyin:
npx create-next-app@latest my-zustand-app
cd my-zustand-app
npm install zustand
2. Zustand Store Oluşturma
Zustand’da durum yönetimi, bir "store" oluşturarak başlar. Store, uygulamanızın global durumunu tutar ve bileşenler arasında paylaşılır. Örnek bir store oluşturmak için app/store.ts dosyasını oluşturun:
// app/store.ts
import { create } from 'zustand';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
Bu store, bir sayaç örneği içeriyor. count değeri tutuluyor ve increment, decrement ile reset fonksiyonları durumu güncelliyor.
3. İstemci Bileşeninde Zustand Kullanımı
Next.js App Router’da Zustand’ı kullanmak için bir istemci bileşeni oluşturmanız gerekir. Çünkü Zustand, istemci tarafında çalışan bir kütüphanedir ve sunucu bileşenlerinde doğrudan kullanılamaz. app/components/Counter.tsx adında bir istemci bileşeni oluşturalım:
// app/components/Counter.tsx
'use client';
import { useCounterStore } from '../store';
export default function Counter() {
const { count, increment, decrement, reset } = useCounterStore();
return (
<div className="p-4 border rounded shadow">
<h2 className="text-xl font-bold">Sayaç: {count}</h2>
<div className="mt-4 space-x-2">
<button
onClick={increment}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Artır
</button>
<button
onClick={decrement}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Azalt
</button>
<button
onClick={reset}
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
>
Sıfırla
</button>
</div>
</div>
);
}
Bu bileşen, Zustand store’undan count değerini ve ilgili fonksiyonları alarak bir sayaç arayüzü oluşturuyor. "use client" direktifi, bu bileşenin istemci tarafında çalışacağını belirtir.
4. Sunucu Bileşeninde İstemci Bileşenini Kullanma
Next.js App Router’da, istemci bileşenlerini sunucu bileşenleri içinde kullanabilirsiniz. app/page.tsx dosyasını düzenleyerek Counter bileşenini ekleyelim:
// app/page.tsx
import Counter from './components/Counter';
export default function Home() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<Counter />
</div>
);
}
Bu sayfa, bir sunucu bileşeni olarak çalışır ve istemci bileşeni olan Counter’ı içinde render eder.
5. Zustand ile Gelişmiş Durum Yönetimi
Zustand, yalnızca basit durumlar için değil, karmaşık senaryolar için de kullanılabilir. Örneğin, bir kullanıcı yönetim sistemi için store oluşturabilirsiniz:
// app/store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface UserState {
user: { name: string; email: string } | null;
login: (name: string, email: string) => void;
logout: () => void;
}
export const useUserStore = create<UserState>()(
persist(
(set) => ({
user: null,
login: (name, email) => set({ user: { name, email } }),
logout: () => set({ user: null }),
}),
{
name: 'user-storage', // Tarayıcıda saklanacak anahtar
storage: createJSONStorage(() => localStorage), // localStorage kullanımı
}
)
);
Bu store, kullanıcı bilgilerini tutar ve persist middleware’i ile durumu tarayıcının localStorage’ında saklar. Böylece sayfa yenilendiğinde kullanıcı bilgileri kaybolmaz.
6. Zustand ile Performans Optimizasyonu
Zustand, performansı artırmak için birkaç güçlü özellik sunar:
- Seçici Kullanım: Zustand’da yalnızca ihtiyacınız olan durum parçalarını seçebilirsiniz. Bu, gereksiz yeniden render işlemlerini önler:
const count = useCounterStore((state) => state.count);
- Middleware Kullanımı:
persist,immergibi middleware’ler ile durumu daha kolay yönetebilirsiniz. Örneğin,immerile karmaşık durum güncellemeleri basitleşir:
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface ComplexState {
data: { items: string[] };
addItem: (item: string) => void;
}
export const useComplexStore = create<ComplexState>()(
immer((set) => ({
data: { items: [] },
addItem: (item) =>
set((state) => {
state.data.items.push(item);
}),
}))
);
- Sunucu/İstemci Ayrımı: Zustand’ı yalnızca istemci bileşenlerinde kullanmak, Next.js’in sunucu bileşenlerinin performans avantajlarını korur.
7. Gerçek Dünya Örneği: Sepet Uygulaması
Bir e-ticaret uygulamasında sepet yönetimini örnekleyelim:
// app/store.ts
import { create } from 'zustand';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartState {
cart: CartItem[];
addToCart: (item: CartItem) => void;
removeFromCart: (id: string) => void;
clearCart: () => void;
}
export const useCartStore = create<CartState>((set) => ({
cart: [],
addToCart: (item) =>
set((state) => ({
cart: state.cart.some((i) => i.id === item.id)
? state.cart.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
)
: [...state.cart, { ...item, quantity: 1 }],
})),
removeFromCart: (id) =>
set((state) => ({ cart: state.cart.filter((item) => item.id !== id) })),
clearCart: () => set({ cart: [] }),
}));
Bu store’u kullanarak bir sepet bileşeni oluşturabilirsiniz:
// app/components/Cart.tsx
'use client';
import { useCartStore } from '../store';
export default function Cart() {
const { cart, addToCart, removeFromCart, clearCart } = useCartStore();
return (
<div className="p-4 border rounded shadow">
<h2 className="text-xl font-bold">Sepet</h2>
{cart.length === 0 ? (
<p>Sepet boş</p>
) : (
<ul>
{cart.map((item) => (
<li key={item.id} className="flex justify-between py-2">
<span>
{item.name} (x{item.quantity}) - ${item.price}
</span>
<button
onClick={() => removeFromCart(item.id)}
className="text-red-500"
>
Kaldır
</button>
</li>
))}
</ul>
)}
<button
onClick={() =>
addToCart({
id: '1',
name: 'Ürün 1',
price: 29.99,
quantity: 1,
})
}
className="mt-4 px-4 py-2 bg-green-500 text-white rounded"
>
Ürün Ekle
</button>
<button
onClick={clearCart}
className="mt-4 ml-2 px-4 py-2 bg-red-500 text-white rounded"
>
Sepeti Temizle
</button>
</div>
);
}
8. En İyi Uygulamalar
- Store’ları Modüler Tutun: Her store’u belirli bir amaç için oluşturun (örneğin, kullanıcı, sepet, tema).
- TypeScript Kullanın: Tip güvenliği için TypeScript ile store’larınızı tanımlayın.
- Sunucu ve İstemci Ayrımına Dikkat Edin: Zustand’ı yalnızca
"use client"ile işaretlenmiş bileşenlerde kullanın. - Persist ile Kalıcı Durum: Kullanıcı oturumları gibi kalıcı veriler için
persistmiddleware’ini kullanın. - Test Edilebilirlik: Zustand store’larını test etmek için
jestveyavitestile kolayca mock yapabilirsiniz.
Sonuç
Next.js App Router ile Zustand, modern web uygulamaları için güçlü ve hafif bir durum yönetimi çözümü sunar. Zustand’ın minimal yapısı, Next.js’in sunucu ve istemci bileşen mimarisiyle mükemmel bir uyum sağlar.
Bu makalemde, temel bir sayaç örneğinden başlayarak, kullanıcı yönetimi ve sepet uygulaması gibi gerçek dünya senaryolarına kadar Zustand’ın nasıl kullanılacağını detaylı bir şekilde inceledim. Bu rehberi takip ederek, siz de Next.js projelerinizde Zustand ile verimli ve performans odaklı durum yönetimi yapabilirsiniz.