【Astro】クライアントディレクティブとは?client:loadの使い分け

【Astro】クライアントディレクティブとは?client:loadの使い分け

Astroにボタンのコンポーネントを置いたのに、クリックしても何も起きない…

client:load を付けたら動いたけど、client:visible との違いがよくわからない

Astroを触りはじめて最初につまずくのが、この 「置いただけでは動かない」 という挙動だと思います。

理由はシンプルで、Astroは デフォルトでJavaScriptを1行も出力しない からです。
そのうえで「ここだけは動かしたい」と指定するための仕組みが、クライアントディレクティブです。

この記事では、5種類あるディレクティブの違いと、どれを選べばいいのかの判断軸を、私なりに整理していきます。

この記事でわかること
  • Astroのコンポーネントが「そのままだと動かない」理由
  • client:load / client:idle / client:visible / client:media / client:only の違い
  • 部品の位置と重さから、どのディレクティブを選ぶかの判断軸
  • 付け忘れ・.astroへの指定など、つまずきやすいポイント

Astroでの記事データの扱い方については、別記事で整理しています。

この記事の前提(2026年8月時点)
  • コードは Astro v7系(最新は7.2系) の公式ドキュメントに沿った書き方です
  • クライアントディレクティブの書き方は v5系でも同じです(このブログ自体はv5.16系で動いています)
  • 公式リファレンスは Template directives reference にあります

Astroのコンポーネントが動かないのは仕様|アイランドとハイドレーション

Astroは アイランドアーキテクチャという考え方でできています。

ページ全体は静的なHTMLとして書き出しておいて、動きが必要な部品だけを「島(アイランド)」として切り出し、そこにだけJavaScriptを届ける、という作りです。

ハイドレーション(hydration)

サーバー側で作ったHTMLに対して、あとからブラウザでJavaScriptを読み込ませ、クリックや入力に反応する状態にすることです。
直訳すると「水分を与える」。カラカラのHTMLに、動きという水を足すイメージですね。

つまりAstroにとっては、JavaScriptを配らないのが標準の状態です。
ReactやSvelteのコンポーネントを置いても、指示がなければサーバー側でHTMLに変換されて終わり。だからクリックしても反応しません。

---
import Counter from "../components/Counter.jsx";
---

<!-- 見た目は出るが、クリックしても動かない -->
<Counter />

<!-- JSが届いて、動くようになる -->
<Counter client:load />

この client:load の部分がクライアントディレクティブです。
「この部品にはJavaScriptを届けてください。届けるタイミングはこれです」 という指定だと考えるとわかりやすいと思います。

らるじゅらるじゅ

エラーも警告も出ないので、最初は「壊れているのかな?」と思っちゃうんですよね。
でもこれ、不具合ではなく設計どおりの動きです。

クライアントディレクティブ5種の書き方

Astroが標準で用意しているのは次の5つです。公式ドキュメントが示す優先度(高→中→低)の順に並べています。
ただし client:only は優先度の枠外で、client:load と同じくページ読み込み直後に動きます。

1. client:load|ページ表示と同時に動かす

<BuyButton client:load />

ページが読み込まれた時点ですぐにハイドレーションします。優先度が一番高い指定です。

最初の画面に見えていて、すぐ押される可能性がある部品に向いています。購入ボタン、ヘッダーの検索フォーム、開閉するグローバルメニューあたりですね。

2. client:idle|ブラウザの手が空いてから動かす

<ShowHideButton client:idle />

ページの初期読み込みが終わり、ブラウザが暇になったタイミング(requestIdleCallback)でハイドレーションします。

すぐ触られるわけではないけれど、画面には最初から見えている部品向けです。
待ち時間の上限を決めたいときは、ミリ秒でタイムアウトを渡せます。

<ShowHideButton client:idle={{timeout: 500}} />

こう書くと、ブラウザが暇にならなくても 500ミリ秒経てばハイドレーションを始めます。

3. client:visible|画面に入ってから動かす

<HeavyImageCarousel client:visible />

その部品がビューポート(表示領域)に入ったときに初めてハイドレーションします。内部では IntersectionObserver が使われています。

ページ下部のカルーセルやコメント欄など、スクロールしないと出てこない重い部品に向いています。読者がそこまで来なければ、そのJavaScriptは一生読み込まれません。

少し手前で準備を始めたいときは、rootMargin を渡します。

<HeavyImageCarousel client:visible={{rootMargin: "200px"}} />

画面に入る200px手前から読み込みが始まるので、表示された瞬間にはもう触れる状態になりやすくなります。回線が遅い環境やレイアウトのガタつきが気になるときに有効です。

4. client:media|画面幅の条件つきで動かす

<SidebarToggle client:media="(max-width: 50em)" />

指定したCSSメディアクエリに一致したときだけハイドレーションします。

スマホのときだけ必要な部品が典型です。モバイル用のハンバーガーメニューやドロワーは、PCでは使われないのにJavaScriptだけ届いている、ということが起こりがちなので、ここで絞れます。

5. client:only|サーバーでは描画しない

<SomeReactComponent client:only="react" />

サーバー側のHTML生成をスキップして、ブラウザだけで描画・実行する指定です。

ポイントは、フレームワーク名を文字列で必ず渡すこと。Astroはこの部品をビルド時にもサーバー側でも動かさないため、何で書かれたコンポーネントなのかを推測できないからです。react / preact / svelte / vue / solid-js のように指定します。

window や localStorage にアクセスするなど、ブラウザにしか存在しないものへ依存している部品の逃げ道として使います。

client:onlyは最後の手段に

サーバー側でHTMLを作らないので、JavaScriptが届くまでその部分は空っぽになります。
表示のガタつきやSEO面で不利になりやすいため、まずは他の4つで解決できないかを先に考えるのがおすすめです。

なお、子要素に slot="fallback" を付けると、読み込みが終わるまでの代わりの表示(ローディングなど)を出しておけます。

どれを選ぶ?部品の「位置」と「重さ」で決める

迷ったときは、その部品が最初の画面に見えているかと、どのくらい重いかの2軸で考えると決まりやすいです。

ディレクティブ動き出すタイミング優先度向いている部品
client:loadページ読み込み直後高すぐ押される可能性のあるボタン・検索窓
client:idleブラウザが暇になったとき中画面には見えているが急がない部品
client:visible画面に入ったとき低下部にある重い部品・コメント欄
client:mediaメディアクエリ一致時低スマホ専用メニューなど
client:only読み込み直後(HTMLなし)高ブラウザ専用APIに依存する部品
迷ったときの判断軸
  • 最初の画面に見えていて、すぐ触られる … client:load
  • 最初の画面に見えているが、急がない … client:idle
  • スクロールしないと出てこない … client:visible
  • 特定の画面幅でしか使わない … client:media
  • サーバーで動かすと壊れる … client:only

とはいえ、とりあえず全部 client:load にするのは避けたいところです。
アイランドを増やすほど初期表示で読み込むJavaScriptが増えて、Astroを選んだ理由そのものが薄れてしまいます。

私は基本的に、まず「ワンランク遅い方」から試すようにしています。
体感で困らなければそのまま、待たされる感じがしたら1段上げる。それくらいの温度感でいいと思います。

指定するときに引っかかりやすい4つのこと

1. .astro コンポーネントには付けられない

client:load などを指定できるのは、React・Preact・Svelte・Vue・Solidといった UIフレームワークのコンポーネントです。

.astro ファイルはサーバー側でHTMLになるための仕組みなので、そもそもクライアントで動く状態を持っていません。
.astro の中に動きが欲しい場合は、素の <script> を書くか、その部分だけをフレームワークのコンポーネントに切り出すことになります。

2. インテグレーションを入れていないと使えない

ReactやSvelteのコンポーネントをAstroで扱うには、対応するインテグレーションの追加が必要です。

npx astro add react

コンポーネントを置いただけでビルドが通らないときは、まずここを疑ってみてください。

3. 付け忘れてもエラーにならない

冒頭の話に戻りますが、ディレクティブを書き忘れても ビルドは成功します。
静的なHTMLとして正しく出力されるので、Astroからすると何も問題がないんですよね。

「動かない」と感じたときの確認順は、ディレクティブの有無 → インテグレーション → コンポーネント本体がおすすめです。

4. サーバー側の遅い処理は server:defer という選択肢もある

.astro コンポーネントのうち、データ取得に時間がかかる部分だけ後から差し込みたいというケースもあります。この場合は server:defer を使います。

---
import Avatar from "../components/Avatar.astro";
---

<Avatar server:defer />

こう書くと、その部分がサーバーアイランドになり、ページ本体とは別に読み込まれます。読み込み中に出しておく内容は、子要素の slot="fallback" で指定できます。

ただし server:defer を使うには、@astrojs/node などのアダプターの導入が必要です。全ページ静的なサイトでもアダプター自体は追加できますが、入れていないとビルドエラーになります。詳しくは公式の Server islands を参照してください。

client:* がブラウザ側の話なのに対して、server:defer はサーバー側の話。名前は似ていますが役割はまったく別物なので、混同しないよう気をつけたいところです。

「動く場所を選ぶ」って考え方、慣れると気持ちいいかも!

まとめ|ディレクティブは「JavaScriptを届ける約束」

クライアントディレクティブは、単なるおまじないではなく、この部品にいつJavaScriptを届けるかの約束です。

Astroはデフォルトで何も配らないからこそ、どこに配るかを自分で決められます。
その判断がそのままページの表示速度になる、と考えると選び方も見えてくるはずです。

この記事のまとめ
  • Astroは デフォルトでJavaScriptを出力しない。だから指定なしでは動かない
  • client:load は最優先、client:idle は手が空いてから、client:visible は画面に入ってから
  • client:media は画面幅で絞る指定、client:only はサーバー描画をスキップする最後の手段
  • 判断軸は 最初の画面に見えているか と どのくらい重いか の2つ
  • .astro には client:* は使えない。サーバー側を遅延させたいなら server:defer(要アダプター)
らるじゅらるじゅ

まずは動かしたい部品に client:load を付けてみて、そこから遅い指定に落としていくのがラクだと思います。
少しずつ削っていく作業、地味ですが効きますよ!