Svelteでコンポーネントを遅延ロードする方法
Svelteアプリはデフォルトで高速ですが、リッチテキストエディタ、チャートライブラリ、または複雑なダッシュボードウィジェットを初期JavaScriptペイロードにバンドルしている場合、ユーザーは使用しない可能性のあるコードをダウンロードしています。Svelteのコンポーネントレベルの遅延ロードは、これらの重いインポートを実際に必要になるまで延期することで、この問題を解決します。
Reactとは異なり、Svelteには組み込みのlazy()ヘルパーがありません。代わりに、遅延ロードはJavaScriptのネイティブなimport()関数と条件付きレンダリングを直接組み合わせて使用します。より手動的ですが、より柔軟性があります。
重要なポイント
- Svelteには組み込みの
lazy()ヘルパーがないため、遅延ロードはJavaScriptのネイティブなimport()と条件付きレンダリングを使用します - SvelteKitはルートレベルのコード分割を自動的に処理しますが、単一ページ内の重いウィジェットにはコンポーネントレベルの遅延ロードが依然として必要です
- Viteは設定不要で動的インポートを個別のチャンクに分割します
- 3つのコアパターンでほとんどのユースケースをカバーできます:
{#await}を使用したオンデマンドロード、ホバー時のプリロード、ブラウザがアイドル状態になるまで延期
SvelteKitでコンポーネントレベルの遅延ロードが依然として重要な理由
SvelteKitはすでにルートレベルのコード分割を自動的に処理しています。各ルートは独自のチャンクを取得するため、ページ間を移動しても初期バンドルが肥大化しません。しかし、単一ページ内では、.svelteファイルの先頭でインポートしたすべてのものが一緒にバンドルされ、最初にロードされます。
ここでコンポーネントレベルの遅延ロードが価値を持ちます。ページに重いチャートコンポーネント、ビデオプレーヤー、またはユーザーの操作後にのみ表示されるモーダルが含まれている場合、ページ到着時にそのコードをロードする理由はありません。
SvelteKitはViteでビルドされ、動的インポートをネイティブに処理します。Viteがimport('./HeavyChart.svelte')を見ると、自動的にそのモジュールを個別のチャンクに分割します。追加の設定は不要です。
Svelteコンポーネントを遅延ロードする方法:コアパターン
{#await}を使用した基本的な遅延ロード
最もシンプルなアプローチは、テンプレート内でSvelteの{#await}ブロックを直接使用します:
<!-- src/routes/+page.svelte -->
<script>
let showChart = false;
let chartData = [1, 2, 3];
</script>
<button onclick={() => showChart = true}>Load Chart</button>
{#if showChart}
{#await import('$lib/components/HeavyChart.svelte')}
<p>Loading chart...</p>
{:then Chart}
<Chart.default data={chartData} />
{:catch error}
<p>Failed to load chart: {error.message}</p>
{/await}
{/if}
これはSvelte 4とSvelte 5の両方で動作します。{:catch}ブロックは重要です — ネットワーク障害は発生するものであり、サイレントエラーはデバッグを困難にします。Svelte 4では、動的にコンポーネントを切り替える場合、通常<svelte:component this={Component}>を使用します。モダンなSvelteでは、コンポーネント参照を変更することで直接再レンダリングをトリガーすることもできます。
ブラウザは最初のインポート後にモジュールをキャッシュします。同じコンポーネントの後続のレンダリングでは、新しいネットワークリクエストはトリガーされません。
Discover how at OpenReplay.com.
より速い体感パフォーマンスのためのホバー時のロード
一般的なパターンは、ユーザーが意図を示したとき — トリガー要素にホバーしたときにロードを開始することです:
<!-- src/routes/+page.svelte -->
<script lang="ts">
import type { Component } from 'svelte';
let HeavyWidget: Component<{ message: string }> | null = $state(null);
async function loadWidget() {
if (!HeavyWidget) {
const module = await import('$lib/components/HeavyWidget.svelte');
HeavyWidget = module.default;
}
}
</script>
<div onmouseenter={loadWidget}>
<p>Hover to preload the widget</p>
</div>
{#if HeavyWidget}
<HeavyWidget message="Ready!" />
{/if}
これはツールチップ、ポップオーバー、サイドバーに適しています。コンポーネントはユーザーがクリックする前にロードを開始するため、ユーザーが操作する頃にはすでにキャッシュされていることが多いです。
ブラウザがアイドル状態のときにロード
ページを改善するが即座に必要ではない非クリティカルなコンポーネントには、requestIdleCallbackを使用します:
<script lang="ts">
import { onMount } from 'svelte';
import type { Component } from 'svelte';
let FeedbackWidget: Component | null = $state(null);
onMount(() => {
const load = () =>
import('$lib/components/FeedbackWidget.svelte').then(
(m) => (FeedbackWidget = m.default)
);
if ('requestIdleCallback' in window) {
requestIdleCallback(load);
} else {
setTimeout(load, 300); // Safariのフォールバック
}
});
</script>
{#if FeedbackWidget}
<FeedbackWidget />
{/if}
SafariのrequestIdleCallbackサポートは歴史的に一貫性がないため、setTimeoutフォールバックが推奨されます。
遅延ロードすべきでない場合
すべてのコンポーネントが遅延ロードの恩恵を受けるわけではありません。以下の場合は避けてください:
- ファーストビューのUI — ナビゲーション、ヒーローセクション、主要コンテンツ
- 小さなコンポーネント — 非同期のオーバーヘッドが節約分を上回る
- マウント時に即座に必要なコンポーネント — ロードのフラッシュがUXを低下させる
過度の分割は多くの小さな非同期チャンクを作成します。Viteのようなモダンなバンドラーはこれを効率的に処理しますが、それでも収穫逓減のポイントがあります。
まとめ
Svelteコンポーネントの遅延ロードは一つのことに集約されます:静的インポートの代わりにimport()を使用し、結果を条件付きでレンダリングすることです。SvelteKitのViteベースのビルドは、コード分割を自動的に処理します。上記の3つのパターン — オンデマンド、ホバー時、アイドル時 — は、ほとんどの実際のシナリオをカバーします。ユーザーが実際にコンポーネントを必要とするタイミングに合わせてトリガーを選択し、エラー処理を追加すれば、初期バンドルは軽量に保たれます。
よくある質問
動的インポートは両方のバージョンで動作します。Svelte 4では、通常、ロードされたコンポーネントをthis属性を持つsvelte:componentを使用してレンダリングします。Svelte 5では、解決されたコンポーネントをテンプレート内のタグとして直接使用できます。基礎となるインポートメカニズムは両方のケースで同じです。
Node.jsが動的インポートをサポートしているため、SSR中にも動的インポートは実行されます。ただし、遅延ロードは主にブラウザの初期JavaScriptペイロードを削減することを目的としたクライアントサイドの最適化です。コンポーネントがSEOやファーストペイントの理由でサーバー上でレンダリングされる必要がある場合は、静的インポートがより良い選択です。
ビルド出力を確認するか、バンドルアナライザーを使用して大きなチャンクを特定してください。チャートレンダラー、リッチテキストエディタ、マップウィジェットなどの重いサードパーティライブラリを取り込むコンポーネントは強力な候補です。コンポーネントが数キロバイト未満しか追加しない場合、追加のネットワークリクエストのオーバーヘッドが節約分を上回る可能性があります。
いいえ。ブラウザは最初の動的インポートが解決された後、モジュールをキャッシュします。後続のレンダリングでは、キャッシュされたモジュールが即座に返されるため、ローディング状態は初回のフェッチ時にのみ表示されます。また、ホバー時やアイドル時にモジュールをプリロードして、可視的な遅延を完全に排除することもできます。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.