🍪

SvelteKitでタイプセーフなCookie管理

2025/01/14に公開

導入

CookieはWebアプリに必須の存在ですが、オブジェクトを格納したりしようとするとシリアライズ・デシリアライズの手間が発生します。また、変換の際やサーバー・クライアント間で型定義を間違えてつけてエラーを発生させてしまう可能性があります。
今回は、SvelteKitでフルスタックにタープセーフなCookie管理ができるライブラリを紹介します。

https://github.com/jill64/svelte-baked-cookie

名前の由来は硬く型付けされたCookieにしたかったのでbaked(焼いた)-cookieにしました。
(svelte-french-toastに影響を受けています)

セットアップ

まずは依存関係のインストール

npm i svelte-baked-cookie

使い方

このライブラリは大きく分けて3つのステップでCookieを管理します。

ステップ1 - Cookie型の定義

アプリケーションで使用するCookieとその型を一覧で定義します。
bakery関数の第一引数のオブジェクトのキーがCookieのキー、値がそのキーのserdeになります。
serdeについてはこちらの記事を参照して下さい。

https://zenn.dev/jill64/articles/22cf402af27c02

以下に例を示します。

bakery.ts
import { bakery } from 'svelte-baked-cookie'
import { json, number, string } from 'svelte-baked-cookie/serde'

export const { bake, rebake } = bakery(
  {
    key1: string,
    key2: number,
    key3: json(
      (x): x is string[] =>
        Array.isArray(x) && x.every((y) => typeof y === 'string'),
      []
    )
  }
)

この例では、3つのCookieを使用します。

1つ目はキーが"key1"で文字列型で格納します。
2つ目はキーが"key2"で数値型で格納します。
3つ目はキーが"key3"で文字列配列型で格納します。

すると、戻り値としてbake関数とrebake関数が得られます。
アプリケーションではcookiesdocument.cookiesを触らずに、これらを使ってCookieの読み書きを行います。

ステップ2 - サーバーサイドでの使用

SvelteKitのサーバーサイドではcookiesが使用できます。
これをbake関数に渡すと型付け(型変換)されたCookieが得られます。

+layout.server.ts
import { bake } from './bakery.js'

export const load = ({ cookies }) => {
  const { bakedCookies } = bake(cookies)

  // string
  const str = bakedCookies.key1 // 'foo'

  // number
  const num = bakedCookies.key2 // 123

  // string[]
  bakedCookies.key3 = ['value', 'set', 'by', 'server']

  return {
    // ...
  }
}

文字列への変換・パースやget setメソッドのことを考える必要はありません。
オブジェクトのキーを指定して読み書きするだけでCookieを操作できます。

ステップ3 - クライアントサイドでの使用

SvelteKitのクライアントサイドではcookiesは直接使用できません。
代わりにrebake関数を使用してサーバーサイドと同じ型付けされたCookieを読み書きできます。

+page.svelte
<script>
  import { rebake } from './bakery.js'

  const cookies = rebake()

  // key1: string
  // key2: number
  // key3: string[]

  // 'foo'を表示
  console.log(cookies.key1)

  // string
  cookies.key2 = 123

  // string[]
  cookies.key3 = ['value', 'set', 'by', 'client']
</script>

ステップ4(任意) - SSR環境における改善

SSRを使用する場合、ステップ3だけでは不十分な場合があります。
なぜならrebake関数は内部でdocument.cookieを読み書きしているのですが、サーバー上でsvelteコンポーネントがレンダリングされる際、document.cookieは存在しません。
よって全ての値がNullishになった状態で、サーバー上でレンダリングされ、ハイドレーションが完了すると初めて実際のCookie値が読み込まれます。
これにより、表示のチラつきが発生する場合があります。
これを回避するためには、追加のステップが必要です。

+layout.server.ts
import { bake } from './bakery.js'

export const load = ({ cookies }) => {
  const { bakedCookies, pie } = bake(cookies)

  // ...

  return {
    pie
  }
}

サーバー上でbake関数を実行する際、戻り値でpieというオブジェクトが返されます。
このpieオブジェクトの中には型付けされる前のCookieが入っています。
これをreturnしてクライアント側に渡します。
そしてクライアント側ではrebake関数にpieを渡します。

+layout.svelte
<script>
  import { rebake } from './bakery.js'

  let { data } = $props()

  let pie = $derived(data.pie)

  // key1: string
  // key2: number
  // key3: string[]
  let cookies = $derived(rebake(pie))
</script>

このような手順を踏むことでSSRのサーバーサイドレンダリング中でもCookieの内容にアクセスできるようになり、表示のチラつきが緩和されます。

まとめ

いかがだったでしょうか。
この記事がSvelteKitにおけるタイプセーフなCookie管理の助けになれば幸いです。

またsvelte-backed-cookieについてバグや不明点がありましたらぜひIssueを開いてください。

Discussion