Skip to content
Watchara NoisriphanAI พารวย by Watchara | AI Builder

พิมพ์เพื่อเริ่มค้นหา

ต่อ analytics จดหมายข่าว และฟอร์มติดต่อ — และบั๊กเงียบสองตัวที่หน้าเว็บไม่ฟ้องอะไรเลย

เก็บงานที่ค้างจากรอบก่อนให้ครบทั้งสามระบบ แล้วเจอบั๊กเงียบสองตัว — copy id ผิดช่องจนฟอร์มพาคนไปหน้า 404 และ CSP บล็อก analytics อยู่หลายวันโดยที่หน้าเว็บดูปกติทุกอย่าง

What I worked on

รอบที่แล้วจบด้วย next step ว่า “ต่อระบบจดหมายข่าวกับ analytics” — รอบนี้เก็บให้ครบทั้งสามระบบ

ก่อนเริ่มไล่ดูก่อนว่ามีอะไรที่ยังไม่ได้ต่อบ้าง เจอ 4 อย่าง: analytics, จดหมายข่าว, ฟอร์มติดต่อ และอีเมลในหน้า contact ที่ยังเว้นว่างไว้

ที่ต่อไปแล้วสามตัว: Umami Cloud สำหรับ analytics, Buttondown สำหรับจดหมายข่าว, Formspree สำหรับฟอร์มติดต่อ

รายละเอียดว่าแต่ละตัวสมัครยังไง ราคาเท่าไหร่ ต้องเอาอะไรมาแปะ เขียนแยกไว้ที่ บทความนี้

What changed

เว็บที่เดิมไม่มีอะไรเชื่อมต่อเลย ตอนนี้เก็บสถิติผู้เข้าชมได้ รับสมัคร subscriber ได้ และรับข้อความจากคนติดต่อได้ โดยยังไม่มีเซิร์ฟเวอร์เป็นของตัวเองสักตัว

ค่าใช้จ่ายรวม: ศูนย์บาท ทั้งสามตัวอยู่ใน free tier

เรื่องที่คิดว่าตัดสินใจถูกตั้งแต่แรกคือการให้ทุกอย่างอ่านค่าจาก environment variable ตอนต่อระบบเลยไม่ได้แก้โค้ดเลยสักบรรทัด — เติมแค่ค่า 5 ตัว (โค้ดที่แก้ทีหลังเป็นเรื่องกันบั๊กซ้ำ ไม่ใช่การต่อระบบ)

What failed

1. ผมแนะนำบริการที่ไม่ตรงกับที่ใช้จริง

ตอนเริ่มคุยกัน AI เสนอให้ใช้ Cloudflare Web Analytics ทั้งที่เว็บนี้อยู่บน Vercel เพราะในโปรเจกต์มีทั้งไฟล์ vercel.json และ _headers (ซึ่งเป็นของ Cloudflare) เลยเดาโฮสต์ผิด ผมต้องถามกลับไปว่า “ยังจำเป็นต้องใช้ Cloudflare เหรอทั้งที่ใช้ Vercel” ถึงได้แก้เป็นตัวเลือกที่เหมาะกว่า

บทเรียนคือของที่ค้างอยู่ในโปรเจกต์จากอดีต ทำให้คนอ่านทีหลัง (หรือ AI) เข้าใจผิดได้

2. copy id มาผิดช่อง แล้วฟอร์มพังแบบไม่มีอะไรฟ้อง

อันนี้เจ็บสุด ผมเอา id ที่ copy มาใส่เป็น endpoint ของจดหมายข่าวตรง ๆ แทนที่จะเป็น URL เต็ม โค้ดเช็คแค่ว่ามีค่าไหม ไม่ได้เช็คว่าเป็น URL ไหม มันเลยผ่านเป็นสถานะ “เชื่อมต่อแล้ว”

ผลคือฟอร์มถูก build ออกมาเป็น action="2517b869-..." ซึ่ง browser ตีความเป็น path บนเว็บเราเอง คนกดสมัครจะไปโผล่หน้า 404 และอีเมลหายไปเฉย ๆ

น่ากลัวตรงที่หน้าเว็บดูปกติสมบูรณ์ ไม่มีอะไรผิดสังเกตเลย

3. ตั้งค่าในเครื่องแล้ว แต่เว็บจริงยังไม่ขึ้น

ไฟล์ .env อยู่ใน .gitignore แปลว่า Vercel มองไม่เห็น ต้องไปใส่ในหน้าตั้งค่าของ Vercel อีกรอบ แล้วต้อง deploy ใหม่ด้วย เพราะค่าพวกนี้ถูกฝังเข้าไฟล์ตั้งแต่ตอน build

รอบแรกที่ไปเช็คเว็บจริงเลยพบว่าไม่มีสคริปต์ analytics โผล่มาสักตัว ทั้งที่ในเครื่องขึ้นแล้ว

4. CSP บล็อก analytics อยู่หลายวันโดยไม่รู้ตัว — และผมเขียนบทความยืนยันว่ามันไม่พังด้วย

อันนี้หนักกว่าทุกข้อ เจอทีหลังตอนกลับมาเช็คเว็บจริงด้วยเบราว์เซอร์ ไม่ใช่ curl

สคริปต์ Umami โหลดจาก cloud.umami.is แต่ ส่ง event ไปที่ gateway.umami.is คนละโดเมนกัน connect-src ใน CSP เปิดไว้แค่โดเมนแรก เบราว์เซอร์เลยบล็อกทุก request ที่ส่งออก

Connecting to 'https://gateway.umami.is/api/send' violates the following
Content Security Policy directive: "connect-src 'self' ... https://cloud.umami.is"

แปลว่าตลอดเวลาที่ผ่านมา dashboard ไม่ได้รับข้อมูลเลยสักครั้ง ทั้งที่สคริปต์โหลดสำเร็จ curl ก็เห็น data-website-id ครบ และหน้าเว็บก็ดูปกติสมบูรณ์

ที่แสบที่สุดคือกับดักข้อนี้ ผมเขียนเตือนไว้ในบทความเองแล้ว ว่า “บางเจ้าโหลดสคริปต์จากโดเมนหนึ่ง แต่ส่งข้อมูลกลับไปอีกโดเมนหนึ่ง” — แล้วย่อหน้าถัดมาผมดันเขียนต่อว่า “กรณีของ Umami โชคดีที่ทั้งสอง อย่างอยู่โดเมนเดียวกัน เลยไม่ต้องแก้อะไรเพิ่ม” ซึ่งผมไม่เคยไปตรวจเลยว่าจริงไหม เดาแล้วเขียนลงไปเลย

เขียนคำเตือนได้ ไม่ได้แปลว่าตัวเองรอด

What I learned

เช็คของจริงเสมอ อย่าเชื่อว่า “ตั้งค่าแล้วน่าจะขึ้น”

ทั้งข้อ 2 และ 3 เจอเพราะไปยิงดูหน้าเว็บจริงแล้วอ่าน HTML ที่ออกมา ไม่ใช่เพราะเทสต์ฟ้อง เป็นธีมเดียวกับรอบที่แล้วเป๊ะ — บั๊กที่เหลืออยู่คือบั๊กที่เครื่องตัวเองจำลองไม่ได้

เครื่องมือตรวจแต่ละอย่างมีเพดานของมัน

ข้อ 4 สอนบทเรียนที่ต่อยอดจากข้อบน: curl ก็ยังไม่ใช่ “ของจริง” พอ เพราะมันแค่ดาวน์โหลด HTML มาเฉย ๆ ไม่ได้บังคับใช้ CSP เหมือนเบราว์เซอร์ ผมเลยได้คำตอบว่า “ผ่าน” ทั้งที่ของจริงถูกบล็อกอยู่

ลำดับที่เชื่อถือได้ไล่จากน้อยไปมากคือ: เทสต์ในเครื่อง → curl เว็บจริง → เปิดเบราว์เซอร์จริงแล้วดู console → ดูว่าข้อมูลไปโผล่ที่ปลายทางจริงไหม ทุกขั้นจับบั๊กคนละแบบ และขั้นที่ผมข้ามคือขั้นที่บั๊กซ่อนอยู่

สถานะ “ยังไม่ได้เชื่อมต่อ” มีค่ามากกว่าที่คิด

ตอนแรกมองว่าข้อความบอกว่ายังไม่ได้ต่อระบบเป็นแค่ placeholder แต่พอเจอบั๊กข้อ 2 ถึงเข้าใจว่า มันคือกันชนที่ป้องกันไม่ให้ข้อมูลหายเงียบ ปัญหาคือเงื่อนไขที่ใช้ตัดสินยังหลวมไป — เช็คแค่ว่ามีค่า ไม่ได้เช็คว่าค่านั้นใช้ได้จริงไหม

ของที่ไม่ได้ใช้แล้วควรเก็บกวาด

ไฟล์ _headers ที่ Vercel ไม่อ่านยังอยู่ในโปรเจกต์ และมันคือต้นเหตุที่ทำให้เดาโฮสต์ผิดในข้อ 1 ของที่ค้างไว้ “เผื่อวันหนึ่งได้ใช้” มีต้นทุนที่มองไม่เห็นคือทำให้คนเข้าใจผิด

แต่ตอนไปแก้ CSP ในข้อ 4 เรื่องกลับไม่ง่ายอย่างที่คิด — พอแก้แค่ vercel.json เทสต์แดงทันที เพราะมี test บังคับให้ CSP ในสองไฟล์ตรงกันเป๊ะ เผื่อวันหนึ่งย้ายโฮสต์ กลายเป็นว่าไฟล์ที่ผมมองว่า เป็นขยะ มีคนผูกการรับประกันไว้กับมันอยู่ ถ้าจะลบต้องลบ test ตัวนั้นด้วย และยอมรับว่าเสียการ รับประกันนั้นไป

บทเรียนที่ได้เพิ่มคือ ก่อนลบของที่ดู “ไม่ได้ใช้” ต้องไล่ดูก่อนว่ามีอะไรผูกอยู่กับมันบ้าง

สำหรับผมตั้งแต่ตอนทำมา คิดว่าถ้ามี คนมาเยอะๆมากพอ และได้รายได้ ที่ cover ค่าใช้จ่าย คิดจะยอมจ่าย

What I did about it

เจอบั๊กข้อ 4 แล้วรู้สึกว่าแค่แก้ CSP ให้ผ่านไม่พอ เพราะรอบหน้าก็พลาดแบบเดิมได้อีก เลยไล่ปิดช่องทั้งหมดในวันเดียวกัน

ทำให้บั๊กแบบข้อ 4 พัง build ทันที

ทำ registry ที่เก็บว่า provider แต่ละเจ้าโหลดสคริปต์จากโดเมนไหนและส่งข้อมูลไปโดเมนไหน โดยไปอ่านจากตัวสคริปต์ของแต่ละเจ้าเอง ไม่ได้เชื่อเอกสาร แล้วให้ตอน build เทียบ registry กับ CSP จริง ถ้าโดเมนไหนขาด build จะพังพร้อมบอกว่าต้องเติมอะไร

ลองจำลองบั๊กเดิมดูแล้ว ได้ข้อความนี้ออกมา

Analytics provider "umami" is configured, but the Content Security Policy
in vercel.json does not allow:
  - connect-src https://gateway.umami.is

The script would load and every event would be blocked, so the dashboard would
stay empty while the site looked completely healthy.

เจอว่าบั๊กเดียวกันซ่อนอยู่กับ provider อีกเจ้า

ตอนไล่อ่านสคริปต์เพื่อทำ registry พบว่า Cloudflare Web Analytics ก็เป็นแบบเดียวกัน — โหลดจาก static.cloudflareinsights.com แต่ส่งข้อมูลไปที่ cloudflareinsights.com ซึ่งเป็น apex CSP เดิมเปิดไว้แค่ตัวแรก แปลว่าถ้าวันไหนสลับไปใช้ Cloudflare ก็จะเงียบแบบเดิมเป๊ะ ส่วน Plausible เป็นเจ้าเดียวในสามเจ้าที่ใช้โดเมนเดียวกันจริง

ปิดช่องของบั๊กข้อ 2 ด้วย

เพิ่มการตรวจว่า endpoint ต้องเป็น URL แบบ https:// เต็ม ๆ ถ้าเป็น id เปล่าหรือ path จะถือว่ายังไม่ได้เชื่อมต่อ แล้วแสดงสถานะ disabled แทนที่จะทำเป็นว่าใช้ได้ ทั้งฟอร์มจดหมายข่าว และฟอร์มติดต่อ

ตัดสินใจเรื่อง _headers แล้ว: เก็บไว้

เขียนคอมเมนต์กำกับไว้ในไฟล์เลยว่าทำไมถึงยังอยู่ และมี test อะไรผูกกับมัน ปัญหาจริงของไฟล์นี้ไม่ใช่ว่ามันไม่มีประโยชน์ แต่คือไม่มีใครบอกว่ามันมีไว้ทำไม

เก็บกวาด CSP

ตัดโดเมนของ provider ที่ไม่ได้ใช้ออก เหลือเฉพาะของ Umami ถ้าวันหนึ่งสลับเจ้า build จะพัง ทันทีพร้อมบอกว่าต้องเติมโดเมนไหน ซึ่งดีกว่าเปิดทิ้งไว้เผื่อแล้วเงียบ

Next step

  • เอาบทเรียนเรื่อง “เขียนคำเตือนแล้วไม่ได้ตรวจเอง” ไปใช้กับบทความเก่า ๆ ที่เคยเขียนไว้
แชร์:FacebookXLinkedIn

รับบทเรียน AI ที่ใช้ได้จริง ทางอีเมล

หนึ่งบทเรียน AI ที่ใช้ได้จริง การแกะโปรเจกต์ หรือข้อคิดจากการสร้างของ สัปดาห์ละครั้ง

ไม่มีสแปม ยกเลิกได้ทุกเมื่อ