← กลับไปหน้าบทความ
ขั้นสูง (MarTech)

ใช้ Vercel Functions กับ Supabase Edge Functions ตรงไหนควรเป็น Backend Logic ของแอปจริง

ทีมบรรณาธิการ linli12 ส.ค. 04:53อัปเดต 12 ส.ค. 04:53อ่าน 5 นาที
ใช้ Vercel Functions กับ Supabase Edge Functions ตรงไหนควรเป็น Backend Logic ของแอปจริง
บทความนี้จัดทำเพื่อความรู้ ชื่อธุรกิจ ตัวเลข หรือบทสนทนาที่ไม่มีแหล่งอ้างอิงกำกับเป็นกรณีตัวอย่างเพื่ออธิบายแนวคิด ดูรายละเอียดที่ นโยบายกองบรรณาธิการ

สรุปสั้น ๆ

แอปที่ใช้ Vercel deploy Next.js ร่วมกับ Supabase มักมี backend logic กระจายอยู่สองจุด คือ Route Handler/Server Action บน Vercel กับ Edge Functions ของ Supabase เอง หลักแบ่งงานที่ใช้ได้จริงคือ logic ที่ผูกกับ routing และ session ของหน้าเว็บควรอยู่บน Vercel ส่วน logic ที่ต้องรับ Webhook ภายนอก ต้องอยู่ใกล้ Postgres มาก หรือต้องข้าม Row Level Security ควรอยู่บน Supabase Edge Functions เพื่อแยก deploy cycle ออกจากแอปหลัก

เมื่อแอปหนึ่งใช้ทั้ง Vercel และ Supabase พร้อมกัน คำถามที่เกิดขึ้นเกือบทุกทีมคือ logic ฝั่งเซิร์ฟเวอร์แต่ละชิ้นควรอยู่ที่ไหน ถ้าใส่ทุกอย่างไว้ใน Route Handler ของ Next.js บน Vercel ก็ทำได้ แต่บางงานจะเริ่มรู้สึกอึดอัดเมื่อโปรเจกต์โตขึ้น เช่น Webhook handler ที่ต้อง deploy บ่อยแยกจากแอปหลัก

ในทางกลับกัน ถ้าย้ายทุกอย่างไปไว้ที่ Supabase Edge Functions ก็จะเสีย benefit ของการเขียนโค้ดในที่เดียวกับ frontend และต้องดูแล deployment สองจุดโดยไม่จำเป็นสำหรับ logic ที่จริง ๆ แล้วผูกกับหน้าเว็บอยู่แล้ว

บทความนี้จะวางหลักแบ่งงานที่ใช้ได้จริง พร้อมตัวอย่างสถานการณ์ที่ควรเลือกทางไหน เพื่อให้ทีมไม่ต้องเดาเองทุกครั้งที่มี feature ใหม่เข้ามา

ทำความเข้าใจว่าแอปนี้มี Backend Logic กี่ชั้น

เมื่อใช้ Vercel คู่กับ Supabase แอปหนึ่งมักมี logic ฝั่งเซิร์ฟเวอร์กระจายอยู่อย่างน้อยสามชั้น คือ Server Component ที่ query ข้อมูลตอน render หน้า, Route Handler หรือ Server Action ที่รับ request เฉพาะจาก frontend ของแอปเอง และ Supabase Edge Functions ที่แยก deploy ต่างหากจาก Supabase project โดยตรง

สามชั้นนี้ไม่ได้แข่งกันว่าใครดีกว่า แต่ออกแบบมาให้ตอบโจทย์คนละแบบ Server Component เหมาะกับการดึงข้อมูลมา render หน้าโดยตรง Route Handler/Server Action เหมาะกับ action ที่ผู้ใช้กดจากหน้าเว็บของแอปเอง ส่วน Edge Functions เหมาะกับ logic ที่ต้องเปิดเป็น endpoint สาธารณะให้บริการภายนอกเรียกเข้ามา หรือทำงานที่ไม่ผูกกับ lifecycle ของหน้าเว็บใด ๆ

การเข้าใจบทบาทที่ต่างกันนี้ช่วยให้ทีมตัดสินใจได้เร็วขึ้นตอนเจอ feature ใหม่ แทนที่จะใส่ทุกอย่างไว้ที่เดียวเพราะความเคยชิน แล้วมาพบว่า maintain ยากภายหลังเมื่อโปรเจกต์โตขึ้น

เกณฑ์ตัดสินใจว่า Logic ชิ้นนี้ควรอยู่ที่ไหน

มีคำถามชุดหนึ่งที่ช่วยตัดสินใจได้เร็วขึ้นเมื่อต้องเขียน logic ฝั่งเซิร์ฟเวอร์ชิ้นใหม่

  • logic นี้ถูกเรียกจากหน้าเว็บของแอปเองเท่านั้นไหม — ถ้าใช่ ควรอยู่ใน Route Handler หรือ Server Action บน Vercel เพราะผูกกับ deploy cycle เดียวกับ frontend อยู่แล้ว ไม่ต้องเพิ่มจุดดูแลใหม่
  • logic นี้ต้องรับ request จากบริการภายนอกที่ไม่ใช่ frontend ของแอปเองไหม — เช่น payment gateway หรือบริการส่งข้อความ ถ้าใช่ ควรพิจารณา Edge Functions เพราะแยก deploy ได้โดยไม่กระทบแอปหลัก
  • logic นี้ต้องข้าม Row Level Security ด้วย service role key ไหม — ถ้าใช่ ควรอยู่ในจุดที่ควบคุมสิทธิ์เข้าถึงได้รัดกุมที่สุด ซึ่งอาจเป็นได้ทั้ง Route Handler หรือ Edge Functions ขึ้นกับว่าใครเป็นผู้เรียก
  • logic นี้ต้องแก้บ่อยแยกจาก release รอบใหญ่ของแอปไหม — เช่น Webhook handler ที่ต้องปรับ signature verification บ่อยตามที่ provider เปลี่ยน การแยกไว้ที่ Edge Functions ทำให้แก้ได้เร็วโดยไม่ต้องรอ deploy แอปทั้งก้อน

Logic ที่ผูกกับ Session ผู้ใช้ ควรอยู่ฝั่ง Vercel เกือบทุกกรณี

งานที่ต้องอ่าน session ของผู้ใช้ที่ล็อกอินอยู่ เช่นตรวจสิทธิ์ก่อนแก้ไขข้อมูลส่วนตัว หรือ render หน้าที่ต่างกันตามสถานะผู้ใช้ ควรอยู่ใน Server Component หรือ Server Action ของ Next.js บน Vercel เพราะ session ถูกจัดการผ่าน cookie ที่ผูกกับ domain ของแอปโดยตรง การพยายามส่ง session นั้นไปให้ Edge Functions ตรวจสอบต่อจะเพิ่มความซับซ้อนโดยไม่จำเป็น

ถ้ามีความจำเป็นต้องให้ Edge Functions รู้ตัวตนของผู้ใช้ เช่นต้องเรียก Edge Functions จากฝั่ง client โดยตรง ควรส่ง JWT ของผู้ใช้แนบไปด้วยแล้วให้ Edge Functions ตรวจสอบ token นั้นเอง ไม่ใช่เชื่อ session จาก cookie ของ Next.js โดยตรง เพราะ Edge Functions รันแยก domain และไม่มีสิทธิ์เข้าถึง cookie ของแอปหลักอยู่แล้ว

ตัวอย่างสมมติ: แบ่ง Logic ของฟีเจอร์ชำระเงินให้ถูกจุด

ลองดูตัวอย่างสมมติของฟีเจอร์ชำระเงินในแอป e-commerce ที่ใช้ Vercel คู่กับ Supabase (โครงสร้างนี้เป็นตัวอย่างสมมติเพื่อประกอบการอธิบาย ไม่ใช่การออกแบบระบบสำเร็จรูป)

ขั้นตอนที่ผู้ใช้กดปุ่ม 'ชำระเงิน' บนหน้าเว็บ เรียก Server Action บน Vercel ที่ตรวจ session ของผู้ใช้ก่อน แล้วสร้าง order ในสถานะ 'pending' พร้อมเรียก API ของผู้ให้บริการชำระเงินเพื่อสร้างลิงก์ชำระเงิน ขั้นตอนนี้อยู่บน Vercel เพราะผูกกับ session ของผู้ใช้ที่กำลังใช้งานอยู่

เมื่อผู้ใช้ชำระเงินเสร็จ ผู้ให้บริการชำระเงินจะยิง Webhook กลับมาแจ้งผลแบบ asynchronous ซึ่งไม่ได้มาจาก frontend ของแอปโดยตรงและไม่มี session ของผู้ใช้แนบมาด้วย endpoint นี้ควรอยู่ที่ Supabase Edge Functions แยกต่างหาก ทำหน้าที่ตรวจ signature ของ Webhook แล้วอัปเดตสถานะ order ในตาราง Postgres ผ่าน service role key

การแบ่งแบบนี้ทำให้ทีมแก้ signature verification logic ของ Webhook ได้โดยไม่ต้อง deploy แอปทั้งก้อนใหม่ ขณะที่ logic ฝั่งผู้ใช้ยังอยู่ใกล้กับ session และ routing ของหน้าเว็บตามปกติ ไม่ต้องส่ง token ข้ามระบบโดยไม่จำเป็น

ตารางสรุปว่าใครควรทำหน้าที่อะไร

เพื่อให้ทีมอ้างอิงได้เร็วตอนตัดสินใจ นี่คือตารางสรุปการแบ่งงานระหว่าง Vercel กับ Supabase Edge Functions ตามลักษณะงาน

ลักษณะงานควรอยู่ที่ไหนเหตุผลหลัก
ตรวจสิทธิ์และ render หน้าตาม session ผู้ใช้Vercel (Server Component/Action)ผูกกับ cookie และ routing ของแอปโดยตรง
รับ Webhook จากบริการภายนอกSupabase Edge Functionsแยก deploy ได้ ไม่ต้อง redeploy แอปหลักทุกครั้งที่แก้
งานที่ต้องข้าม RLS ด้วย service role key และเรียกจากฝั่งผู้ใช้โดยตรงEdge Functions พร้อมตรวจ JWT เองควบคุมสิทธิ์ได้ชัดเจนโดยไม่พึ่ง cookie ของแอปหลัก
งานประมวลผลหนักที่ใช้เวลานานแยกไปเป็น background job/queue ต่างหากทั้งสอง runtime มีเพดานเวลาทำงานต่อ request จำกัด

ระวังไม่ให้ Logic สำคัญซ้ำกันสองที่โดยไม่ตั้งใจ

ปัญหาหนึ่งที่เกิดขึ้นเมื่อแบ่ง logic ไว้สองที่คือกฎทางธุรกิจบางอย่างถูกเขียนซ้ำในทั้ง Route Handler และ Edge Functions โดยไม่ตั้งใจ เช่นการคำนวณราคาสุทธิหลังส่วนลดที่ต้องตรงกันทั้งตอนแสดงผลบนหน้าเว็บและตอนอัปเดตสถานะ order ผ่าน Webhook ถ้าสองจุดนี้ใช้ logic คนละเวอร์ชันเพราะแก้ทีละที่ไม่พร้อมกัน อาจเกิดความไม่ตรงกันระหว่างสิ่งที่ผู้ใช้เห็นกับสิ่งที่บันทึกจริงในฐานข้อมูล

แนวทางที่ช่วยลดปัญหานี้คือแยก business logic ที่ต้องใช้ร่วมกันออกมาเป็น pure function ที่ไม่ผูกกับ runtime ใดโดยเฉพาะ แล้ว import ไปใช้ในทั้งสองฝั่งเท่าที่ runtime รองรับ ถ้า logic นั้นซับซ้อนและต้องพึ่ง Node API ที่ Edge Functions ใช้ไม่ได้ ควรพิจารณาให้ Edge Functions เรียกกลับมาตรวจสอบผ่าน API ของ Vercel แทนการเขียน logic ซ้ำเองอีกชุด

แบ่งความรับผิดชอบในทีมให้ตรงกับการแบ่ง Logic

เมื่อทีมโตขึ้นจนมีคนดูแล frontend กับ backend integration แยกกัน การแบ่ง logic ระหว่าง Vercel กับ Supabase Edge Functions อย่างชัดเจนยังช่วยเรื่องความรับผิดชอบในทีมด้วย ไม่ใช่แค่เรื่องเทคนิคอย่างเดียว เพราะสองจุดนี้มัก deploy คนละ pipeline และอาจมีคนละคนที่ต้อง review code ก่อน merge

ทีมที่แบ่งชัดมักตั้งกฎง่าย ๆ ไว้ล่วงหน้า เช่น 'logic ที่แก้ไฟล์ในโฟลเดอร์ Edge Functions ต้องผ่าน reviewer ที่เข้าใจเรื่อง Webhook security เสมอ' หรือ 'logic ที่แตะ Server Action ต้องเทสต์ auth flow ก่อน merge ทุกครั้ง' กฎแบบนี้ช่วยลดความเสี่ยงที่คนไม่คุ้นเคยกับ context ของแต่ละฝั่งจะแก้โค้ดโดยไม่รู้ผลกระทบ

ในทีมเล็กที่มีนักพัฒนาไม่กี่คนอาจไม่ต้องตั้งกฎเป็นทางการขนาดนั้น แต่ควรมีเอกสารสั้น ๆ อธิบายว่าทำไม logic แต่ละชิ้นถึงอยู่ตรงจุดที่มันอยู่ เพื่อให้คนที่เข้ามาทำงานต่อในอนาคตเข้าใจเหตุผลโดยไม่ต้องเดาเอง หรือย้าย logic กลับไปกลับมาโดยไม่มีเหตุผลรองรับ

ทดสอบทั้งสองฝั่งแยกกัน แต่ต้องมี Integration Test คลุมทั้งระบบด้วย

เมื่อ logic กระจายอยู่สองที่ การทดสอบแยกส่วน (unit test) ของแต่ละฝั่งยังไม่พอที่จะมั่นใจว่าทั้งระบบทำงานถูกต้อง เพราะจุดที่มักพังจริงคือรอยต่อระหว่างสองฝั่ง เช่น Route Handler สร้าง order ด้วย schema แบบหนึ่ง แต่ Edge Functions ที่รับ Webhook กลับมาคาดหวัง schema อีกแบบเพราะถูกแก้แยกกันคนละช่วงเวลา

ทีมที่มี logic แบบนี้ควรมี integration test อย่างน้อยหนึ่งชุดที่จำลอง flow เต็มรูปแบบ ตั้งแต่ผู้ใช้กด action บนหน้าเว็บ ผ่าน Route Handler สร้างข้อมูลเริ่มต้น แล้วจำลอง Webhook ยิงกลับมาที่ Edge Functions เพื่อยืนยันว่าสถานะสุดท้ายในฐานข้อมูลถูกต้องตรงตามที่ควรจะเป็น ไม่ใช่ทดสอบแค่ว่าแต่ละฟังก์ชันทำงานได้เดี่ยว ๆ

การรัน integration test แบบนี้อาจไม่ต้องทำทุกครั้งที่ commit แต่ควรรันอย่างน้อยก่อน deploy เข้า production ทุกรอบที่มีการแก้ schema หรือ logic ที่เกี่ยวข้องกับรอยต่อระหว่างสองฝั่งนี้โดยเฉพาะ เพื่อจับปัญหาความไม่ตรงกันก่อนที่ผู้ใช้จริงจะเจอ

ทำแบบนี้แล้วพัง เพราะอะไร

ข้อผิดพลาดที่พบบ่อยเมื่อแบ่ง backend logic ระหว่าง Vercel กับ Supabase Edge Functions โดยไม่วางหลักเกณฑ์ให้ชัดตั้งแต่ต้น

  • ใส่ logic ที่ผูกกับ session ผู้ใช้ไว้ใน Edge Functions — แล้วต้องส่ง token ข้ามระบบโดยไม่จำเป็น เพิ่มความซับซ้อนของการตรวจสอบสิทธิ์โดยไม่ได้ประโยชน์อะไร
  • ใส่ Webhook handler ไว้ใน Route Handler ของ Vercel — ทำให้ต้อง deploy แอปทั้งก้อนใหม่ทุกครั้งที่ต้องแก้ signature verification logic เล็ก ๆ น้อย ๆ
  • เขียนกฎทางธุรกิจซ้ำกันสองที่โดยไม่มีจุดอ้างอิงกลาง — ทำให้ผลลัพธ์ที่ผู้ใช้เห็นกับข้อมูลที่บันทึกจริงไม่ตรงกันเมื่อแก้ไขทีละจุดไม่พร้อมกัน
  • ไม่ตรวจ JWT เองใน Edge Functions ที่เปิดให้เรียกจาก client โดยตรง — ทำให้ใครก็เรียก endpoint นั้นได้โดยไม่มีการยืนยันตัวตนที่แท้จริง

สรุป

การแบ่ง backend logic ระหว่าง Vercel กับ Supabase Edge Functions ไม่มีสูตรตายตัวที่ใช้ได้กับทุกแอป แต่มีหลักคิดที่ใช้ตัดสินใจได้เร็วคือดูว่า logic นั้นผูกกับ session ของผู้ใช้หรือไม่ ต้องรับ request จากภายนอกหรือไม่ และต้องแก้บ่อยแยกจาก release รอบใหญ่หรือไม่

สิ่งสำคัญกว่าการเลือกที่อยู่ของ logic คือการไม่ปล่อยให้กฎทางธุรกิจสำคัญกระจัดกระจายไปหลายที่โดยไม่มีจุดอ้างอิงกลาง เพราะนั่นคือสาเหตุหลักที่ทำให้ระบบดูแลยากขึ้นเมื่อโปรเจกต์โตขึ้นเรื่อย ๆ

  • Logic ที่ผูกกับ session ผู้ใช้ควรอยู่บน Vercel เกือบทุกกรณี
  • Webhook และงานที่ต้องแยก deploy ควรอยู่ที่ Supabase Edge Functions
  • แยก business logic ที่ใช้ร่วมกันออกมาเป็นจุดอ้างอิงกลาง ไม่เขียนซ้ำสองที่

คำถามที่พบบ่อย

แอปเล็กที่ยังไม่มี Webhook จำเป็นต้องใช้ Supabase Edge Functions ไหม

ไม่จำเป็น ถ้าโปรเจกต์ยังไม่มีงานที่ต้องรับ Webhook ภายนอกหรือต้องแยก deploy จากแอปหลัก การใช้ Route Handler บน Vercel เพียงอย่างเดียวก็เพียงพอแล้วในระยะแรก

Edge Functions เรียก Server Action ของ Next.js ได้ไหม

โดยทั่วไป Server Action ออกแบบมาให้เรียกจาก frontend ของแอปเองเป็นหลัก ถ้าต้องการให้ Edge Functions สื่อสารกับแอปหลัก ควรเปิดเป็น API endpoint ปกติที่ตรวจสอบสิทธิ์ผ่าน token แทนการเรียก Server Action โดยตรง

ถ้า logic ต้องข้าม RLS แต่ก็ผูกกับ session ผู้ใช้ ควรทำยังไง

สามารถทำใน Route Handler บน Vercel ได้ โดยตรวจสอบ session ผู้ใช้ก่อน แล้วใช้ service role key เฉพาะในขอบเขตที่จำเป็น พร้อมมี logic ตรวจสอบสิทธิ์ของตัวเองอย่างรัดกุมก่อนข้าม RLS

ควรเขียน business logic ร่วมเป็น shared package ไหม

ควรพิจารณาถ้า logic นั้นถูกใช้ทั้งสองฝั่งจริงและไม่พึ่งพา Node API ที่ Edge runtime ใช้ไม่ได้ ช่วยลดความเสี่ยงที่ logic จะไม่ตรงกันเมื่อแก้ไขแยกจุด

Edge Functions ช้ากว่า Route Handler บน Vercel ไหม

ไม่แน่นอนตายตัว ขึ้นกับ workload และตำแหน่งของผู้ใช้ ควรวัด latency จริงของงานเฉพาะที่ทำ ไม่ใช้ข้อสรุปทั่วไปโดยไม่ทดสอบ

จำเป็นต้องแยก repository ระหว่าง Edge Functions กับแอปหลักไหม

ไม่จำเป็น หลายทีมเก็บโค้ด Edge Functions ไว้ในโฟลเดอร์เดียวกับแอปหลักใน repository เดียวกัน เพียงแต่ deploy แยกคำสั่งกัน ทำให้ยังดูแลโค้ดง่ายในที่เดียว

วัด Conversion LINE ทุกแบรนด์ รวมศูนย์ที่เดียว

องค์กรที่มีหลายแบรนด์ หลายสาขา หลาย LINE OA — linli รวม Tracking และส่ง Conversion กลับทุกแพลตฟอร์ม พร้อม Custom Limit เริ่มฟรี 14 วัน

ติดต่อทีม Sales

บทความที่เกี่ยวข้อง

Deploy Next.js บน Cloudflare Workers เหมาะกับโปรเจกต์แบบไหน แล้วแบบไหนควรมองข้าม

Deploy Next.js บน Cloudflare Workers เหมาะกับโปรเจกต์แบบไหน แล้วแบบไหนควรมองข้าม

Cloudflare Workers รัน Next.js ได้จริงผ่าน adapter เฉพาะ แต่ runtime ที่ต่างจาก Node ทำให้บาง feature ใช้ไม่ได้ตรง ๆ บทความนี้ชี้ว่าโปรเจกต์แบบไหนควรใช้ แบบไหนควรรอก่อน
รับ Webhook 200 ครั้งต่อนาทีจากผู้ให้บริการภายนอก แต่มั่นใจได้ยังไงว่าไม่ประมวลผลซ้ำ

รับ Webhook 200 ครั้งต่อนาทีจากผู้ให้บริการภายนอก แต่มั่นใจได้ยังไงว่าไม่ประมวลผลซ้ำ

สร้าง Webhook Receiver ด้วย Cloudflare Workers ทำได้เร็ว แต่การรองรับ Retry จริงและตรวจ Signature ให้ปลอดภัยต้องออกแบบไว้ตั้งแต่ต้น ไม่ใช่แค่รับ POST แล้วตอบ 200
สร้าง Webhook ด้วย Supabase Edge Functions แล้วเก็บทุก Event ลง Postgres ทันที

สร้าง Webhook ด้วย Supabase Edge Functions แล้วเก็บทุก Event ลง Postgres ทันที

Edge Functions ของ Supabase อยู่ใกล้ Postgres มากกว่า Workers ทั่วไป ทำให้เขียน webhook receiver ที่บันทึกข้อมูลตรงได้สะดวก แต่มีจุดที่ต้องระวังเรื่อง runtime และ idempotency ที่ต่างออกไป