3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SvelteKitでSSR時のストアのハイドレーションを考えてみた

Posted at

SvelteKit触ってみていいじゃん!と思ったんですが、よそのフレームワークにあるあの機能はないのかな?と思い自分なりの解決方法が見えたので共有します。
この記事では「NuxtではSSR時にストア(Vuex)がクライアントでハイドレーションされるのにSvelteKitにはないの?」という疑問と、(私なりの)その解決方法を紹介します。

SvelteKitはストアをハイドレーションするか?

しません。
もしかしたら将来的にそういう機能が実装されるかもしれませんが現時点(1.0.0-next.377)ではハイドレーションしません。

そもそもなぜストアをハイドレーションしたいのかというと、以下のような理由があげられます。

  1. SSRとCSRで二重でHTTP API等を呼び出したくない
  2. SSRで取得した値を再利用したい
  3. ストアから値を取り出すだけの使い方をしたい

この要望をSvelteKitの仕組みでうまく隠蔽?したいと思います。

SvelteKitでストアをハイドレーションさせる

機能はなくてもやりたいことは実装できます。
厳密にはストアそのものをハイドレーションするわけではないですが、次のようなフローでサーバー側で取得した値をクライアントでストアに詰め直します。

順を追って説明します。

1.SvelteKitのエンドポイントを利用してAPIリソースを取得する

SvelteKitのエンドポイントはSSR時はサーバーサイドで取得され、そのレスポンスはハイドレーションされてページコンポーネントのpropsで取得できるようになります。
CSRの場合は遷移する前に同ページパスに加えて__data.jsonでリクエストされてその取得した値が遷移先のページコンポーネントのpropsで利用できるようになります。

つまり、ストアに詰めた値はハイドレーションされませんが、エンドポイントのレスポンスの値はハイドレーションされることが重要です。

※以下の例ではAPIのレスポンスに型定義をつけるためにaspidaを利用しています。

src/routes/weather.ts
import type { RequestHandler, RequestHandlerOutput } from '@sveltejs/kit'
import aspida from '@aspida/node-fetch'
import fetch from 'node-fetch'
import api from '$api/$api'
import type { Weather } from '$api/data/2.5/weather'

const fetchConfig = {
  credentials: "include",
  baseURL: "https://api.openweathermap.org",
  throwHttpErrors: true // throw an error on 4xx/5xx, default is false
}

export type GetOutput = { data: Weather }

export const GET: RequestHandler = async (): Promise<RequestHandlerOutput<GetOutput>> => {
  const client = api(aspida(fetch, fetchConfig))
  const data = await client.data.$2_5.weather.$get({
    query: {
      lat: 35.68944,
      lon: 139.69167,
      appid: '***********'
    }
  })
  return {
	status: 200,
	headers: {},
	body: {
      data
    }
  }
}

aspidaを利用しているためGETの返す値の型定義をRequestHandlerOutputのジェネリクスに指定することができます。

2.Load関数でエンドポイントのレスポンスをPropsで取得する

SvelteKitのエンドポイントのレスポンスはLoad関数のpropsで受け取ることができます。

src/routes/weather.svelte
<script context="module" lang="ts">
  import type { Load } from '@sveltejs/kit'
  import type { GetOutput } from '.'

  export const load: Load<{}, GetOutput> = ({ props }) => {
    return {}
  }

	export const hydrate = true
</script>

Loadの型定義で第二引数に指定した型がPropsの型となります。
エンドポイントで利用しているGetOutput型を指定することで型の恩恵を受けることができます。
スクリーンショット 2022-07-28 20.52.48.png
propsの中にdataプロパティがある。その中にはAPIのJSONが入っていることがわかる。
スクリーンショット 2022-07-28 20.53.27.png
(ちゃんと型補完されてよし!)

3.ストアにデータを保持させる

ストアの実装は次のようになっています。Svelteは独自でストア機能を持っているのでそれを利用しています。
setter関数でAPIのレスポンスをストアにセットするようになっています。

src/lib/stores/weatherStore.ts
import { writable } from 'svelte/store'
import type { Weather } from '$api/data/2.5/weather'

type WeatherStore = {
  name: string
  coord: {
    lon: number
    lat: number
  },
  weather: { id: number, main: string, description: string, icon: string }[]
}

const store = writable<WeatherStore>({
  name: '',
  coord: {
    lon: 0,
    lat: 0,
  },
  weather: [{
    id: 0,
    main: '',
    description: '',
    icon: '',
  }]
})

const setter = (apiRes: Weather) => {
  store.set(apiRes)
}

export const useWeatherStore = () => {
  return {
    store,
    setter
  }
}

このストアを先程のLoad関数で呼び出します。

src/routes/weather.svelte
import { useWeatherStore } from '$lib/stores/weatherStore'

export const load: Load<{}, GetOutput> = ({ props }) => {
  useWeatherStore().setter(props.data)
  return {}
}

あとはお好きなコンポーネントでストアを呼び出してその値にアクセスするだけです。

src/lib/components/weather.svelte
<script lang="ts">
  import { useWeatherStore } from '$lib/stores/weatherStore'
  const weather = useWeatherStore().store
</script>

<p>{ $weather.name }</p>
<ul>
  {#each $weather.weather as item}
  <li>{ item.description }</li>
  {/each}
</ul>

Svelteではストアの値にアクセスする際に接頭辞に$をつけるだけです。
スクリーンショット 2022-07-28 21.02.06.png
ちゃんと型による恩恵を受けていることがわかります。

スクリーンショット 2022-07-28 21.02.54.png

SvelteKitはストアの値をハイドレーションすることができませんがエンドポイントの仕組みを利用することで、ストアがハイドレーションされたかのように扱うことができました。
最初にあげたストアをハイドレーションしたいという理由を十分に満たすことができたと思います。

それでは!

おまけ

  • この内容はSvelte交流会#1で発表した内容です。
  • SvelteKitのStoreはリクエスト毎に独立しているわけではないので注意が必要です(別途記事で対処法出せたら・・・)
  • ページエンドポイントやLoad関数周りの仕様が変更になる可能性があります。参照
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?