บล็อกนี้ทั้งเว็บเป็นไฟล์ static เสิร์ฟผ่าน CDN ไม่มีเซิร์ฟเวอร์เป็นของตัวเองสักตัว แต่มีอยู่วันหนึ่งที่อยากได้ของแบบที่เว็บ static ไม่ควรทำได้ นั่นคือ แจกเนื้อหาเชิงลึกแลกกับอีเมลคนอ่าน โค้ด worker เต็ม ๆ ของบทความสายเทคนิค ให้คนที่สนใจจริงกรอกอีเมลแล้วเปิดอ่านต่อได้
พอไปหาวิธี ทุกทางล้วนบอกให้ไปมีเครื่องมือสักตัวก่อน HubSpot, Mailchimp, ปลั๊กอิน WordPress หรืออย่างน้อยก็ต้องมี backend ไว้เช็คว่าใครปลดล็อกแล้ว สำหรับเว็บ static ที่ตั้งใจให้ไม่มีเซิร์ฟเวอร์ มันดูเหมือนทางตัน แต่สุดท้ายมันทำได้ ด้วยชิ้นส่วนที่มีอยู่แล้วบนเว็บ และมีกับดักหนึ่งที่เกือบทำให้ทั้ง gate รั่วโดยไม่รู้ตัว
ช่วงที่ 1ทำไม gated content ปกติต้องมี backend
หัวใจของ gated content คือต้องมีใครสักคนจำได้ว่าใครปลดล็อกแล้ว พอมีสถานะที่ต้องจำ ก็ต้องมีที่จำ นั่นคือเหตุผลที่เครื่องมือทั้งหลายผูกมากับเซิร์ฟเวอร์หรือฐานข้อมูล HubSpot/Mailchimp เก็บสถานะให้ในระบบเขา ปลั๊กอิน WordPress ก็อาศัย backend ของ WordPress
เว็บ static ไม่มีสถานะ ไม่มีที่จำ ทุก request เสิร์ฟไฟล์เดิมให้ทุกคนเท่ากัน คำถามจริงจึงไม่ใช่ "จะทำ gate ยังไง" แต่คือ จะเอาที่จำสถานะมาจากไหน โดยไม่ต้องยกเซิร์ฟเวอร์ขึ้นมาทั้งตัว
คำตอบอยู่ที่ชั้นที่คนมักลืมว่ามีอยู่ ถ้าเว็บอยู่หลัง Cloudflare ทุก request วิ่งผ่าน edge ของ Cloudflare ก่อนถึงไฟล์จริงเสมอ ตรงชั้นนั้นแปะโค้ดเล็ก ๆ (Cloudflare Worker) ให้ตัดสินใจก่อนได้ ว่า request นี้ควรเห็นของจริงไหม นั่นแหละที่จำสถานะที่เราต้องการ โดยไม่ต้องมี backend เป็นของตัวเอง
ช่วงที่ 2ทำบน static ยังไง: teaser สาธารณะ + payload หลังด่าน
โครงมีสามชิ้น แบ่งงานกันชัด
- teaser เป็น HTML สาธารณะ ส่วนหน้าของบทความเปิดให้ทุกคนและครอว์เลอร์อ่านได้เต็มที่ นี่คือพื้นที่ SEO/GEO ห้ามกัน เพราะถ้ากันหมด Google ก็ไม่เห็นอะไรเลย
- เนื้อหาที่กันไว้ เก็บแยก ไม่อยู่ในหน้า teaser ข้อนี้สำคัญที่สุด ของจริงต้องไม่ติดมากับ HTML ของหน้า teaser เลย ถ้าเอาไปซ่อนด้วย CSS หรือ blur มันยังอยู่ใน page source ใครเปิด devtools ก็อ่านได้หมด เท่ากับไม่ได้กัน
- Worker เป็นด่าน เนื้อหาที่กันไว้มีทางเข้าทางเดียวคือผ่าน Worker ซึ่งเช็คก่อนว่า request นี้มี unlock cookie ที่ถูกต้องไหม มี = เสิร์ฟของจริง ไม่มี = เด้งกลับไปหน้าฟอร์มกรอกอีเมล
flow ฝั่งคนอ่านเป็นแบบนี้ อ่าน teaser ฟรีจนถึงรอยตัด เจอกล่องให้กรอกอีเมลเพื่ออ่านต่อ กรอกแล้ว Worker บันทึกอีเมลลงที่เก็บ key-value ของ Cloudflare (KV) แล้วออก cookie ลงลายเซ็นกลับมา จากนั้นขอเนื้อหาที่กันไว้ได้ Worker เห็น cookie ถูกต้องก็เสิร์ฟของจริงให้
จุดที่ทำให้ปลอดภัยคือ cookie ลงลายเซ็นฝั่งเซิร์ฟเวอร์ ไม่ใช่แค่ค่าธรรมดาที่ปลอมได้ ใครแก้ค่าเองลายเซ็นก็ไม่ผ่าน และตัวเนื้อหาไม่เคยโผล่ในหน้าสาธารณะ มีทางออกทางเดียวคือผ่านด่าน สองอย่างนี้รวมกันคือทั้งหมดของ soft paywall แบบที่ Substack ใช้ ต่างกันแค่เราทำเองบน static
ส่วนอีเมลที่เก็บ อย่าเก็บแค่ตัวอีเมล เก็บว่ามาจากโพสต์ไหน ยินยอมรับข่าวไหม และเวลาไหนตั้งแต่วันแรก วันหลังอยาก export ไปเครื่องมือ newsletter ก็ได้ลิสต์ที่สะอาด และดูได้ว่าโพสต์ไหนแลกอีเมลได้ดีกว่ากัน
ช่วงที่ 3กับดัก origin: gate ที่เกือบรั่วทั้งดุ้น
ตอนรีวิวโค้ดด่านก่อนปล่อยจริง (ให้โมเดลตัวที่สองไล่หาช่องโหว่ ไม่ใช่ตัวที่เขียนเอง) เจอบั๊กระดับ gate เปิดได้โดยไม่ต้องปลดล็อก ทั้งที่ Worker เขียนถูกทุกบรรทัด
ปัญหาอยู่ที่จุดที่มองข้าม Worker เป็นแค่ด่านหน้า แต่ไฟล์จริงอยู่ที่ origin (เซิร์ฟเวอร์ต้นทาง) ถ้าเนื้อหาที่กันไว้ถูกเก็บเป็นไฟล์ static สาธารณะบน origin คนที่รู้ที่อยู่ origin ก็เรียกไฟล์นั้นตรง ๆ ข้าม Worker ไปเลย ด่านหน้าล็อกแน่นหนาแต่ประตูหลังเปิดอ้าอยู่ นี่คือกับดักที่เราตั้งชื่อให้ว่า ช่องโหว่ที่ origin
ช่องที่สองคล้ายกัน route ของด่านมักเช็คด้วยชื่อ path ตรง ๆ แต่ถ้าคนเข้ารหัสตัวอักษรใน path (เช่นเปลี่ยน method เป็นรูปเข้ารหัสที่อ่านออกตอน decode) การเช็คแบบตรงตัวจะไม่แมตช์ แล้ว request หลุดไปที่ proxy ทั่วไปซึ่ง decode ให้ทีหลัง กลายเป็นเข้าถึงของจริงได้อีกทาง
บทเรียนรวบเป็นประโยคเดียว ด่านที่ดีต้องกันทั้งทางเข้าหลักและประตูหลัง สองอย่างที่ต้องปิดคือ
- กัน origin ไม่ให้เรียกได้ตรง ๆ ไม่เก็บเนื้อหา gated เป็นไฟล์สาธารณะ หรือใส่ token ที่ให้เฉพาะ Worker ผ่านได้
- decode path ให้เป็นมาตรฐานก่อนเช็ค route เสมอ อย่าเทียบกับ path ดิบที่ยังเข้ารหัสอยู่
ที่จับได้ก่อนปล่อยเพราะให้โมเดลคนละตัวรีวิว โมเดลที่เขียนโค้ดเองมักมั่นใจว่ากันครบแล้ว แต่คนที่ไม่ได้เขียนจะไล่หาทางอ้อมเจอง่ายกว่า เรื่องนี้เราเขียนแยกไว้แล้วในโพสต์ก่อน
ช่วงที่ 4เอาไปใช้กับเว็บของคุณ
ถ้าเว็บอยู่บน Cloudflare อยู่แล้ว (หรือย้าย DNS มาได้) โครงทั้งหมดคือ
- เขียนเนื้อหาที่จะกันก่อน อย่าสร้างด่านเปล่า ๆ ที่ยังไม่มีของอยู่หลัง ทำ payload จริงให้เสร็จก่อน แล้วค่อยสร้างด่านครอบ
- ตัด teaser กับ payload ให้ชัด ส่วนหน้าพิสูจน์คุณค่า (สาธารณะ ครอว์เลอร์อ่านได้) ส่วนหลังคือของจริงที่แลกอีเมล เก็บคนละที่
- Worker เช็ค unlock cookie มี cookie ถูกต้องเสิร์ฟ payload ไม่มีเด้งไปฟอร์ม บันทึกอีเมล+source+consent+เวลาลง KV แล้วออก cookie ลงลายเซ็น
- ปิดประตูหลัง กัน origin ไม่ให้เรียกตรง + decode path ก่อนเช็ค แล้วลองเจาะเองด้วยการเรียก origin ตรง ๆ กับ path เข้ารหัส ให้เห็นว่าปิดจริง
- อย่าเด้งฟอร์มทันทีที่โหลด ปล่อยให้อ่าน teaser ก่อน แล้วค่อยกันตรงรอยตัดหรือตอนเลื่อนถึง เด้งทันทีทำให้คนปิดหนีและ SEO เสีย
หลักการทั้งหมดอยู่ในบทความนี้ครบแล้ว ทำตามได้เลย ส่วนโค้ด Worker ฉบับเต็ม ทั้งการออก cookie ลงลายเซ็น การกัน origin และ schema ที่เก็บใน KV เราเก็บไว้เป็น method page สำหรับสมาชิกบล็อก ไม่ใช่เพราะกั๊ก แต่เพราะด่านที่ก็อปโค้ดไปวางโดยไม่เข้าใจว่าปิดประตูหลังยังไง คือด่านที่รั่ว หลักการต้องมาก่อนโค้ด
ตัวด่านนี้ไม่ใช่ทฤษฎี มันรันจริงอยู่บนบล็อกนี้ กันเนื้อหา method ของหลายโพสต์ และมีคนแลกอีเมลเข้ามาแล้ว การออกแบบเดียวกันนี้ใช้ได้กับ lead magnet, คอร์สพรีวิว, หรือรายงานที่อยากได้อีเมลก่อนโหลด โดยไม่ต้องจ่ายค่าเครื่องมือการตลาดสักบาท
- ด่านนี้รันจริงบน productize.life กันเนื้อหา method ของหลายโพสต์ (Gate A) และมีอีเมลเข้าจริง เนื้อหาส่วนนี้เขียนจากงานที่ทำเอง
- ช่องโหว่ที่ origin + path alias: จับได้จากการรีวิวโค้ดด่านด้วยโมเดลตัวที่สองก่อนปล่อยจริง เล่าวิธีไว้ที่ ให้ AI ตัวที่สองรีวิวโค้ดที่ AI เขียน
- แนวคิด soft paywall (teaser สาธารณะ + payload กันไว้) อ้างอิงรูปแบบที่ Substack/Medium ใช้ ปรับมาทำเองบน static