- フロントエンドでデータを保存する方法がいくつかあるけど、何が違うのか分からない
- Nuxtを使っているが、各保存方法をどうやって実装すればいいか分からない
- 状態管理ライブラリ(Pinia)とブラウザストレージの使い分けが分からない
フロントエンド開発では、ユーザーの操作やアプリの状態をどこにどう保存するか、という選択が頻繁に発生します。間違った保存方法を選んでしまうと、セキュリティリスクやパフォーマンス低下につながることもあります。
この記事では、フロントエンドで使える主要なデータ保存方法を一覧で整理し、それぞれの特徴・ユースケース・保存期間を解説します。さらに、Nuxtを使った場合の具体的な実装例もコード付きで紹介します。
フロントエンドのデータ保存方法:一覧比較
まずは全体像を把握するために、主なデータ保存方法を表で比較してみましょう。
| 種類 | 容量 | 保存期間 | サーバー送信 | 主なユースケース |
|---|---|---|---|---|
| Cookie | 最大4KB | 任意に設定可 | あり | 認証トークン・ユーザー設定 |
| LocalStorage | 5〜10MB | 永続(手動削除まで) | なし | ユーザー設定・オフラインデータ |
| SessionStorage | 5〜10MB | タブを閉じるまで | なし | フォームの一時保存・ウィザード |
| ブラウザメモリ | 制限なし(実質的) | ページリロードまで | なし | アプリ状態管理・リアルタイム処理 |
| IndexedDB | 数GB(ブラウザ次第) | 永続(手動削除まで) | なし | 大容量データ・オフラインアプリ |
| Cache API | 数GB(ブラウザ次第) | 永続(手動削除まで) | なし | PWA・静的リソースキャッシュ |
それぞれの保存方法には、得意な場面と苦手な場面があります。以下のセクションで詳しく見ていきましょう。
各保存方法の詳細
Cookie
Cookieは、ブラウザとサーバー間でやり取りされる小さなデータです。HTTPリクエストのたびにサーバーへ自動送信されるのが最大の特徴です。
主なユースケース
- ログイン状態の維持(セッションID、JWTトークン)
- ユーザーの言語設定やテーマ設定
- トラッキング・アナリティクス
ローカルストレージ(LocalStorage)
ローカルストレージは、ブラウザに永続的にデータを保存できる仕組みです。ページをリロードしたりブラウザを閉じて再度開いても、明示的に削除するまでデータは残ります。
主なユースケース
- ユーザーの設定(テーマ・フォントサイズなど)
- ショッピングカートのデータ(非ログイン状態)
- オフライン用のデータキャッシュ
セッションストレージ(SessionStorage)
セッションストレージはローカルストレージと似ていますが、保存期間が「タブを閉じるまで」に限定されます。タブをまたいで共有されない点も特徴です。
主なユースケース
- 複数ステップのフォーム(ウィザード)の一時保存
- ページ遷移をまたいだ検索条件の保持
- 同一セッション中のみ必要な一時データ
ブラウザのメモリ(JavaScriptの変数・状態管理)
JavaScriptの変数やフレームワークの状態管理(Piniaなど)に保存される方式です。最も高速にアクセスできますが、ページリロードで消えます。
主なユースケース
- ログイン中ユーザーの情報
- UI状態(モーダルの開閉・タブの選択など)
- APIレスポンスの一時保持
- リアルタイムに更新されるデータ(チャット・通知など)
IndexedDB
IndexedDBは、ブラウザ内に構築できる本格的なデータベースです。非同期APIで大容量のデータを扱えるため、オフライン対応アプリに活用されます。
主なユースケース
- 大量データのローカルキャッシュ(商品一覧・地図データなど)
- オフライン対応アプリのデータ保存
- ユーザーが生成した画像・ファイルの一時保存
Cache API(Service Worker)
Cache APIは、Service Workerと組み合わせて使うブラウザキャッシュの仕組みです。ネットワークリクエストのレスポンスをキャッシュすることで、オフライン時でもアプリを動作させることができます。
主なユースケース
- PWA(Progressive Web App)の構築
- HTMLや画像などの静的リソースをキャッシュしてオフライン対応
- APIレスポンスのキャッシュ戦略(Stale While Revalidate など)
Nuxtでの実装方法
ここからは、Nuxt 4を使った場合の各保存方法の実装例を紹介します。
Cookie の実装(useCookie)
Nuxt 4でも useCookie はそのまま使えます。SSR・クライアント両方で動作し、別途ライブラリは不要です。
export const useAuth = () => {
// maxAge: 7日間のCookieを設定(秒単位)
const token = useCookie('auth_token', {
maxAge: 60 * 60 * 24 * 7,
secure: true,
sameSite: 'lax',
})
const login = (newToken: string) => {
token.value = newToken
}
const logout = () => {
token.value = null
}
return { token, login, logout }
}テンプレートでの使用例:
<script setup lang="ts">
const { token, logout } = useAuth()
</script>
<template>
<div>
<p v-if="token">ログイン中</p>
<button @click="logout">ログアウト</button>
</div>
</template>LocalStorage の実装
LocalStorageはブラウザ専用APIのため、SSRで動作するNuxtでは import.meta.client でクライアント判定をしてから呼び出すのがポイントです。ネイティブの localStorage.setItem / getItem / removeItem をそのまま使えます。
export const useTheme = () => {
const theme = ref<'light' | 'dark'>('light')
// クライアントサイドでのみLocalStorageから初期値を読み込む
if (import.meta.client) {
const saved = localStorage.getItem('app_theme')
if (saved === 'light' || saved === 'dark') {
theme.value = saved
}
}
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
if (import.meta.client) {
localStorage.setItem('app_theme', theme.value)
}
}
const clearTheme = () => {
theme.value = 'light'
if (import.meta.client) {
localStorage.removeItem('app_theme')
}
}
return { theme, toggleTheme, clearTheme }
}テンプレートでの使用例:
<script setup lang="ts">
const { theme, toggleTheme } = useTheme()
</script>
<template>
<div>
<p>現在のテーマ:{{ theme }}</p>
<button @click="toggleTheme">テーマを切り替える</button>
</div>
</template>LocalStorageは文字列しか保存できないため、オブジェクトを扱う際は JSON.stringify / JSON.parse でシリアライズ・デシリアライズが必要です。
// 保存
localStorage.setItem('user', JSON.stringify({ name: '山田', age: 30 }))
// 取得
const raw = localStorage.getItem('user')
const user = raw ? JSON.parse(raw) : nullSSRの注意点localStorage はサーバーサイドには存在しないAPIです。必ず import.meta.client で囲むか、<ClientOnly> コンポーネントと組み合わせて使いましょう。
SessionStorage の実装
SessionStorageもLocalStorageと同じネイティブAPIで操作できます。sessionStorage.setItem / getItem / removeItem を使い、同様に import.meta.client でクライアント判定をして呼び出します。
type WizardForm = {
name: string
email: string
plan: string
}
export const useWizard = () => {
const step = ref(1)
const formData = ref<WizardForm>({ name: '', email: '', plan: '' })
// クライアントサイドでのみSessionStorageから初期値を読み込む
if (import.meta.client) {
const savedStep = sessionStorage.getItem('wizard_step')
if (savedStep) step.value = Number(savedStep)
const savedForm = sessionStorage.getItem('wizard_form')
if (savedForm) formData.value = JSON.parse(savedForm)
}
const nextStep = () => {
step.value++
if (import.meta.client) {
sessionStorage.setItem('wizard_step', String(step.value))
}
}
const prevStep = () => {
step.value--
if (import.meta.client) {
sessionStorage.setItem('wizard_step', String(step.value))
}
}
const updateForm = (data: Partial<WizardForm>) => {
formData.value = { ...formData.value, ...data }
if (import.meta.client) {
sessionStorage.setItem('wizard_form', JSON.stringify(formData.value))
}
}
const clearWizard = () => {
step.value = 1
formData.value = { name: '', email: '', plan: '' }
if (import.meta.client) {
sessionStorage.removeItem('wizard_step')
sessionStorage.removeItem('wizard_form')
}
}
return { step, formData, nextStep, prevStep, updateForm, clearWizard }
}ブラウザメモリの実装(Pinia)
Nuxt 4では、Piniaのstoreは app/stores/ ディレクトリに置きます。@pinia/nuxt がこのディレクトリを自動認識してくれるため、個別のimportが不要になりました。
npm install @pinia/nuxtexport default defineNuxtConfig({
modules: ['@pinia/nuxt'],
})import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const user = ref<{ name: string; email: string } | null>(null)
const isLoggedIn = computed(() => !!user.value)
const setUser = (data: { name: string; email: string }) => {
user.value = data
}
const clearUser = () => {
user.value = null
}
return { user, isLoggedIn, setUser, clearUser }
})コンポーネントでの使用例:
<script setup lang="ts">
const userStore = useUserStore()
</script>
<template>
<p>{{ userStore.user?.name }}</p>
</template>Pinia + LocalStorage の組み合わせpinia-plugin-persistedstate を使うと、Piniaのstoreの内容をLocalStorageへ自動的に永続化できます。リロード後もstoreを復元したい場合に便利です。
IndexedDB の実装(idb-keyval)
IndexedDBは低レベルAPIで直接扱うと複雑なため、idb-keyval などの軽量ライブラリを使うのがおすすめです。
npm install idb-keyvalimport { get, set, del } from 'idb-keyval'
export const useOfflineData = () => {
const saveData = async (key: string, value: unknown) => {
if (import.meta.client) {
await set(key, value)
}
}
const getData = async <T>(key: string): Promise<T | undefined> => {
if (import.meta.client) {
return await get<T>(key)
}
}
const deleteData = async (key: string) => {
if (import.meta.client) {
await del(key)
}
}
return { saveData, getData, deleteData }
}Cache API の実装(@vite-pwa/nuxt)
Cache APIはService Workerと組み合わせて使います。@vite-pwa/nuxt モジュールを使うと、設定ファイルだけでPWAとキャッシュ戦略を構築できます。
npm install @vite-pwa/nuxt -Dexport default defineNuxtConfig({
modules: ['@vite-pwa/nuxt'],
pwa: {
manifest: {
name: 'My Nuxt App',
short_name: 'NuxtApp',
theme_color: '#ffffff',
},
workbox: {
// 静的アセットをキャッシュ
globPatterns: ['**/*.{js,css,html,png,svg,ico}'],
runtimeCaching: [
{
// APIレスポンスをキャッシュ(Stale While Revalidate)
urlPattern: /\/api\//,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24, // 1日
},
},
},
],
},
},
})どれを選べばいい?選択の考え方
保存方法を選ぶときは、以下の3つの観点で考えると整理しやすいです。
- リロード後もデータを残したいか?
- 残したい → LocalStorage / Cookie / IndexedDB / Cache API
- 残さなくていい → SessionStorage / ブラウザメモリ(Pinia)
- サーバーと情報をやり取りする必要があるか?
- ある(認証情報など) → Cookie(HttpOnly推奨)
- ない → LocalStorage / IndexedDB など
- データ量はどのくらいか?
- 小〜中(設定値など) → Cookie / LocalStorage
- 大容量(ファイル・大量データ) → IndexedDB / Cache API
まとめ
最後まで読んでいただき、ありがとうございました!
今回は、「フロントエンドのデータ保存方法とNuxt 4での実装」についてご紹介しました。
改めて各保存方法を振り返ると:
- Cookie:サーバー送信が必要な認証情報に。
HttpOnly属性でセキュリティを高める - LocalStorage:設定値や永続化したいクライアントデータに
- SessionStorage:フォームの一時保存など、タブ内で完結するデータに
- ブラウザメモリ(Pinia):UI状態やAPIデータなど、高速アクセスが必要な状態管理に
- IndexedDB:オフラインアプリや大容量データの保存に
- Cache API:PWAやオフライン対応が必要なリソースキャッシュに
「どれを使えばいいか分からない」と感じたときは、まず「リロード後も保持したいか」「サーバーに送る必要があるか」「どのくらいのデータ量か」の3点を確認してみてください。それだけで多くの場合、適切な選択肢に絞り込めます。
ぜひ今回の内容を参考に、プロジェクトに合った保存方法を選んでみてください!
