ข้ามไปเนื้อหาหลัก

Category: guide

Astro Islands Architecture — JavaScript เฉพาะที่จำเป็น

เข้าใจ Islands Architecture ของ Astro ว่าทำงานอย่างไร ทำไมถึงส่ง zero JS by default และเมื่อไรควรใช้ client directives เพื่อ hydrate component

· อ่านประมาณ 2 นาที

สารบัญ

Islands Architecture คืออะไร

แนวคิดที่เว็บหน้าหนึ่งประกอบด้วย “islands” ของ interactivity บน “ocean” ของ static HTML:

┌─────────────────────────────────┐
│  Static HTML (ไม่มี JS)          │
│  ┌──────────┐   ┌────────────┐  │
│  │ 🏝 Search │   │ 🏝 Counter │  │
│  │ (hydrate)│   │ (hydrate)  │  │
│  └──────────┘   └────────────┘  │
│                                 │
│  ┌────────────────────────────┐ │
│  │ Static Card Grid (ไม่มี JS)│ │
│  └────────────────────────────┘ │
└─────────────────────────────────┘

แต่ละ island hydrate แยกกัน ส่วนที่เป็น static HTML ไม่มี JS เลย

Zero JS by Default

---
// Component นี้ render เป็น static HTML เท่านั้น
// ไม่มี JavaScript ส่งไปยัง browser เลย
---
<div class="card">
  <h2>Static Content</h2>
  <p>Rendered at build time</p>
</div>

แม้จะ import React/Vue component ใน Astro ก็ยังเป็น static HTML ถ้าไม่มี client directive

Client Directives

---
import Counter from '../components/Counter.jsx';
import SearchBox from '../components/SearchBox.jsx';
import HeavyChart from '../components/Chart.jsx';
---

<!-- hydrate ทันทีที่ page โหลด -->
<Counter client:load />

<!-- hydrate เมื่อ browser idle -->
<SearchBox client:idle />

<!-- hydrate เมื่อ component เข้า viewport -->
<HeavyChart client:visible />

<!-- hydrate เฉพาะบน breakpoint ที่กำหนด -->
<MobileMenu client:media="(max-width: 640px)" />

<!-- ไม่ hydrate เลย — static HTML อย่างเดียว -->
<StaticCard />
Directiveเมื่อ hydrateเหมาะกับ
client:loadทันทีnavigation, critical UI
client:idlebrowser idlesecondary features
client:visibleเข้า viewportbelow-fold content
client:mediamedia query matchresponsive components
client:onlyclient-side เท่านั้นcomponent ที่ต้องการ browser API

ทำไมถึงดีกว่า SPA

SPA (React/Next):              Astro Islands:
├── JavaScript bundle ใหญ่      ├── HTML ทั้งหน้า (fast)
├── Parse JS ก่อน render        ├── Island 1 JS (~5KB)
├── Hydrate ทั้งหน้า            ├── Island 2 JS (~12KB)
└── LCP ช้า                    └── Island hydrate แยก
  • Partial Hydration — ส่ง JS เฉพาะส่วนที่ interactive จริงๆ
  • Progressive Enhancement — หน้าทำงานได้แม้ JS ยังไม่โหลด
  • Framework Agnostic — ใช้ React บน island หนึ่ง Vue บนอีก island ได้

Static Site ที่ไม่ต้องการ Islands

เว็บ content-heavy เช่น blog หรือ portfolio ส่วนใหญ่ไม่ต้องการ JavaScript เลย:

---
// ทำงานได้สมบูรณ์ด้วย HTML + CSS ล้วนๆ
import { getCollection } from 'astro:content';
const posts = await getCollection('posts');
---
{posts.map((post) => <Card {...post.data} />)}

Astro จะส่ง 0 KB JavaScript สำหรับหน้านี้ — ทำให้ LCP เร็วมาก

ตัวอย่างจริง: Search Island

---
// src/pages/search.astro
// PagefindUI เป็น island — โหลด client-side เท่านั้น
---
<Layout>
  <div id="search"></div>
  <script is:inline defer src="/pagefind/pagefind-ui.js"></script>
  <script>
    document.addEventListener('astro:page-load', () => {
      new PagefindUI({ element: '#search' });
    });
  </script>
</Layout>

หน้า search ส่ง HTML ว่างๆ ก่อน แล้ว Pagefind UI hydrate หลังจาก JS โหลด — ผู้ใช้เห็นหน้าทันที ไม่รอ JS

เมื่อไรควรใช้ client:load

  1. Component ต้องทำงานทันทีที่หน้าโหลด (nav, search bar)
  2. Component ต้องการ browser API (localStorage, window)
  3. ข้อมูลต้อง fetch จาก API แบบ real-time
  4. User interaction ที่ต้องการ state (form, counter, toggle)

เว็บ content อย่าง portfolio/blog ส่วนใหญ่ทำทุกอย่างได้ด้วย HTML + CSS + vanilla JS โดยไม่ต้อง island เลย