Back to list
fusengine

nextjs-zustand

by fusengine

Redefining development through cognitive automation and collaborative agent systems.

0🍴 0📅 Jan 24, 2026

SKILL.md


name: nextjs-zustand description: Zustand for Next.js App Router - Client Components only, hydration handling, persist middleware. Use when implementing global state in Next.js. user-invocable: false

Zustand for Next.js

State management for Next.js Client Components.

Installation

bun add zustand

Important: Client Components Only

Zustand stores work in Client Components only. Server Components cannot use hooks.

// ❌ BAD - Server Component
// app/page.tsx
export default function Page() {
  const count = useCounterStore((s) => s.count) // Error!
}

// ✅ GOOD - Client Component
// app/Counter.tsx
'use client'
export function Counter() {
  const count = useCounterStore((s) => s.count)
}

Basic Store

// stores/useCounterStore.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 }),
}))

Store with Async Actions

// stores/useUserStore.ts
import { create } from 'zustand'

interface User {
  id: string
  name: string
  email: string
}

interface UserState {
  user: User | null
  loading: boolean
  error: string | null
  fetchUser: (id: string) => Promise<void>
  logout: () => void
}

export const useUserStore = create<UserState>((set) => ({
  user: null,
  loading: false,
  error: null,

  fetchUser: async (id) => {
    set({ loading: true, error: null })
    try {
      const res = await fetch(`/api/users/${id}`)
      if (!res.ok) throw new Error('Failed to fetch')
      const user = await res.json()
      set({ user, loading: false })
    } catch (err) {
      set({ error: (err as Error).message, loading: false })
    }
  },

  logout: () => set({ user: null }),
}))

Persist with SSR Hydration

// stores/useAuthStore.ts
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface AuthState {
  token: string | null
  isHydrated: boolean
  setToken: (token: string) => void
  clearToken: () => void
  setHydrated: () => void
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      isHydrated: false,
      setToken: (token) => set({ token }),
      clearToken: () => set({ token: null }),
      setHydrated: () => set({ isHydrated: true }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => localStorage),
      onRehydrateStorage: () => (state) => {
        state?.setHydrated()
      },
    }
  )
)

Hydration Component

// components/StoreHydration.tsx
'use client'

import { useEffect, useState } from 'react'
import { useAuthStore } from '@/stores/useAuthStore'

export function StoreHydration({ children }: { children: React.ReactNode }) {
  const [isHydrated, setIsHydrated] = useState(false)

  useEffect(() => {
    setIsHydrated(true)
  }, [])

  if (!isHydrated) {
    return null // or loading skeleton
  }

  return <>{children}</>
}

// Alternative: use store's isHydrated
export function AuthGuard({ children }: { children: React.ReactNode }) {
  const isHydrated = useAuthStore((s) => s.isHydrated)

  if (!isHydrated) {
    return <div>Loading...</div>
  }

  return <>{children}</>
}

Usage in Client Components

// components/UserProfile.tsx
'use client'

import { useUserStore } from '@/stores/useUserStore'
import { useEffect } from 'react'

export function UserProfile({ userId }: { userId: string }) {
  const { user, loading, error, fetchUser } = useUserStore()

  useEffect(() => {
    fetchUser(userId)
  }, [userId, fetchUser])

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  if (!user) return <div>No user</div>

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  )
}

Devtools

// stores/useAppStore.ts
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

export const useAppStore = create<AppState>()(
  devtools(
    (set) => ({
      // state and actions
    }),
    { name: 'AppStore' }
  )
)

Combined Middlewares

import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

export const useStore = create<State>()(
  devtools(
    persist(
      (set) => ({
        // state
      }),
      { name: 'app-storage' }
    ),
    { name: 'AppStore' }
  )
)

Best Practices

  1. Client Components only - Use 'use client' directive
  2. Handle hydration - Avoid hydration mismatches
  3. Selector pattern - useStore((s) => s.field) for performance
  4. Separate stores - One store per domain (auth, cart, ui)
  5. Server state - Use TanStack Query for server data

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon