概要
2025年12月、Svelte 5.44.0でhydratableという新しいAPIがリリースされました。
サーバーで取得したデータをクライアントでも再利用できるようにする機能です。
実際どれくらい効果があるんだろう?と思い、比較デモを作って試してみた記事です。
結論(先に知りたい方へ)
この機能を使うと以下のメリットがあります
- サーバーで取得したデータをクライアントで再利用できる
- データ取得の二重実行を防げる
- 2回実行が1回で済むため、単純計算で時間が半分になる
1行の変更で実現できるので、SSRを使っているSvelteプロジェクトなら試してみる価値がありそうです。
実験用のデモ
効果を比較するため2つのページを作りました。
- hydratableなし(これまでと同様)
- hydratableあり(最適化済み)
なお、このデモではAPI呼び出しをsetTimeoutで再現しており、実際のネットワークリクエストではありません。
効果を分かりやすくするため、意図的に2秒の遅延を設定しています。
何が問題なのか
SSRでは、サーバーでHTMLを生成し、クライアント側でJavaScriptを実行してインタラクティブにするHydrationという処理が行われます。
これまでの実装だと以下のようになります。
<script>
import { onMount } from 'svelte';
// 非同期データ取得
async function fetchUserData() {
await new Promise((resolve) => setTimeout(resolve, 2000));
return {
name: 'Tanaka Taro',
email: 'tanaka@example.com',
lastLogin: new Date().toLocaleString('ja-JP')
};
}
// 🚨 問題: awaitを使うと、サーバーとクライアントの両方で実行される
const userData = await fetchUserData();
</script>
<h1>{userData.name}</h1>
この実装の問題点
- サーバーで
fetchUserData()を実行(2秒) - HTMLが生成されブラウザに送信される
- ブラウザでHydration開始時に再度
fetchUserData()を実行(さらに2秒) - 合計4秒以上かかり、Hydrationがブロックされる
ユーザーは約4秒間待たされ、その間ボタンのクリックやフォーム入力などのインタラクションができません。
DevToolsで問題を確認する
実際にブラウザの開発者ツールで確認してみましょう。
Consoleでの確認
デモサイトの「hydratableなし」のページを開き、DevToolsのConsoleタブを見ると、以下のようなログが表示されます。
[🖥️ サーバー] コンポーネント初期化開始
[🖥️ サーバー] fetchUserData() 開始
[🖥️ サーバー] fetchUserData() 完了 (2.00秒)
[🖥️ クライアント] コンポーネント初期化開始
[🖥️ クライアント] fetchUserData() 開始
[🖥️ クライアント] fetchUserData() 完了 (2.00秒)
[🖥️ クライアント] onMount実行 - Hydration完了!
[🖥️ クライアント] 合計時間: 4.02秒
サーバーとクライアントの両方でfetchUserData()が実行されているのが分かります。
Performanceでの確認
DevToolsのPerformanceタブで記録すると、クライアント側でのデータ取得がメインスレッドをブロックし、Hydrationが遅延している様子が確認できます。
解決策: hydratableの導入
Svelte 5のhydratableを使うと、この問題を解決できます。
<script>
import { hydratable } from 'svelte';
async function fetchUserData() {
await new Promise((resolve) => setTimeout(resolve, 2000));
return {
name: 'Tanaka Taro',
email: 'tanaka@example.com',
lastLogin: new Date().toLocaleString('ja-JP')
};
}
// ✅ 解決策: hydratableを使う
const userData = await hydratable('user-data', () => fetchUserData());
</script>
<h1>{userData.name}</h1>
1行だけの変更ですが、動作が大きく変わります。
hydratableの仕組み
- サーバーで
fetchUserData()を実行(2秒) - データがシリアライズされ、HTMLの
<head>タグ内に埋め込まれる - ブラウザでHydration開始時、埋め込まれたデータを即座に読み込む(0秒)
- 合計2秒のみ、Hydrationをブロックしない
DevToolsで改善を確認する
「hydratableあり」のページでConsoleを見ると、以下のようになります。
[🖥️ サーバー] コンポーネント初期化開始
[🖥️ サーバー] fetchUserData() 開始
[🖥️ サーバー] fetchUserData() 完了 (2.00秒)
[🖥️ クライアント] コンポーネント初期化開始
[🖥️ クライアント] hydratable完了、データ取得済み (キャッシュから即座に読み込み)
[🖥️ クライアント] onMount実行 - Hydration完了!
[🖥️ クライアント] 合計時間: 2.01秒
クライアント側でのデータ取得がスキップされ、約2秒で完了しています。
デザイナー・フロントエンドエンジニアの視点での価値
ユーザー体験への直接的な影響
2回の実行が1回で済むため、ページが早くインタラクティブになり、クリックやタップへの応答が即座に可能になります。
特に低速なモバイルネットワーク環境では、ネットワークリクエストの削減による効果が大きく、ユーザーの離脱率低下につながります。
ローディング状態などを最小化できる
Hydrationが高速化されることで、ローディングスピナーやスケルトンスクリーンの表示時間を短縮できます。
Core Web Vitalsへの影響
TTI(Time to Interactive)が改善されるため、Interaction to Next Paint(INP)などのCore Web Vitals指標にも良い影響を与える可能性があります。
実装時の注意点
一意なキーの使用
// 良い例
const userData = await hydratable('user-data', () => fetchUserData());
// 悪い例: 他のコンポーネントと重複する可能性
const userData = await hydratable('data', () => fetchUserData());
hydratableの第一引数のキーは一意である必要があります。
ライブラリを作成する場合は、ライブラリ名をプレフィックスとして使用してください。
// ライブラリでの使用例
const userData = await hydratable('my-lib:user-data', () => fetchUserData());
実験的機能の有効化
Svelte 5では、コンポーネントのトップレベルでawaitを使う機能は実験的な機能のため、svelte.config.jsで明示的に有効化する必要があります。
export default {
compilerOptions: {
experimental: {
async: true
}
},
kit: {
adapter: adapter()
}
};
シリアライズ可能なデータ
hydratableで扱うデータは、JSONシリアライズ可能である必要があります。
関数やDOMノードなどは含めないようにしましょう。
// ✅ OK
return {
name: 'Tanaka Taro',
email: 'tanaka@example.com',
lastLogin: new Date().toISOString()
};
// ❌ NG: 関数は含められない
return {
name: 'Tanaka Taro',
greet: () => console.log('Hello')
};
まとめ
2025年12月にリリースされたSvelte 5.44.0のhydratableを実験してみました。
1行だけの変更でSSRにおけるデータ取得を最適化でき、2回実行が1回で済むためHydration時間が半分になることが確認できました。
実験を通じて分かったこと
- SSRでは同じデータ取得が二重に実行される問題がある
-
hydratableを使うとサーバーのデータをクライアントで再利用できる - DevToolsで実行順序とタイミングを確認できる
hydratableは簡単に導入できるので、SSRを使っているプロジェクトで試してみる価値がありそうです。