ข้ามไปยังเนื้อหา

Server-Only & Scripts

นี่คือแนวคิดที่ทำให้นักพัฒนา React ประหลาดใจมากที่สุด ใน React ทุก component ที่คุณเขียนจะส่ง JavaScript ไปยัง browser แม้แต่ component ที่แค่ render หัวข้อก็ตาม Astro กลับด้านของ default นี้: .astro component ธรรมดาผลิต JavaScript ออกมาเป็นศูนย์

React
// 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.
Astro
---
// 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 ไม่มีอะไรเลย

เพราะ 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 (จะกล่าวถึงในโมดูลถัดไป)

เมื่อคุณต้องการ JavaScript ฝั่ง browser เพียงเล็กน้อย — เช่น toggle, counter หรือ event สำหรับ analytics — Astro ให้คุณใส่ tag <script> เข้าไปใน template ได้โดยตรง Astro จะ bundle และ deduplicate script เหล่านี้ให้อัตโนมัติ

React
// Counter.jsx — React
// Ships React + hydration + component JS
import { 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>
);
}
Astro
---
// 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:*)

Astro
---
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>
.astro component ธรรมดาส่ง JavaScript ไปยัง browser เท่าไร?
คุณต้องการ click handler ที่ toggle เมนูใน .astro component วิธีที่เบาที่สุดซึ่งยังอยู่ใน .astro (ไม่ใช้ island) คืออะไร?
เกิดอะไรขึ้นกับ tag `<script>` ภายใน .astro component เมื่อ component นั้นถูกใช้หลายครั้งในหน้าเดียวกัน?
ข้อใดที่คุณ 'ทำได้' ภายใน frontmatter ของ Astro component?