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

Rendering และ Output Modes

Astro render HTML ของ route ได้ในสองจังหวะที่ต่างกัน และตัวเลือกนี้เป็นแกนกลางของวิธี deploy:

  • Static (SSG) — HTML ถูก build ครั้งเดียว ตอน build time แล้ว serve เป็นไฟล์ธรรมดา เร็ว cache ได้ deploy ไปยัง static host หรือ CDN ไหนก็ได้ เหมาะกับ content ที่เหมือนกันสำหรับทุกคน
  • On-demand (SSR) — HTML ถูก build แต่ละ request บน server จึงใช้ข้อมูลตอน request ได้ (user ที่ login อยู่, cookies, query params, ข้อมูล live) ต้องมี runtime ซึ่งแปลว่าต้องมี adapter
flowchart TB
  subgraph static["Static (SSG)"]
    b["build time"] --> f["ไฟล์ HTML"] --> cdn["serve จาก CDN"]
  end
  subgraph ssr["On-demand (SSR)"]
    r["แต่ละ request"] --> srv["server render HTML"] --> resp["response"]
  end
Static build ครั้งเดียว; on-demand render แต่ละ request

ค่า default คือ static คุณ opt โปรเจกต์ (หรือ route เดียว) เข้าสู่ on-demand rendering เมื่อหน้านั้นจำเป็นต้องใช้ข้อมูลตอน request จริง ๆ

คุณไม่ได้เลือก mode เดียวสำหรับทั้งเว็บ ในโปรเจกต์ที่เปิด server ไว้ (output: 'static' พร้อม adapter หรือ output: 'server') แต่ละ route ตัดสินใจ เองด้วย export const prerender:

---
// A mostly-static site with one dynamic route:
export const prerender = false; // render THIS page on demand, per request
const user = await getUser(Astro.request); // needs the incoming request
---
<p>Welcome, {user.name}</p>
  • export const prerender = true → build route นี้เป็นไฟล์ static (แม้จะอยู่ในโปรเจกต์ที่เปิด server)
  • export const prerender = false → render route นี้แบบ on-demand (แม้จะอยู่ในโปรเจกต์ที่ส่วนใหญ่เป็น static)

นี่คือทางสายกลางที่ทรงพลัง: เว็บที่ 95% เป็นไฟล์ static พร้อม route แบบ on-demand ไม่กี่ตัว (dashboard, form handler) โดยแต่ละตัว render เฉพาะเมื่อจำเป็น Astro 4.x เรียกการผสมนี้ว่า hybrid output วันนี้มันก็แค่สิ่งที่ 'static' ทำเมื่อมีอย่างน้อยหนึ่ง route opt out ด้วย prerender = false (ดู callout ข้างบน)

astro build จริงจะ print ออกมาตรง ๆ ว่า route ไหนถูก prerender เป็นไฟล์ static — อะไรก็ตามที่ ไม่ อยู่ในลิสต์นั้น ในโปรเจกต์ที่เปิด server ไว้ กำลังถูก render แบบ on-demand แทน สาม route: หนึ่งที่ตั้งใจให้ static ชัดเจน หนึ่งที่ตั้งใจให้ on-demand ชัดเจน และหนึ่งที่ใช้ default:

src/pages/mixed/static-about.astro
---
export const prerender = true;
---
<html><body><h1>Static about page</h1></body></html>
src/pages/mixed/dashboard.astro
---
export const prerender = false;
const cookieHeader = Astro.request.headers.get('cookie');
---
<html><body><h1>Dashboard for request with cookie: {cookieHeader}</h1></body></html>
$ npx astro build --outDir dist
prerendering static routes
├─ /mixed/static-about/index.html (+13ms)
├─ /index.html (+1ms)
✓ Completed in 27ms.
[build] Rearranging server assets...
[build] Server built in 467ms
[build] Complete!

/mixed/static-about/index.html อยู่ในลิสต์ “prerendering static routes” — ไฟล์ HTML จริงที่ถูกเขียนไปที่ dist/ ส่วน /mixed/dashboard ไม่โผล่ในนั้นเลย: มันถูก compile เข้าไปใน server entry แทน เพื่อ render ใหม่ทุกครั้งที่มี request build log เองนี่แหละคือหลักฐานว่าแต่ละ route ได้ mode ไหนจริง ๆ ไม่ใช่แค่สิ่งที่ source file ประกาศไว้

Static output ไม่ต้องการอะไรนอกจาก file host ส่วน on-demand rendering ต้องการ runtime เพื่อรัน server code ของคุณแต่ละ request — และนั่นคือที่ที่ adapter เข้ามา คุณติดตั้ง adapter สำหรับ platform เป้าหมายแล้วตั้งค่าใน config:

// astro.config.mjs — enabling on-demand rendering on Cloudflare
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare(), // or node(), vercel(), netlify()...
});

adapter แปลง server rendering ของ Astro ให้เป็น format ของ platform (Cloudflare Worker, Node server, Vercel function) ถ้าไม่มี adapter feature แบบ on-demand (SSR routes, server islands, Actions endpoints) จะรันไม่ได้ — เป็น error ที่เจอบ่อยตอน deploy ครั้งแรก: “an adapter is required” บท SSR & adapters พูดถึงการเลือกและ config adapter แบบละเอียด

astro build รัน component script ของคุณ, render HTML, bundle CSS และ island JavaScript ใด ๆ แล้วเขียน output ไปที่ dist/ สำหรับ static site นั่นคือโฟลเดอร์ของไฟล์ .html และ asset ที่คุณโยนลง CDN ไหนก็ได้ สำหรับเว็บที่เปิด server ไว้ dist/ ยังมี server entry ที่ adapter รันด้วย ไม่ว่าทางไหน browser ก็ได้รับ HTML ที่เสร็จแล้วบวกเฉพาะ island JS — คำมั่นพื้นฐานที่ build ส่งมอบให้

route ที่ prerender รันครั้งเดียว ตอน build time — ไม่มี request ที่เข้ามาจริงให้อ่านอะไรที่เฉพาะเจาะจงกับ request เลย Astro.cookies, Astro.session, และการอ่าน Astro.request.headers ล้วนต้องพึ่ง request จริง ดังนั้นการใช้อันไหนก็ตามบนหน้าที่ยังตั้ง prerender = true อยู่ (หรือในโปรเจกต์ 'static' ที่ไม่มี override ต่อ route) จะเกิด warning จริงที่พิสูจน์ได้ตอน build — ไม่ใช่แค่เงียบ ๆ ไม่ทำอะไร:

src/pages/mixed/static-session.astro
---
export const prerender = true;
const views = await Astro.session?.get('views');
---
<html><body><p>{views}</p></body></html>
$ npx astro build --outDir dist
[WARN] `Astro.request.headers` was used when rendering the route `src/pages/mixed/static-session.astro'`. `Astro.request.headers` is not available on prerendered pages. If you need access to request headers, make sure that the page is server-rendered using `export const prerender = false;` or by setting `output` to `"server"` in your Astro config to make all your pages server-rendered by default.

นั่นคือ build warning จริง reproduce กับ Astro.session โดยตรง (ทั้ง Astro.session และ Astro.cookies อ่าน header ของ request ข้างใต้เหมือนกัน จึงขึ้น warning เดียวกันเป๊ะ) ค่าที่ได้กลับมาเป็น undefined ตอน build — ไม่มี request ให้มี session หรือ cookie เลย วิธีแก้คือตามที่ warning บอกตรง ๆ: export const prerender = false บน route นั้น ย้ายมันเข้าไปในกลุ่ม on-demand ที่พิสูจน์ไว้ข้างบน

snippet เกี่ยวกับ rendering mode ที่ AI generate มา ควรเช็คกับสิ่งที่ยังเป็นจริงใน Astro 7:

  • มันตั้ง output: 'hybrid' หรือเปล่า? นั่นคือ config error จริงบน 7.3.5 — ถูกถอดออกใน v5 พิสูจน์แล้วข้างบนด้วยข้อความ upgrade guide เป๊ะ ๆ 'static' (default) ครอบคลุมมันอยู่แล้ว
  • หน้าไหนอ่าน Astro.cookies, Astro.session, หรือ Astro.request.headers โดยไม่มี export const prerender = false อยู่ใกล้ ๆ เลย และไม่มี adapter/output: 'server' ใน config หรือเปล่า? พิสูจน์แล้วข้างบน: นั่นคือ build-time warning จริง (“not available on prerendered pages”) และค่าที่ได้กลับมาคือ undefined — ไม่ใช่ข้อมูลตอน request ที่ snippet คาดว่าจะได้
  • มันอ้างว่า route “กลายเป็น on-demand อัตโนมัติ” เพราะ import อะไรที่เกี่ยวกับ request หรือเปล่า? rendering mode ถูกควบคุม แค่ ด้วย export const prerender (หรือ output ของทั้งโปรเจกต์) เท่านั้น — การ import Astro.cookies ไม่ได้พลิก mode ให้ มันแค่ warn (แล้วคืนอะไรที่ใช้ไม่ได้) ถ้า mode ยังไม่ใช่ on-demand อยู่แล้ว
  • มันปฏิบัติกับ “static” และ “on-demand” เหมือนเป็นตัวเลือกทั้งเว็บแบบ all-or-nothing หรือเปล่า? พิสูจน์แล้วข้างบน: โปรเจกต์เดียวผสมทั้งสองแบบได้ ตัดสินใจทีละ route

🐛 หา bug ในโค้ดนี้

src/pages/account/settings.astro
---
const user = Astro.cookies.get('session_id');
// no export const prerender anywhere in this file
---
<h1>Settings for {user?.value}</h1>
เฉลย

เมื่อไม่มี export const prerender = false ในไฟล์นี้เลย และไม่มีอะไรใน config ของโปรเจกต์บังคับ output: 'server' route นี้จะ prerender โดย default — render ตอน build time ครั้งเดียว ไม่มี request ให้อ่าน cookie เลย นี่คือ warning Astro.request.headers-บนหน้าที่ถูก prerender ที่พิสูจน์แล้วข้างบนเป๊ะ ๆ (Astro.cookies.get อ่าน request header ข้างใน) และ user จะได้ undefined กลับมาสำหรับทุกคนที่เข้าชม ตอน build time ครั้งเดียว ตลอดไป — ไม่ใช่ต่อผู้เข้าชมแบบที่โค้ดสื่อ วิธีแก้: เพิ่ม export const prerender = false เพื่อให้ route นี้ render ต่อ request จริง ๆ Astro.cookies จะได้มี request จริงให้อ่าน

rendering mode ของ route ตอบคำถามเดียว: “HTML ของหน้านี้ขึ้นกับว่าใครถามหรือเปล่า หรือมันเป็นไฟล์เดียวกันสำหรับทุกคน?” ถ้าเหมือนกันสำหรับทุกคน ก็ prerender ครั้งเดียวแล้ว serve ไฟล์นั้นตลอดไป — นั่นคือสิ่งที่ default ทำ ทันทีที่หน้าต้องรู้อะไรเกี่ยวกับ request ที่เข้ามาจริง ๆ (cookie, session, query param ที่เปลี่ยนคำตอบ) มันต้องรัน ระหว่าง request นั้น ไม่ใช่ล่วงหน้า — นั่นคือ prerender = false การพิสูจน์ข้างบนแสดงทั้งสองทิศทางของความผิดพลาด: prerender บางอย่างที่ต้องการ request (ได้ undefined เงียบ ๆ, warning) และไม่รู้ว่า static output ยังพก route แบบ on-demand ไม่กี่ตัวไปด้วยกันได้

Prerendered (prerender = true / default static) On-demand (prerender = false)
รันตอนไหน ครั้งเดียว ตอน build time ทุก request
อ่าน Astro.cookies/Astro.session/request headers ได้ไหม ไม่ได้ — warn แล้วคืนอะไรที่ใช้ไม่ได้ พิสูจน์แล้วข้างบน ได้
Hosting static host/CDN ไหนก็ได้ ต้องมี adapter + runtime
Latency เร็วที่สุด — เป็นไฟล์ ขึ้นกับ cold start ของ server/edge, DB call ฯลฯ
เหมาะกับ content ที่เหมือนกันสำหรับทุกคนที่เข้าชม ข้อมูลต่อ user หรือต่อ request
  • “output: 'hybrid' ยังเป็นวิธีผสม static กับ dynamic route อยู่” มันถูกถอดออกใน Astro v5 — พิสูจน์แล้วข้างบนด้วยข้อความ upgrade guide เป๊ะ ๆ 'static' บวก prerender = false ต่อ route คือวิธีปัจจุบัน (และวิธีเดียว)
  • “อ่าน Astro.cookies บนหน้า static แค่คืน undefined เงียบ ๆ” พิสูจน์แล้วข้างบน: มันส่ง build-time warning จริงด้วย ที่ระบุ route ตรง ๆ และอธิบายวิธีแก้ — มันดัง ไม่ได้เงียบ ถ้าคุณดู build output อยู่
  • “โปรเจกต์เป็นได้แค่ static site หรือ server-rendered site อย่างใดอย่างหนึ่ง ไม่ใช่ทั้งคู่” พิสูจน์แล้วข้างบนด้วย build log จริง: โปรเจกต์เดียวผลิตทั้งไฟล์ .html ที่ prerender และ route on-demand ที่ compile เข้า server entry เคียงข้างกัน
  • “ตั้ง prerender = false เป็นวิธีเดียวที่ adapter จะเข้ามาเกี่ยวข้อง” server islands กับ Actions ก็ต้องมี adapter เหมือนกัน แม้บนเว็บที่ static ล้วน (prerender = true ทุกหน้า)
ความต่างระหว่าง static กับ on-demand rendering คืออะไร?
คุณจะทำให้ route เดียว render แบบ on-demand ในโปรเจกต์ที่ส่วนอื่นเป็น static ยังไง?
adapter มีไว้ทำอะไร?
คุณ deploy เว็บที่มี SSR route แต่ไม่ได้ตั้งค่า adapter จะเกิดอะไรขึ้น?
warning จริงนี้ขึ้นตอน build หน้าที่อ่าน Astro.session โดยมี prerender = true เกิดอะไรขึ้นจริง ๆ?
[WARN] `Astro.request.headers` was used when rendering the route `src/pages/mixed/static-session.astro'`. `Astro.request.headers` is not available on prerendered pages...
บน Astro 7.3.5 config นี้จะเกิดอะไรขึ้น?
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({ output: 'hybrid' });