Server-Only & Scripts
นี่คือแนวคิดที่ทำให้นักพัฒนา React ประหลาดใจมากที่สุด ใน React ทุก component ที่คุณเขียนจะส่ง JavaScript ไปยัง browser แม้แต่ component ที่แค่ render หัวข้อก็ตาม Astro กลับด้านของ default นี้: .astro component ธรรมดาผลิต JavaScript ออกมาเป็นศูนย์
React ส่ง JS เสมอ — Astro ไม่ส่ง
หัวข้อที่มีชื่อว่า “React ส่ง JS เสมอ — Astro ไม่ส่ง”// Heading.jsx — React// This component ships JS to the browser.// React needs a runtime to mount it, diff it,// and re-render it if props change.export default function Heading({ title }) { return <h1>{title}</h1>;}
// React also ships event-wiring, reconciler, etc.// even when none of that is needed here.---// Heading.astro — Astro// This compiles to:// <h1>My Title</h1>// No JavaScript. No runtime. No hydration.const { title } = Astro.props;---<h1>{title}</h1>compiler ของ Astro จะแปลง Heading.astro ให้เป็นฟังก์ชันที่ return string ของ HTML ออกมา browser ได้รับ string นั้นไป จบ ไม่มี script tag ไม่มี React runtime ไม่มีอะไรเลย
สิ่งที่คุณทำไม่ได้ใน .astro component ธรรมดา
หัวข้อที่มีชื่อว่า “สิ่งที่คุณทำไม่ได้ใน .astro component ธรรมดา”เพราะ frontmatter ทำงาน ตอน build/server time และ template ผลิต static HTML ออกมา คุณจึงไม่สามารถ:
- เรียก
useState,useReducerหรือ React hook ใด ๆ - ผูก event listener ใน frontmatter (ไม่มี
onClickที่ทำงานใน browser) - อ่าน
window,documentหรือlocalStorageใน frontmatter (สิ่งเหล่านี้ไม่มีอยู่ตอน build time) - ทำให้ UI ตอบสนองต่อ input ของผู้ใช้ — สิ่งนั้นต้องใช้ island (จะกล่าวถึงในโมดูลถัดไป)
การเพิ่ม vanilla browser JS ด้วย script tag
หัวข้อที่มีชื่อว่า “การเพิ่ม vanilla browser JS ด้วย script tag”เมื่อคุณต้องการ JavaScript ฝั่ง browser เพียงเล็กน้อย — เช่น toggle, counter หรือ event สำหรับ analytics — Astro ให้คุณใส่ tag <script> เข้าไปใน template ได้โดยตรง Astro จะ bundle และ deduplicate script เหล่านี้ให้อัตโนมัติ
// Counter.jsx — React// Ships React + hydration + component JSimport { useState } from 'react';
export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}> Increment </button> </div> );}---// Counter.astro — vanilla JS via <script>// Ships only the tiny script below — no framework runtime---<div> <p id="count">Count: 0</p> <button id="btn">Increment</button></div>
<script> let count = 0; const display = document.getElementById('count'); const btn = document.getElementById('btn'); btn.addEventListener('click', () => { count++; display.textContent = 'Count: ' + count; });</script>tag <script> ใน Astro component จะถูกประมวลผลโดย Vite: Vite จะ bundle, tree-shake แล้ว inject เพียงครั้งเดียวต่อหนึ่งหน้า แม้ component นั้นจะถูกใช้หลายครั้งก็ตาม สำหรับความ interactive ที่ซับซ้อน ให้ใช้ island (component ของ React, Preact, Svelte หรือ framework อื่นที่มี directive client:*)
ตัวอย่างที่รันได้จริงพร้อม script
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้จริงพร้อม script”---
const items = ["Astro", "React", "TypeScript", "Vite"];
---
<html lang="en">
<head><meta charset="utf-8" /><title>Script demo</title></head>
<body style="font-family:sans-serif;padding:2rem">
<h1>Tech Stack</h1>
<ul id="list" style="padding-left:1.5rem">
{items.map((item) => <li>{item}</li>)}
</ul>
<button id="toggle" style="margin-top:1rem;padding:.5rem 1rem;cursor:pointer">
Toggle list
</button>
</body>
</html>
<script>
const list = document.getElementById('list');
const btn = document.getElementById('toggle');
btn.addEventListener('click', () => {
list.style.display = list.style.display === 'none' ? '' : 'none';
});
</script>