どこに保存する?フロントエンドのデータ管理をNuxtで理解する

この記事で解決できるお悩み
  • フロントエンドでデータを保存する方法がいくつかあるけど、何が違うのか分からない
  • Nuxtを使っているが、各保存方法をどうやって実装すればいいか分からない
  • 状態管理ライブラリ(Pinia)とブラウザストレージの使い分けが分からない

フロントエンド開発では、ユーザーの操作やアプリの状態をどこにどう保存するか、という選択が頻繁に発生します。間違った保存方法を選んでしまうと、セキュリティリスクやパフォーマンス低下につながることもあります。

この記事では、フロントエンドで使える主要なデータ保存方法を一覧で整理し、それぞれの特徴・ユースケース・保存期間を解説します。さらに、Nuxtを使った場合の具体的な実装例もコード付きで紹介します。

目次

フロントエンドのデータ保存方法:一覧比較

まずは全体像を把握するために、主なデータ保存方法を表で比較してみましょう。

スクロールできます
種類容量保存期間サーバー送信主なユースケース
Cookie最大4KB任意に設定可あり認証トークン・ユーザー設定
LocalStorage5〜10MB永続(手動削除まで)なしユーザー設定・オフラインデータ
SessionStorage5〜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) : null

SSRの注意点
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/nuxt
export 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-keyval
import { 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 -D
export 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点を確認してみてください。それだけで多くの場合、適切な選択肢に絞り込めます。

ぜひ今回の内容を参考に、プロジェクトに合った保存方法を選んでみてください!

この記事が気に入ったら
フォローしてね!

シェアしていただけると大変励みになります!
  • URLをコピーしました!
  • URLをコピーしました!
目次