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

วิธีอ่านโค้ด Astro ที่ไม่ได้เขียนเอง

ทุกโมดูลก่อนหน้านี้สอนให้คุณ เขียน Astro โมดูลนี้สอนอีกครึ่งหนึ่ง: การอ่านโค้ด Astro ที่คนอื่น — หรือ AI — เขียนไว้ แล้วตัดสินว่ามันถูกต้องหรือไม่ AI ผู้ช่วยส่งไฟล์ .astro ที่ compile ผ่าน ที่ astro check ยอมรับ และหน้าตาเหมือนตัวอย่างที่คุณเรียนมาทุกประการ แต่ทั้งหมดนั้นไม่ได้พิสูจน์ว่าโค้ดถูกต้อง — ความผิดของโค้ด Astro ที่ AI เขียนส่วนใหญ่ถูกออกแบบให้มองไม่เห็นจาก type checker อยู่แล้ว ปัญหา training data ของ Astro เองยิ่งทำให้แย่กว่า framework ส่วนใหญ่: โค้ด Astro สาธารณะที่ LLM เคยเห็นส่วนใหญ่มีรูปร่างแบบ Astro 2, 3 หรือ 4 — output: 'hybrid', <ViewTransitions />, Astro.glob(), collection ที่ไม่มี loader — ทั้งหมดนี้ดูลื่นไหลน่าเชื่อถือ แต่ผิดทั้งหมดบน Astro 7.3.5 ที่คอร์สนี้บันทึกไว้ เจ็ดขั้นตอนด้านล่างคือลำดับที่เผยความผิดพลาดเหล่านี้จริง ๆ และบทเรียนถัดไปในโมดูลนี้แต่ละบทจะเจาะลึกทีละส่วน

flowchart TD
  A["1. Rendering mode & adapter<br/>static vs on-demand, prerender, ติดตั้ง adapter หรือยัง"] --> B["2. Island & client directive<br/>ความเคยชิน client:load, prop ที่ต้อง serializable, client:only"]
  B --> C["3. Content & data<br/>ต้องมี loader, render(entry), Astro.glob() หายไปแล้ว"]
  C --> D["4. Astro ฝั่ง server<br/>ทุก action คือ endpoint สาธารณะ"]
  D --> E["5. Security<br/>set:html, secret ที่หลุดข้าม island boundary"]
  E --> F["6. Performance & cache<br/>routeRules กับ Cache-Control, is:inline, transition:persist"]
  F --> G["7. Tooling & production<br/>config key ที่เลิกใช้, astro preview ไม่ใช่ server จริง"]
ลำดับการอ่านไฟล์ Astro ที่ไม่ได้เขียนเอง

ก่อนอื่นให้หาก่อนว่าโค้ด Astro ชิ้นนี้ รันตอนไหน — ตอน build ครั้งเดียว หรือทุกครั้งที่มี request บน server — เพราะทุกคำถามถัดไป (อ่าน cookie ได้ไหม ต้องมี adapter หรือเปล่า header จะทำงานจริงไหม) ขึ้นกับคำตอบนี้ทั้งหมด โค้ดที่ AI เขียนมักสมมติว่าหน้าเพจ render ต่อ request ทั้งที่จริง ๆ ถูก prerender ไว้ (หรือตรงข้ามกัน) และ training data ที่เก่ากว่า Astro v5 ยังใช้ระบบสามโหมดที่ Astro ไม่มีแล้ว

  • สิ่งที่ต้องมองหา: โปรเจกต์ (หรือ route นั้น ๆ) ตั้ง export const prerender = false ไว้จริงหรือไม่ หรือพึ่ง output: 'server' ระดับโปรเจกต์อยู่? config ยังตั้ง output: 'hybrid' — ค่าที่ Astro v5 เอาออกไปแล้ว — อยู่หรือเปล่า? มี adapter ตั้งค่าไว้หรือไม่ ถ้ามี route ใด (หรือ Action หรือ server island) ต้องการ on-demand rendering? มีหน้าที่อ่าน Astro.cookies, Astro.session, หรือ Astro.request.headers ทั้งที่รอบข้างยังบอกว่าเป็น static อยู่หรือไม่?
  • คำถามที่ต้องถาม: HTML ของไฟล์นี้ขึ้นกับว่าใครเป็นคนถามจริง ๆ หรือเหมือนกันทุกคน — และการประกาศ rendering mode ของโค้ดเองตรงกับคำตอบนั้นจริงหรือไม่?
  • บทเรียนที่อธิบายเรื่องนี้: Rendering & Output Modes สำหรับ static กับ on-demand และ build warning จริงที่พิสูจน์แล้วเมื่อหน้า prerender อ่าน request data; SSR & Adapters สำหรับ adapter, output, และเหตุผลที่ 'hybrid' เป็นคำเก่าไปแล้ว

พอรู้แล้วว่าอะไร render ตอนไหน ต่อไปดูว่าอะไรถูกส่งไปที่ browser จริง ๆ component จาก framework ที่ import เข้า .astro จะเป็น static HTML จนกว่าจะมี client directive แปลงมันเป็น island — และ directive ที่ LLM มักเลือกโดยปริยายแทบไม่ใช่ตัวที่ถูกต้องเลย

  • สิ่งที่ต้องมองหา: component ที่โต้ตอบได้ทุกตัวเป็น client:load ไม่ว่าจะอยู่ตำแหน่งไหนของหน้าหรือไม่? client:only เคยปรากฏโดยไม่ระบุ framework บ้างไหม? prop ที่ส่งเข้า island เป็นค่าธรรมดา (serializable) หรือเป็น function/class instance ที่ wire format ส่งข้ามไปไม่ได้? component จาก framework (.tsx/.jsx) ใช้ <slot /> แบบของ Astro เอง โดยคาดหวังว่ามันจะทำงานเหมือนตอนอยู่ใน .astro หรือไม่?
  • คำถามที่ต้องถาม: component นี้ต้องโต้ตอบได้ทันทีที่หน้าโหลดจริงหรือ หรือ trigger ที่ช้ากว่า (client:visible, client:idle) ก็เพียงพอโดยใช้ JavaScript ต้นทางน้อยกว่า — และ prop ทุกตัวที่ island นี้รับจะรอดจากการถูก serialize ลง HTML จริงหรือไม่?
  • บทเรียนที่อธิบายเรื่องนี้: Client Directives สำหรับ directive ทั้งห้า, markup จริงของ <astro-island>, และ error NoClientOnlyHint ที่พิสูจน์แล้ว; Framework Integrations สำหรับสิ่งที่เปลี่ยนไปเมื่อมี UI framework มากกว่าหนึ่งตัว; Sharing State สำหรับ state ที่ต้องข้าม island boundary อย่างถูกต้อง

Content collection คือจุดที่ training data ก่อน v5 เก่าที่สุดเห็นได้ชัด — API ทั้งหมด (ต้องมี loader, render(entry) เป็น standalone function, Astro.glob() หายไป) เปลี่ยนรูปร่าง และโค้ดที่หน้าตาเก่าจะพังในแบบที่ระบุชื่อได้ ไม่ใช่แค่ “compile ไม่ผ่าน”

  • สิ่งที่ต้องมองหา: ทุกการเรียก defineCollection ประกาศ loader หรือไม่? โค้ดเรียก entry.render() แบบ method แทนที่จะ import render จาก astro:content หรือเปล่า? dynamic route ที่ควรเป็น static export getStaticPaths() จริงหรือไม่? frontmatter ถูกตรวจสอบด้วย schema ที่ตรงกับ field จริงหรือไม่? มีที่ไหนยังเรียก Astro.glob() อยู่หรือไม่?
  • คำถามที่ต้องถาม: นี่คือ Content Layer API ปัจจุบัน (มี loader, Zod schema, render(entry)) หรืออ่านแล้วเหมือน tutorial ที่เขียนก่อน Astro 5 — และ astro check/astro sync จะจับความต่างนี้ได้จริงไหม หรือต้อง build จริงเท่านั้นถึงจะเห็น?
  • บทเรียนที่อธิบายเรื่องนี้: Content Collections สำหรับรูปร่าง loader/schema และ runtime error จริงของ entry.render(); Dynamic Routes สำหรับ getStaticPaths() และ build error ที่มีชื่อจริงของมัน; Data Fetching สำหรับเหตุผลที่ Astro.glob() กลายเป็น type error ไม่ใช่แค่สไตล์การเขียน; Schemas & References สำหรับ error การตรวจสอบ Zod จริงเมื่อ schema กับ frontmatter ไม่ตรงกัน

พื้นผิวฝั่ง server ของ Astro — Action, endpoint, middleware, session — หน้าตาเหมือนการเรียกฟังก์ชันธรรมดาจากในโค้ดเบส ซึ่งนั่นแหละที่ทำให้ลืมง่ายว่าส่วนใหญ่มันก็เป็น endpoint สาธารณะด้วย ทันทีที่เป็น on-demand

  • สิ่งที่ต้องมองหา: handler ของ Action ตรวจ context.locals.user (หรือเทียบเท่า) เป็นบรรทัดแรกหรือไม่ หรือสมมติว่าผู้เรียกถูก authenticate แล้วเพราะ form render เฉพาะหลัง login? middleware เรียก (และ return) next() ในทุกเส้นทางของฟังก์ชันเสมอหรือไม่? หลัง login แล้ว มีการ regenerate session บ้างไหม?
  • คำถามที่ต้องถาม: ถ้า curl endpoint นี้ตรง ๆ โดยไม่มี session cookie เลย จะเกิดอะไรขึ้น — และคำตอบนั้นตรงกับสิ่งที่ UI เองกันไว้หรือไม่?
  • บทเรียนที่อธิบายเรื่องนี้: Actions สำหรับ 401 จริงที่พิสูจน์แล้วเมื่อไม่มีการตรวจ auth; Middleware & Sessions สำหรับลำดับของ sequence(), context.locals, และ session fixation; Endpoints & API Routes สำหรับกฎการเข้าถึงสาธารณะแบบเดียวกันที่ใช้กับ endpoint ธรรมดา

จุดพังด้าน security เฉพาะของ Astro ที่พบซ้ำ ๆ ในโค้ดที่ AI เขียนมีสองแบบ และไม่มีแบบไหนเป็น compiler error เลย: การฉีด HTML ดิบ กับ secret ที่หลุดจากโค้ดฝั่ง server ไปยังสิ่งที่ browser ได้รับจริง

  • สิ่งที่ต้องมองหา: set:html เคย render string ที่ย้อนกลับไปหา input ของ user โดยไม่มี sanitizer จริงคั่นกลางหรือไม่? ค่าที่อ่านจาก import.meta.env/astro:env/server เคยถูกส่งเป็น prop ให้ island ที่ client hydrate หรือไม่ — แม้ module ต้นทางจะเป็น server-only จริง ๆ ก็ตาม?
  • คำถามที่ต้องถาม: ถ้าค่านี้ไปโผล่ใน source ของหน้า (view-source ไม่ใช่แค่ log ฝั่ง server) ยอมรับได้ไหม — และมีอะไรใน code path นี้ที่หยุดสิ่งนั้นจริง ๆ หรือแค่ดูเหมือนหยุดเฉย ๆ?
  • บทเรียนที่อธิบายเรื่องนี้: Astro Syntax Deep Dive สำหรับ payload XSS จริงที่ set:html รันออกมา พิสูจน์ด้วย Container API; SSR & Adapters สำหรับสิ่งที่ boundary context/access ของ astro:env บังคับใช้จริง — และสำคัญพอ ๆ กัน สิ่งที่มันไม่ได้บังคับ

Astro มีคันโยกหลายอันที่ ฟังดู เหมือนเป็นอันเดียวกัน — cache header, config route cache, การ optimize รูป, script ที่ใช้ร่วมกันกับที่ซ้ำ, DOM ที่ persist ข้ามการนำทาง — และโค้ดที่ AI เขียนมักสมมติว่าคันโยกหนึ่งทำหน้าที่ของอีกอันหนึ่ง

  • สิ่งที่ต้องมองหา: routeRules/cache ถูกมองว่าเป็นตัวตั้ง header Cache-Control ที่ browser เห็นหรือไม่? is:inline ถูกใช้กับ script ที่ตั้งใจให้รันครั้งเดียว ใช้ร่วมกันต่อหน้าหรือไม่? <Image> ระยะไกลที่ hardcode width/height สมมติว่า image.domains ป้องกันมันอยู่หรือไม่? transition:persist ปรากฏบน element ที่ไม่มีคู่ตรงกันแน่นอนบนหน้าปลายทางหรือไม่?
  • คำถามที่ต้องถาม: ตาม header, script tag, หรือ node ที่ persist นี้ไปจนถึงสิ่งที่ browser จริงได้รับ — มันเป็นกลไกที่โค้ดสมมติไว้จริงหรือเป็นอีกกลไกหนึ่งที่ชื่อคล้ายกัน?
  • บทเรียนที่อธิบายเรื่องนี้: Caching & Edge สำหรับความต่างจริงที่พิสูจน์แล้วระหว่าง Astro.response.headers กับ routeRules/cache; Images & Assets สำหรับจุดที่ image.domains บังคับใช้จริง (และไม่บังคับใช้); View Transitions สำหรับการจับคู่ของ transition:persist และ <ClientRouter /> ที่เปลี่ยนชื่อ

สุดท้าย ตรวจว่าการตั้งค่าโปรเจกต์รอบข้าง — config key, คำสั่งที่ใช้ “ทดสอบ” build production — เป็นปัจจุบันหรือไม่ เพราะโค้ดที่ AI เขียนอาจถูกต้องเฉพาะจุด แต่ยังอยู่ใน setup ที่เก่าหรือทำให้เข้าใจผิด

  • สิ่งที่ต้องมองหา: astro.config.mjs ยังตั้ง markdown.remarkPlugins/rehypePlugins/gfm เป็น top-level key ของ markdown.* อยู่หรือไม่? astro preview เคยถูกใช้ (หรือ script ให้รัน) เป็น production server จริงหรือไม่?
  • คำถามที่ต้องถาม: schema config ของ Astro เวอร์ชันที่ ติดตั้งจริง ยังรับ key นี้ตามที่โค้ดสมมติไว้หรือไม่ — และคำสั่งที่ใช้ “พิสูจน์ว่าใช้งานได้จริงบน production” เป็นคำสั่งที่เอกสารของ Astro เองบอกว่าใช้แบบนั้นได้อย่างปลอดภัยจริงหรือไม่?
  • บทเรียนที่อธิบายเรื่องนี้: Config & Integrations สำหรับ Markdown key ระดับบนสุดที่เลิกใช้แล้ว; Testing & Tooling สำหรับ exit code ของ astro check และตำแหน่งของ eslint-plugin-astro
  • AI Bug Catalog — 22 pattern เฉพาะ แต่ละอันมีโค้ดที่ AI มักเขียน, error/warning/พฤติกรรมจริงที่มันสร้าง, และวิธีแก้
  • Review Checklist — 22 pattern เดียวกัน ในรูป checklist หน้าเดียว เจ็ดกลุ่ม
  • Verification Tools — สิ่งที่ astro check, astro build, Container API, eslint-plugin-astro, และ harness ของคอร์สนี้เองพิสูจน์ได้จริง
  • worked review สามชิ้น — Worked Review 1, Worked Review 2, Worked Review 3 — ใช้ลำดับการอ่านนี้กับไฟล์ที่ AI เขียนเต็มไฟล์
ลำดับการอ่านไฟล์ Astro ที่ไม่ได้เขียนเอง:
1. Rendering mode & adapter → รันตอนไหนจริง ๆ และ config ตรงกันหรือไม่?
2. Island & client directive → อะไรถูกส่งไป browser และ prop ทุกตัวรอดการเดินทางไหม?
3. Content & data → นี่คือ Content Layer API ปัจจุบัน หรือหน้าตาก่อน v5?
4. Astro ฝั่ง server → ถ้า curl ตรง ๆ โดยไม่มี session จะเกิดอะไร?
5. Security → HTML ดิบหรือ secret เคยข้าม trust boundary ไหม?
6. Performance & cache → คันโยกนี้คือที่โค้ดสมมติไว้ หรืออีกอันที่ชื่อคล้ายกัน?
7. Tooling & production → config เป็นปัจจุบันไหม และ "preview" ถูกใช้แทน "production" หรือเปล่า?
การกระโดดไปถาม "astro check ผ่านไหม" ตอบคำถามข้างต้นแทบไม่ได้เลย — ส่วนใหญ่ของ
catalog ในโมดูลนี้ผ่าน astro check โดยไม่มี error หรือ warning เลยสักตัว
  • “ถ้า astro check ผ่าน แปลว่าโค้ดถูกต้อง” entry ส่วนใหญ่ใน catalog ของโมดูลนี้ผ่าน astro check สะอาด — มันแค่ type-check .astro/.ts/.tsx ไม่ได้รันหน้าเพจ ไม่ได้ build จริง ไม่ได้ยิง request ไปที่ endpoint
  • “โค้ด Astro ที่ AI เขียนแค่ต้องการ prompt ใหม่กว่านี้” ปัญหาไม่ใช่แค่ความเก่า — output: 'hybrid', Astro.glob(), และ <ViewTransitions /> คือ Astro ที่ ดูลื่นไหลน่าเชื่อถือ syntax ถูกต้องทุกอย่าง แต่บังเอิญเล็งไปที่เวอร์ชันที่ 7.3.5 ที่ติดตั้งในคอร์สนี้ไม่มีแล้ว
  • “ความล้มเหลวแบบเงียบเกิดน้อยกว่า build error เลยสำคัญน้อยกว่า” ใน catalog นี้ตรงข้ามเลย: secret ที่หลุดใน prop ที่ serialize ของ island หรือ transition:persist ที่หยุด persist แบบเงียบ ๆ ขึ้น production ได้โดยไม่มี error ไม่มี warning ไม่มีสัญญาณอะไรเลยว่ามีอะไรผิด
  • “โมเดล rendering ของ Astro เรียบง่าย เลยมีอะไรให้ review น้อยกว่า framework ฝั่ง client” Astro เพิ่มแกนที่ framework ฝั่ง client ไม่มีเลย — เวลาที่ โค้ดชิ้นหนึ่งรัน (ตอน build หรือตอน request) เปลี่ยนสิ่งที่ API ทุกตัวใน catalog นี้ได้รับอนุญาตให้ทำ
ทำไมโมดูลนี้ถึงตรวจ rendering mode กับ adapter ก่อนตรวจเรื่อง island หรือ content?
การอ่าน config นี้ก่อนเผยอะไร?
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({ output: 'hybrid' });
ทำไม 'astro check ผ่านไหม' ถึงตอบคำถามส่วนใหญ่ในลำดับการอ่านเจ็ดขั้นของโมดูลนี้ไม่ได้?
ขั้นไหนในลำดับการอ่านนี้ที่ถามตรง ๆ ว่า 'ถ้า curl endpoint นี้ตรง ๆ โดยไม่มี session จะเกิดอะไรขึ้น'?
ทำไมโมดูลนี้จึงจัด 'secret ที่ส่งเข้า prop ของ island' ไว้ในกลุ่ม Security แทนที่จะเป็นกลุ่ม Island ทั้งที่เกี่ยวกับ client directive?