Category: guide
Astro Islands Architecture — JavaScript เฉพาะที่จำเป็น
เข้าใจ Islands Architecture ของ Astro ว่าทำงานอย่างไร ทำไมถึงส่ง zero JS by default และเมื่อไรควรใช้ client directives เพื่อ hydrate component
สารบัญ
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:idle | browser idle | secondary features |
client:visible | เข้า viewport | below-fold content |
client:media | media query match | responsive components |
client:only | client-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
- Component ต้องทำงานทันทีที่หน้าโหลด (nav, search bar)
- Component ต้องการ browser API (localStorage, window)
- ข้อมูลต้อง fetch จาก API แบบ real-time
- User interaction ที่ต้องการ state (form, counter, toggle)
เว็บ content อย่าง portfolio/blog ส่วนใหญ่ทำทุกอย่างได้ด้วย HTML + CSS + vanilla JS โดยไม่ต้อง island เลย