0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Astro】JSを90%削減!?「Islands Architecture」の魔術的な使いどころと実装パターン 🏝️✨

Posted at

こんにちは😊
株式会社プロドウガ@YushiYamamotoです!
らくらくサイトの開発・運営を担当しながら、React.js・Next.js専門のフリーランスエンジニアとしても活動しています❗️

昨日の記事では「Next.jsからAstroへの移行判断基準」についてお話ししました。
今日は、Astroの代名詞とも言える**「Astro Islands(アイランドアーキテクチャ)」について、ドキュメントには載っていないような現場レベルでの実践的な使いどころ**を深掘りしていきます。

「ただReactコンポーネントが使えるだけでしょ?」と思っている方、もったいないです!
適切なディレクティブ(client:*)を使い分けることで、Webサイトのパフォーマンスは劇的に向上します。


1. そもそも「Islands Architecture」とは? 🏝️

従来のSPA(Single Page Application)やNext.jsのSSRでは、ページ全体がJavaScriptで制御される「巨大な大陸」でした。
対してAstroは、**「基本は静的なHTMLの海」の中に、「インタラクティブな島(Island)」**を浮かべるアーキテクチャを採用しています。

この仕組みにより、**「必要な箇所に、必要なタイミングでのみJavaScriptを読み込む」**ことが可能になります。


2. 実践!ディレクティブの使い分け「最適解」 🛠️

AstroにはコンポーネントをいつHydrate(起動)させるかを決める client:* ディレクティブがあります。
現場でよく使うパターンを整理しました。

ディレクティブ 読み込みタイミング 推奨ユースケース
client:load ページロード時、即座に ヒーローエリアの動的要素、グローバルナビゲーション
client:idle ページの初期読み込み完了後 重要度の低い通知バー、チャットウィジェット
client:visible 画面にスクロールして入った時 フッター付近のカルーセル、コメント欄、重い地図
client:media 特定のCSSメディアクエリに合致した時 スマホ専用のハンバーガーメニュー
client:only クライアントサイドのみ(SSRスキップ) windowオブジェクト必須のライブラリ、localStorage操作

3. 具体的な実装パターン 📝

ここからは、実際のWeb制作現場で頻出する3つのパターンをコード付きで解説します。

Case 1: 「重いライブラリ」を遅延読み込みする 🐢💨

Swiper.jsやReact Slickなどのカルーセルライブラリは便利ですが、JSバンドルサイズが大きくなりがちです。
ファーストビューにないカルーセルを初期ロードで読み込むのは無駄です。

client:visible を使えば、ユーザーがそこまでスクロールするまでJSを1バイトも読み込みません。

components/HeavyCarousel.tsx (React)
import React from 'react';
// 重いライブラリのインポート
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';

export const HeavyCarousel = () => {
  return (
    <div className="w-full h-64 bg-gray-100 p-4">
      <Swiper spaceBetween={50} slidesPerView={3}>
        <SwiperSlide>Slide 1 🏞️</SwiperSlide>
        <SwiperSlide>Slide 2 🏙️</SwiperSlide>
        <SwiperSlide>Slide 3 🌉</SwiperSlide>
        <SwiperSlide>Slide 4 ⛺️</SwiperSlide>
      </Swiper>
      <p className="text-center mt-2 text-sm text-gray-500">
        ※ このコンポーネントが見えるまで、SwiperのJSは読み込まれません
      </p>
    </div>
  );
};

Astro側での使用:

---
import { HeavyCarousel } from '../components/HeavyCarousel';
---

<div style="height: 1000px;">
  スクロールしてください... ↓
</div>

<!-- 画面内に入った瞬間にJSをフェッチして起動 -->
<HeavyCarousel client:visible />

Case 2: スマホ閲覧時だけJSを動かす(ハンバーガーメニュー) 📱

PCではCSSだけで完結するメニューでも、スマホでは開閉のためにJSが必要な場合があります。
client:media を使えば、PCユーザーにはJSを一切送らず、スマホユーザーにだけJSを送ることができます。

components/MobileMenu.tsx (React)
import React, { useState } from 'react';

export const MobileMenu = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="relative">
      <button 
        onClick={() => setIsOpen(!isOpen)}
        className="p-2 border rounded"
      >
        {isOpen ? '✖️ Close' : '🍔 Menu'}
      </button>

      {isOpen && (
        <ul className="absolute top-full left-0 w-48 bg-white shadow-lg border mt-2 p-2 rounded">
          <li className="p-2 hover:bg-gray-100"><a href="/">Home</a></li>
          <li className="p-2 hover:bg-gray-100"><a href="/about">About</a></li>
          <li className="p-2 hover:bg-gray-100"><a href="/contact">Contact</a></li>
        </ul>
      )}
    </div>
  );
};

Astro側での使用:

---
import { MobileMenu } from '../components/MobileMenu';
---

<header class="flex justify-between p-4">
  <h1>My Site</h1>
  
  <!-- PC (768px以上) では表示すらしない、またはCSSで制御 -->
  <div class="md:hidden">
    <!-- 画面幅が768px未満の場合のみJSをロードしてHydrate -->
    <MobileMenu client:media="(max-width: 768px)" />
  </div>
  
  <!-- PC用メニューは静的HTMLで記述 -->
  <nav class="hidden md:block">
    <a href="/" class="mr-4">Home</a>
    <a href="/about">About</a>
  </nav>
</header>

Case 3: 島と島をつなぐ(状態共有) 🤝

Islands Architectureの弱点は、**「島同士が独立しているため、ReactのContextやPropsで状態を共有できない」**ことです。
例えば、「ヘッダーのカートアイコン」と「商品一覧の追加ボタン」は別の島です。

これを解決するのが、Astro推奨の軽量ステート管理ライブラリ Nano Stores です。

Nano Storesの特徴
React, Vue, Svelte, Vanilla JSなど、フレームワークを跨いで状態を共有できます。非常に軽量で、Astroとの相性が抜群です。

1. ストアの作成 (stores/cartStore.ts)

import { atom } from 'nanostores';

// カート内の商品数を管理するアトム
export const cartCount = atom(0);

export function addToCart() {
  cartCount.set(cartCount.get() + 1);
}

2. カートボタン (components/AddToCartButton.tsx)

import { addToCart } from '../stores/cartStore';

export const AddToCartButton = () => (
  <button onClick={addToCart} className="bg-blue-500 text-white px-4 py-2 rounded">
    カートに入れる 🛒
  </button>
);

3. ヘッダーの表示 (components/CartHeader.tsx)

import { useStore } from '@nanostores/react';
import { cartCount } from '../stores/cartStore';

export const CartHeader = () => {
  const count = useStore(cartCount); // ストアを購読
  return <div>Cart: {count} items</div>;
};

Astro側での使用:

---
import { CartHeader } from '../components/CartHeader';
import { AddToCartButton } from '../components/AddToCartButton';
---

<header>
  <CartHeader client:load /> <!-- 常に表示 -->
</header>

<main>
  <!-- 別の場所にある島でも状態が同期される -->
  <AddToCartButton client:visible />
</main>

4. まとめ:パフォーマンス・ファーストの設計思考 🧠

Astro Islandsを使いこなすコツは、**「デフォルトは静的HTML。どうしても必要な時だけJSを足す」**という引き算の思考です。

  • client:visible を積極的に使い、初期ロード時間を短縮する。
  • client:media でモバイルパフォーマンスを最適化する。
  • Nano Stores で島同士を疎結合につなぐ。

これらを意識するだけで、Lighthouseのスコアは緑色(90点以上)で安定し、ユーザー体験も開発者体験も向上します。
「脱Next.js」の選択肢として、ぜひAstro Islandsのパワーを体感してみてください!

明日は、**「View Transitions API で実現する、SPAのような滑らかな遷移」**について解説します。お楽しみに!👋


最後に:業務委託のご相談を承ります

私は業務委託エンジニアとしてWEB制作やシステム開発を請け負っています。最新技術を活用したレスポンシブなWebサイト制作、インタラクティブなアプリケーション開発、API連携など幅広いご要望に対応可能です。

「課題解決に向けた即戦力が欲しい」「高品質なWeb制作を依頼したい」という方は、お気軽にご相談ください。一緒にビジネスの成長を目指しましょう!

👉 ポートフォリオ

🌳 らくらくサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?