Blog

Astroのアイランドが凄い。ページ内で一部だけインタラクティブにすることが可能!

今月の始めにAstroの良さを語ったばかりなのですが、それどころじゃありませんでした。
Astroアイランドが凄すぎました。

Astroアイランドは、静的HTMLページの一部だけをインタラクティブにできるというものです。
Astroのドキュメントを読んでいると、なんだかReactやVueを一緒に使うことができるようだ…ということはなんとなくわかっていたのですが、
思った以上に整備されていて手軽で、パフォーマンスに優れていました。

Astroアイランドとは

公式の図にちょっと足しただけですが、こういうものです。

今回やりたかったことは、CMSに登録した投稿の記事一覧をAPI通信でとってくるというものです。
CMSで投稿が編集されれば、ビルドなしでサイトが書き変わります。(ブラウザロードやクリックなどの動作でデータ取得できる)

API通信を組み込み、ビルドで静的HTMLに落とし込むSSGタイプでもありません。
かといって、サイト自体をSSRにするということでもありません。
「Astroアイランド(別名:コンポーネントアイランド)は、Astroが開拓したWebアーキテクチャーのパターンです。」と公式にありますが、確かに今までなかった(そしてほしかった)「静的HTMLのまま」で「一部だけインタラクティブに」する機能です。

自分がこれまで制作してきたものを振り返るに、
「アニメーション加えたいがためにWordPressのRestAPIを使用して記事一覧を出し、1ページだけフレームワークを取り入れる」
みたいなことをやってきましたが、静的HTMLの一部分にフレームワークを取り入れるのは結構大変でした。
Astroアイランドはそこをフルサポートしてくれています。

アイランド以外のところは完全静的HTMLなので、表示速度も速いです。

具体的に、Astroアイランドはどうやって作るか

.astroのページファイルは、すべて静的HTMLとして生成されます。
しかし、インタラクティブにしたい部分に下記のように「clientディレクティブ」を付与したコンポーネントを記載すると、そこがアイランドとなります。

<MyReactComponent client:load />

MyReactComponentは、Reactやvueのコンポーネントです。完全にそのUIフレームワークコンポーネントのアイランド(島)なので、むしろAstroコンポーネントをこの中で書くことはできないです。
ReactのuseStateを使って状態管理し、リアクティブな要素を使って表示を切り替えるみたいなことができます。
これらのUIフレームワークをとりいれるには、インテグレーションをインストールする必要があります。
(インテグレーションって何?と思ってましたが、直訳だと「一体化」…みたいな意味なので、「Astroとその他フレームワークを一体化させるためのパッケージ」というイメージ?)

インテグレーションをインストールする(例:React)

公式の説明はこちら

このブログのおすすめ記事一覧を表示するとします。
そのために、Reactをつかってリアクティブな要素を使えるようにします。
ReactはAstro公式のインテグレーションがありますので、AstroでReactを使うために、下記をインストールします。

npm install --save-dev @astrojs/react react react-dom

astro.config.mjsに下記追加します。

import { defineConfig } from 'astro/config';

import react from '@astrojs/react';

export default defineConfig({
  integrations: [react()],
});

react以外の記述の仕方も公式に載っています。

.envにエンドポイントを記載

ルートフォルダに.envファイルを作成し、エンドポイントを書いておきます。

PUBLIC_ENDPOINT=https://hogecms/wp-json/wp/hogehoge/1?posts_per_page=-1&cat=recommend

サーバ側のコードではすべての環境変数が使えますが、クライアント側のコードではセキュリティのためにPUBLIC_というプレフィックスを持つ環境変数のみが使えるようになっています。
今回クライアント側で動作させるので、接頭語としてPUBLIC_をつけないと、各ファイルから参照できません。

データをfetchする関数を作る

/src/library/api.tsを作成し、fetch関数を作ります。

const ENDPOINT = import.meta.env.PUBLIC_ENDPOINT;

export type Posts = {
  id: string;
  name: string;
  content: string;
  eyecatch: string;
  url: string;
}[];

const fetchAPI = async () => {
  const response = await fetch(`${ENDPOINT}`)
    .then((res) => {
      return res.json();
    })
    .catch((err) => {
      console.error(err);
    });
  return response;
};

export const fetchPosts = async (): Promise<Posts> => {
  const data = await fetchAPI();

  const posts = data.map((article) => {
    const post = {
      id: article.id,
      name: article.title,
      content: article.content,
      eyecatch: article.thumb_image_url,
      url: `/blog/${article.name}/`,
    };
    return post;
  });
  return posts;
};

.envのデータはimport.meta.env.PUBLIC_ENDPOINT;で取得することができます。
今回はエンドポイントを決めうちにしていますが、fetchAPIにパラメータを渡して、エンドポイントの文字列を任意に作れるような実装の方がより汎用的です。

Reactのコンポーネントを作る

src/components/MyReactComponent.tsxを作成します。
これは完全にReactのコンポーネントです。先ほどのfetch関数を呼んで、データを取得します。

import React, { FC, useState } from "react";
import type { Posts } from "../library/api";
import { fetchPosts } from "../library/api";

export const MyReactComponent: FC = () => {
  const [posts, setPosts] = useState<Posts>();

  const getPosts = async () => {
    if (!posts) {
      const res = await fetchPosts();
      if (res) setPosts([...res]);
    }
  };
  getPosts();

  return (
    <div className="item-wrap">
      {posts?.map((post) => {
        return (
          <div key={post.id} className="item">
            <a href={post.noteUrl} target="_blank">
              <div className="item-img">
                <img src={post.eyecatch} alt="" />
              </div>
              <div className="item-title">
                <p>{post.name}</p>
              </div>
            </a>
          </div>
        );
      })}
    </div>
  );
};

.astroファイルからReactコンポーネントをよびだす

---
import {MyReactComponent} from '../components/MyReactComponent';
---
<MyReactComponent client:load />

clientディレクティブには、どの段階でコンポーネントを読み込むかを指示できます。
client:loadは、ページの読み込み時にすぐにコンポーネントの JavaScript を読み込み、ハイドレート(静的HTMLを動的に変換)します。
clientディレクティブを使うと、呼び出すデータの重さによって使い分けができます。通常はclient:loadで良いと思いますが、とても重いものを取得する可能性があるならidleやvisibleなども検討すると、ファーストビューの表示を妨げず快適になりそうです。

clientディレクティブの種類

ページを確認する

これで、npm run buildすると、distフォルダに最終ファイルが生成されます。
このdistフォルダのものを確認したい場合は、
npm run previewでローカルサーバーを立ち上げます。
これで、API元の記事を編集したりしてみると、サイトの表示が変わります。

distフォルダの該当のindex.htmlを見てみると、アイランド部分には<astro-island>というタグが下記のようなタグが入っています。

<astro-island uid="hoge" 〜〜〜 >fugafuga</astro-island>

チェックしといた方がいいところ

個人的にゾッとしたところなのですが、記事を編集したあと、ブラウザ更新もしていないのにサイトの表示がきりかわるという現象に出くわしました。
おかしいと思ってサーバーにアップしたあとクロームデバッグツールのネットワークタブを見ていると、延々とデータフェッチを繰り返していました。
原因はReactコンポーネントの簡単な記述ミスだったのですが、ネットワークタブは見ておいた方が良いかもしれません。
(なぜかローカル作業の時は問題なかったのが恐ろしい)

まとめ

アイランドの利点として、公式ではパフォーマンスを挙げています。
それももちろんそうなのですが、個人的にそれより嬉しいのは、Astro独自の技術を作ったりせず、「他の有名なフレームワークなどと組み合わせる」という方向性で作られていることです。
もう、現時点でフロントエンドはフレームワークやライブラリが多すぎます。
「覚えろ」ではなく「持てる技術で挑んでこい」。そんなふうに言ってるように聞こえる(誇張)。この姿勢が最も嬉しい。

是非ともAstroがWeb制作の王道フレームワークになってほしいと思います。

おすすめの記事 recommend blog

新着 new blog

github