Nuxt 3を使った開発で「困った!」と感じる場面は意外と多いものです。
このシリーズでは、現場でよく遭遇する困りごとに対して、具体的な解決方法を提供していきます。
PiniaとComposition APIの使い分けで迷わない状態管理
状態管理って悩みますよね。
特にNuxt 3では、Composition APIが標準となり、 「これってPiniaを使うべき?
それともComposition APIだけで十分?」という 判断に迷うことが多いのではないでしょうか。
今回は、よくある困りごととして「ユーザーの認証状態の管理」を例に、 PiniaとComposition APIの使い分けについて考えていきます。

Nuxt3で状態管理の使い分け、結論から!
状態管理の選択は、以下の基準で判断すると迷わなくなります:
アプリ全体で共有する状態 → Pinia
- 複数のコンポーネントでアクセスが必要
- ページ遷移後も状態を維持する必要がある
- ローカルストレージとの連携が必要
コンポーネント内での一時的な状態 → Composition API
- フォームの入力値
- UIの表示状態(モーダルの開閉など)
- 親子コンポーネント間での状態共有
具体例:ユーザー認証の状態管理

R
「ユーザーの認証状態をアプリ全体で管理したい。でも、実装が複雑になりすぎないかな…」
このような悩みの背景には、以下のような要件があります:
- ログイン状態をアプリケーション全体で共有したい
- ページをリロードしても状態を維持したい
- ログアウト時は確実に状態をクリアしたい
この場合は、Piniaを使用するのがベストな選択です。 具体的な実装例を見ていきましょう。
具体例:ユーザー認証の状態管理の実装
「ユーザーの認証状態をアプリ全体で管理したい。でも、実装が複雑になりすぎないかな…」
このような悩みには、以下のような実装で対応できます:
typescript
// stores/auth.ts
import { defineStore } from 'pinia'
interface User {
id: string
email: string
name: string
}
interface AuthState {
user: User | null
token: string | null
isAuthenticated: boolean
}
export const useAuthStore = defineStore('auth', {
state: (): AuthState => ({
user: null,
token: null,
isAuthenticated: false
}),
actions: {
setUser(user: User | null) {
this.user = user
this.isAuthenticated = !!user
},
setToken(token: string | null) {
this.token = token
if (token) {
localStorage.setItem('auth_token', token)
} else {
localStorage.removeItem('auth_token')
}
},
async login(email: string, password: string) {
try {
const response = await $fetch('/api/login', {
method: 'POST',
body: { email, password }
})
this.setToken(response.token)
this.setUser(response.user)
return { success: true }
} catch (error) {
return {
success: false,
error: 'ログインに失敗しました'
}
}
}
},
getters: {
currentUser: (state): User | null => state.user,
isLoggedIn: (state): boolean => state.isAuthenticated
}
})
この実装のポイントを見ていきましょう:
1. 明確な型定義
TypeScriptの型定義をしっかり行うことで、どんな情報を扱うのかが一目瞭然です。また、補完が効くためコーディングがスムーズになります。
2. 状態の一元管理
認証に関する状態をまとめて管理することで:
- 状態の更新漏れを防げる
- ローカルストレージとの連携も一箇所で管理できる
3. エラーハンドリング
ログイン処理のエラーハンドリングをストア内で行うことで、コンポーネント側の実装がシンプルになります。
コンポーネントでの使用例を見てみましょう:
typescript
// components/Header.vue
const Header = defineComponent({
setup() {
const auth = useAuthStore()
return {
isLoggedIn: computed(() => auth.isLoggedIn),
user: computed(() => auth.currentUser),
logout: () => auth.logout()
}
}
})
まとめ

今回のポイントを整理しておきましょう:
- PiniaとComposition APIの使い分け基準
- Piniaは「アプリ全体で共有する状態」の管理に
- Composition APIは「コンポーネントローカルな状態」の管理に
- 認証状態管理でPiniaを選ぶ理由
- アプリ全体での状態共有が容易
- ページリロード時の状態維持が実装しやすい
- ストア内でロジックを完結できる
- 実装時の重要ポイント
- TypeScriptの型定義をしっかり行う
- 状態の更新は必ずアクションを経由する
- エラーハンドリングはストア内で実装する
最後まで読んでいただき、ありがとうございました。
このシリーズが、みなさんの開発のお役に立てば幸いです。