ApexHuasคู่มือช่างเว็บ GitHub repo

บทที่ 8 — 3 บั๊กจริงที่ CI จับได้ (และวิธีคิดตอนดีบัก)

ทั้งสามเคสนี้เกิดขึ้นจริงในโปรเจกต์นี้ ทุกเคส ผ่านฉลุยบนเครื่องคนเขียน แต่ CI จับได้ — เปิด PR เก่าดูประกอบได้เลย

เคสที่ 1: npm ci ล้ม เพราะ lock file ไม่ตรง

อาการ — PR #4 ทั้งสอง job แดงภายใน 20 วินาที:

npm error `npm ci` can only install packages when your package.json and
package-lock.json are in sync.
npm error Missing: @emnapi/[email protected] from lock file

สาเหตุ — ตอนติดตั้ง lucide-react (ชุดไอคอน) บน macOS ตัว npm บันทึกเฉพาะ dependency ย่อยที่เครื่อง Mac ต้องใช้ลง package-lock.json แต่พอ CI รันบน Linux มันต้องการแพ็กเกจย่อยอีกชุดที่ไม่มีในไฟล์ → npm ci ที่เข้มงวดเลยปฏิเสธทันที

สิ่งที่ลองแล้วไม่หาย — สั่ง npm install --package-lock-only เพื่ออัปเดต lock file แล้ว push ใหม่ → ยังแดงเหมือนเดิม เพราะมันอัปเดตแบบเพิ่มจากของเดิมซึ่งยังขาดข้อมูลข้ามแพลตฟอร์มอยู่

วิธีแก้ที่ได้ผล — สร้าง lock file ใหม่ทั้งไฟล์:

rm -rf node_modules package-lock.json
npm install

บทเรียนpackage-lock.json ต้อง commit ขึ้น repo เสมอ และเวลามันพังแบบ "ขาดของ" ให้สร้างใหม่ทั้งไฟล์ ดีกว่าไล่แก้ทีละบรรทัด

เกร็ด: บั๊กนี้เกิดซ้ำอีกครั้งตอนสร้างเว็บคู่มือที่คุณกำลังอ่านอยู่นี่แหละ — ติดตั้ง library ตัวเดียว (marked) บน Mac แล้ว CI แดงด้วย error บรรทัดเดียวกันเป๊ะ นี่คือเหตุผลที่เราจดเคสพวกนี้ไว้ พอเจอซ้ำก็แก้จบใน 2 นาทีแทนที่จะงงอีกครึ่งชั่วโมง

เคสที่ 2: typecheck ล้มบนเครื่องสะอาด

อาการ — CI แดงที่ด่าน typecheck ทั้งที่ในเครื่องผ่าน:

src/app/layout.tsx(20,50): error TS2304: Cannot find name 'LayoutProps'.

สาเหตุ — Next.js 16 สร้าง type บางตัว (เช่น LayoutProps, PageProps) ให้อัตโนมัติตอนรัน dev หรือ build โดยเก็บไว้ในโฟลเดอร์ .next/types ซึ่งไม่ได้ commit ขึ้น repo (มันเป็นไฟล์ที่สร้างใหม่ได้)

เครื่องเราเคยรัน npm run dev มาก่อน ไฟล์เลยมีอยู่แล้ว → ผ่าน CI เป็นเครื่องใหม่ที่ไม่เคยรันอะไร → ไม่มีไฟล์ → พัง

วิธีพิสูจน์ว่าคิดถูก (ขั้นตอนสำคัญที่สุด — อย่าเดาแล้วแก้เลย):

rm -rf .next          # ทำให้เครื่องตัวเองสะอาดเหมือน CI
npx tsc --noEmit      # พังเหมือนกันจริง ✓ เท่ากับเข้าใจสาเหตุถูกแล้ว

วิธีแก้ — เพิ่มขั้นตอนสร้าง type ก่อน typecheck ใน ci.yml:

- name: Generate route types
  run: npx next typegen
- name: Typecheck
  run: npx tsc --noEmit

บทเรียน — "ในเครื่องผมผ่านนะ" เป็นประโยคอันตราย ต่างกันตรง สภาพแวดล้อม ไม่ใช่ตรงโค้ด เวลาเจอแบบนี้ให้ทำเครื่องตัวเองให้เหมือน CI แล้วไล่ดู

เคสที่ 3: กฎ lint ของ React จับปัญหาที่กำลังจะเกิด

อาการ — PR #7 (ระบบตะกร้า) ด่าน lint แดง:

error  Calling setState synchronously within an effect can trigger cascading renders

เบื้องหลัง — ก่อนหน้านั้นเราเจอบั๊กจริงในเบราว์เซอร์อยู่แล้ว: ใส่ของลงตะกร้า → เข้าหน้าตะกร้า → ของหาย ตะกร้าว่างเปล่า

โค้ดตอนนั้นเขียนด้วย effect สองตัว: ตัวหนึ่งอ่านตะกร้าจาก localStorage ตอนเปิดหน้า อีกตัวเขียนตะกร้าลง localStorage ทุกครั้งที่ข้อมูลเปลี่ยน ผลคือรอบแรกสุด ตัว "เขียน" ทำงานก่อนที่ตัว "อ่าน" จะเสร็จ → เขียนค่าว่างทับของจริงที่เก็บไว้

วิธีแก้ — เลิกใช้ effect แล้วเปลี่ยนไปใช้ useSyncExternalStore ซึ่งเป็นเครื่องมือที่ React ออกแบบมาสำหรับ "ข้อมูลที่อยู่นอก React" (เช่น localStorage) โดยเฉพาะ:

const items = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);

ผลพลอยได้คือได้แก้ปัญหาอื่นไปด้วย — ตัวเลขบน badge ตะกร้าทุกจุดในหน้าอัปเดตพร้อมกัน และไม่มีอาการหน้ากระพริบตอนโหลด

บทเรียน — กฎ lint ไม่ใช่เรื่องความสวยงามของโค้ด หลายข้อมาจากบั๊กที่คนทั้งโลกเคยเจอมาแล้ว เจอ lint เตือนให้อ่านว่ามันเตือนเรื่องอะไร อย่าเพิ่งรีบปิดกฎ

วิธีคิดเวลาดีบัก (ใช้ได้ทุกเคส)

4 ขั้นที่โปรเจกต์นี้ใช้ทุกครั้งที่เจอบั๊ก

1. ทำให้พังซ้ำให้ได้

ถ้ายังทำให้เกิดซ้ำไม่ได้ แปลว่ายังไม่รู้ว่าปัญหาคืออะไร — การแก้ตอนนี้คือการเดา ตัวอย่างเคส 2: ลบโฟลเดอร์ .next แล้วพังซ้ำได้ = ผ่านขั้นนี้

rm -rf .next && npx tsc --noEmit
1 / 4

สั่ง AI ด้วยกระบวนการนี้ได้เลย เช่น 'ทำให้บั๊กเกิดซ้ำให้ดูก่อน แล้วค่อยเสนอวิธีแก้'

สิ่งที่ห้ามทำ: แก้แบบเดาสุ่มไปเรื่อย ๆ จนบังเอิญเขียว เพราะคุณจะไม่รู้ว่ามันหายจริงหรือแค่ซ่อนอยู่

ลองทำเอง: เปิด https://github.com/Natt-Woramet/apexhuas/pull/4 แล้วไล่ดูตามลำดับเวลา — จะเห็นว่า CI แดงก่อน มี commit แก้ตามมา แล้วค่อยเขียว ลองอ่าน commit message ของ commit ที่แก้ดูว่าเขาอธิบายสาเหตุไว้ยังไง

สรุปครึ่งแรก

ถึงตรงนี้คุณควรทำได้แล้ว: สั่งงาน AI ให้ได้ผลที่ใช้ได้, clone repo, แตก branch, commit, เปิด PR, อ่านผล CI และแก้เมื่อมันแดง

ต่อจากนี้คือครึ่งหลัง — เรื่องที่ทำให้โค้ดที่ merge แล้วกลายเป็นเว็บจริงบนอินเทอร์เน็ต: Docker, server, โดเมน, HTTPS และท่อ deploy อัตโนมัติ