🧊

AstroのServer islandsについて

Server Islands | Astro https://astro.build/blog/future-of-astro-server-islands/

機能が発表されたのはもう半年も前。なんとなーく把握はしてたけど、それをちゃんとしたいという趣旨。

ドキュメントとデモはこちら。

Server islands | Docs https://docs.astro.build/en/guides/server-islands/ Astro Server Islands https://server-islands.com/

TL;DR

  • .astroコンポーネントに対して、server:defer属性をつけておくと
  • 初回のHTMLレスポンスが返却されるときには、フォールバックコンテンツがまず描画されてる
  • その後、クライアント側でJSから本当のコンテンツを遅延でリクエストして、フォールバックを差し替える

いやまぁ、これだけなんよな実際。

server:defer

個人的に盲点だったのは、この指定ができるのは.astroで書かれたコンポーネントだけってところ。

つまり、ReactやSvelteなんかで書いたものには使えない。

まぁslot = "fallback"にしても、そらそうかって感じ。

初段のHTMLの様子

  • output: server or prerender = falseならリクエスト時に生成するHTML
  • output: static or prerender = trueならビルド時に生成されてるHTML

これはデモサイトでページのソースをみるとわかるけど、こんなの。

<link
  rel="preload" as="fetch"
  href="/_server-islands/CartCount?e=default&p=E3112300A0AFA5A5171D7A813i6My0q1B4HWDGwK6Xs1gsS4&s=%7B%7D"
  crossorigin="anonymous"
>
<script async type="module" data-island-id="4026065b-4200-4403-9a33-d2c99a1a86c0">
let script = document.querySelector('script[data-island-id="4026065b-4200-4403-9a33-d2c99a1a86c0"]');

let response = await fetch('/_server-islands/CartCount?e=default&p=E3112300A0AFA5A5171D7A813i6My0q1B4HWDGwK6Xs1gsS4&s=%7B%7D');

if(response.status === 200 && response.headers.get('content-type') === 'text/html') {
	let html = await response.text();

	// Swap!
	while(script.previousSibling &&
		script.previousSibling.nodeType !== 8 &&
		script.previousSibling.data !== 'server-island-start') {
		script.previousSibling.remove();
	}
	script.previousSibling?.remove();

	let frag = document.createRange().createContextualFragment(html);
	script.before(frag);
}
script.remove();
</script>

地道なやつだわな。

裏側の実装は、もはやAstroのお家芸みたいなところであるViteのプラグイン。

https://github.com/withastro/astro/tree/45c3f333872a236d7c6a70ac805356737cdc68ec/packages/astro/src/core/server-islands

さっき見たインラインのJSなんかはここ。

https://github.com/withastro/astro/blob/45c3f333872a236d7c6a70ac805356737cdc68ec/packages/astro/src/runtime/server/render/server-islands.ts

ユースケースを考える

  • ログインが必要な情報なんかを表示したいとき
  • 動的な内容なので、事前にビルドはできない
  • なので、クライアントサイドでuseEffect()してfetch()・・・みたいな処理をしてた

このJSコードのペイロードと、処理自体もサーバー側に飛ばせるのは利点かな。

または、

  • もともと動的にサーバーでレンダリングしてるけど
  • やけに遅いAPIや処理があるとか

Astroの場合、こういうのもStreamingでレスポンスを返すようになってたと思うけど、それでも後続が遅れることに変わりないので、ボトルネックだけをskipできると。

ちなみに、server:deferなコンポーネントはネストさせることもできるみたいやけど、なんか使い道はあるだろうか。

そのほか

バグか仕様かよくわからなかった挙動もあった。

  • client:onlyしたReactなんかのコンポーネントにネストする形で配置すると、リクエストは飛ぶがコンテンツが置き換わらない
    • server:deferをつけないものは表示される

Astro Server Island inside Svelte’s <slot> inconsistently renders in production · Issue #12394 · withastro/astro https://github.com/withastro/astro/issues/12394

これが近いかな・・?

まとめ

Next.js+RSCほど柔軟ではないけど、まあ堅実な機能って感じかね。

  • Astroベースでやることを決めてて
  • .astroじゃないコンポーネントは、末端でのみ利用する

という条件下でなら、割と使い所はあるような気はする。

が、大体数のコンポーネントが.astroではない場合は、どうだろう。 先述のバグっぽい挙動もあるし、Reactベースなら素直にNext.jsにしたほうが楽そうか。