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

Server Component เรียก Supabase ตรง ๆ หรือผ่าน API Route ต่างกันตรงไหน

ทีมบรรณาธิการ linli12 ส.ค. 04:52อัปเดต 12 ส.ค. 04:52อ่าน 5 นาที
Server Component เรียก Supabase ตรง ๆ หรือผ่าน API Route ต่างกันตรงไหน
บทความนี้จัดทำเพื่อความรู้ ชื่อธุรกิจ ตัวเลข หรือบทสนทนาที่ไม่มีแหล่งอ้างอิงกำกับเป็นกรณีตัวอย่างเพื่ออธิบายแนวคิด ดูรายละเอียดที่ นโยบายกองบรรณาธิการ

สรุปสั้น ๆ

Next.js บน Vercel คู่กับ Supabase เป็นชุดที่นิยมใช้สร้าง SaaS เพราะ Supabase ให้ Postgres, Auth และ Storage มาพร้อมกัน คำถามหลักที่ทีมต้องตัดสินใจคือให้ Server Component เรียก Supabase ตรง ๆ เพื่อความเร็ว หรือผ่าน API Route เพื่อควบคุม Logic รวมศูนย์ ทั้งสองแบบใช้ได้จริง แต่เหมาะกับสถานการณ์ต่างกัน และทีมต้องวาง RLS ให้ถูกตั้งแต่ต้นไม่ว่าจะเลือกทางไหน

โปรเจกต์หนึ่งที่ผมเข้าไปช่วยรีวิว Code เริ่มต้นด้วยการให้ทุก Server Component เรียก Supabase Client ตรง ๆ เพราะเอกสารตัวอย่างของ Supabase ทำแบบนั้น ทุกอย่างไปได้สวยจนถึงเดือนที่สามที่ทีมต้องเพิ่ม Logic ตรวจสิทธิ์แบบซับซ้อนขึ้น เช่นผู้ใช้บาง Role ดูข้อมูลบางแถวได้เฉพาะช่วงเวลาที่กำหนด ปัญหาคือ Logic แบบนี้กระจายอยู่ใน Component หลายสิบไฟล์ เพราะแต่ละหน้าคัดลอกวิธีเรียก Supabase มาแล้วปรับนิดหน่อยเอง แก้จุดหนึ่งแล้วลืมแก้อีกจุดหนึ่งบ่อยครั้ง

คำถามว่าควรเรียก Supabase ตรง ๆ จาก Server Component หรือผ่าน API Route ไม่มีคำตอบเดียวที่ถูกเสมอ เพราะทั้งสองแบบเป็น Pattern ที่ Vercel และ Supabase รองรับอย่างเป็นทางการ สิ่งที่ต่างกันคือ Trade-off ระหว่างความเร็วในการพัฒนากับความง่ายในการดูแล Logic รวมศูนย์เมื่อโปรเจกต์โตขึ้น

บทความนี้จะเทียบสองแนวทางนี้แบบละเอียด พร้อมพูดถึงจุดที่ทีมมักพลาดตอนวาง Row Level Security และ Connection Pooling ซึ่งเป็นสองเรื่องที่ทำให้ Architecture ที่ดูดีตอนเริ่มต้นกลายเป็นปัญหาตอนมีผู้ใช้จริงเยอะขึ้น

แบบที่หนึ่ง: Server Component เรียก Supabase ตรง ๆ

Next.js App Router เปิดให้ Server Component รัน Code ฝั่ง Server ได้โดยตรง ทำให้เรียก Supabase Client ผ่าน Server-side SDK แล้ว Query ข้อมูลมาแสดงผลได้เลยโดยไม่ต้องผ่านชั้น API เพิ่ม วิธีนี้ลด Boilerplate ลงมาก เพราะไม่ต้องเขียน Endpoint แยกสำหรับทุกการ Query ที่ต้องการ

ข้อดีที่ชัดที่สุดคือความเร็วในการพัฒนา Feature ใหม่ เพราะนักพัฒนาแค่ Import Supabase Client เข้ามาใน Component แล้วเขียน Query ได้ทันที ไม่ต้องสลับไปสร้าง Route Handler แยกก่อน เหมาะกับทีมเล็กหรือช่วง MVP ที่ต้องการ Ship Feature ให้เร็วที่สุด

แต่จุดที่ต้องระวังคือเมื่อ Logic การตรวจสิทธิ์หรือการแปลงข้อมูลก่อนแสดงผลเริ่มซับซ้อนขึ้น การกระจาย Query อยู่ในหลาย Component ทำให้ยากต่อการรีวิวว่าทุกจุดตรวจสิทธิ์ถูกต้องตรงกันหมดหรือเปล่า และถ้าอนาคตต้องการเปิด Mobile App หรือ Third-party Integration ที่ต้องเรียกข้อมูลชุดเดียวกัน ก็ต้องเขียน Logic ซ้ำอีกรอบเพราะไม่มีชั้น API กลางให้เรียกใช้ร่วมกัน

แบบที่สอง: แยก API Route เป็นชั้นกลาง

อีกแนวทางคือสร้าง Route Handler (`app/api/.../route.ts`) เป็นชั้นกลางที่ Server Component หรือ Client ฝั่งไหนก็ตามเรียกผ่านชั้นนี้เท่านั้น ไม่มีที่ไหนเรียก Supabase ตรง ๆ นอกจากในไฟล์ Route Handler ที่กำหนดไว้ชัดเจน

ข้อดีคือ Logic การตรวจสิทธิ์ การแปลงข้อมูล และการจัดการ Error รวมอยู่ที่จุดเดียว เมื่อต้องแก้กฎการเข้าถึงข้อมูล แก้ที่ไฟล์เดียวแล้วมีผลกับทุกที่ที่เรียกใช้ นอกจากนี้ยังเปิดทางให้ Mobile App หรือ Partner ภายนอกเรียกใช้ API ชุดเดียวกันได้โดยไม่ต้องเขียนใหม่ ซึ่งสำคัญมากถ้าทีมมีแผนขยายไปมากกว่าเว็บอย่างเดียวในอนาคต

ข้อเสียคือ Boilerplate เพิ่มขึ้นชัดเจน ทุก Feature ใหม่ต้องสร้าง Route Handler อย่างน้อยหนึ่งจุดก่อนถึงจะใช้งานได้ ทีมเล็กที่ต้องการความเร็วในช่วงแรกอาจรู้สึกว่าขั้นตอนนี้ทำให้ Ship ช้าลงโดยไม่จำเป็น โดยเฉพาะถ้า Feature นั้นเป็น Query ง่าย ๆ ที่ไม่มี Logic ซับซ้อนอะไรเลย

ตารางเทียบสองแนวทางแบบเห็นภาพ

เพื่อให้ตัดสินใจง่ายขึ้น ลองดูตารางเทียบตามมิติที่ทีมมักใช้ประกอบการตัดสินใจ:

มิติServer Component เรียกตรงผ่าน API Route
ความเร็วในการพัฒนา Feature ใหม่เร็วกว่า ไม่ต้องสร้าง Endpoint แยกช้ากว่าเล็กน้อยเพราะต้องสร้าง Route ก่อน
รวมศูนย์ Logic ตรวจสิทธิ์กระจายตาม Component ดูแลยากเมื่อโตรวมที่จุดเดียว แก้ครั้งเดียวมีผลทุกที่
รองรับ Mobile/Partner ในอนาคตต้องเขียน Logic ซ้ำใหม่เรียกใช้ API ชุดเดียวกันได้เลย
เหมาะกับขนาดทีมทีมเล็ก ช่วง MVPทีมกลางถึงใหญ่ หรือ Product ที่จะโตต่อ

Row Level Security คือด่านที่พลาดไม่ได้ไม่ว่าจะเลือกแบบไหน

ไม่ว่าจะเลือก Pattern ไหน มีเรื่องหนึ่งที่ห้ามข้าม นั่นคือ Row Level Security (RLS) ของ Postgres ที่ Supabase เปิดให้ตั้งค่าได้ในระดับตาราง ทีมจำนวนไม่น้อยเข้าใจผิดว่าถ้าเขียน Logic ตรวจสิทธิ์ไว้ในชั้น API Route แล้วก็ไม่จำเป็นต้องเปิด RLS อีก เพราะคิดว่าชั้น API เป็นด่านตรวจเดียวที่พอแล้ว

ความเสี่ยงของแนวคิดนี้คือถ้ามีจุดใดจุดหนึ่งใน Codebase ที่เผลอเรียก Supabase Client แบบใช้ Service Role Key ตรง ๆ โดยลืมใส่เงื่อนไข Filter ให้ตรงกับ User ที่ Login อยู่ ข้อมูลของผู้ใช้คนอื่นอาจรั่วออกมาได้ทันทีโดยไม่มีอะไรกั้นไว้เลย เพราะ Service Role Key มีสิทธิ์อ่านเขียนทุกแถวในตารางโดยไม่ผ่าน RLS

แนวทางที่ปลอดภัยกว่าคือเปิด RLS ไว้เป็นค่าเริ่มต้นสำหรับทุกตารางที่มีข้อมูลของผู้ใช้ แล้วเขียน Policy ให้ชัดว่า User แต่ละคนอ่านหรือแก้ไขได้เฉพาะแถวที่เป็นของตัวเอง จากนั้นชั้น API Route หรือ Server Component ค่อยทำหน้าที่เป็นด่านที่สอง ไม่ใช่ด่านเดียว วิธีนี้ทำให้ต่อให้มีบั๊กในโค้ดฝั่ง Application ข้อมูลก็ยังมีเกราะป้องกันจากฝั่งฐานข้อมูลอยู่ดี

จุดที่ต้องระวังเพิ่มคือถ้าเลือกให้ Server Component เรียก Supabase ด้วย Anon Key ผ่าน Session ของผู้ใช้ RLS จะทำงานตาม User นั้นโดยอัตโนมัติ แต่ถ้าบาง Route จำเป็นต้องใช้ Service Role Key เพื่องานเบื้องหลังเช่น Cron Job หรือ Webhook ต้องแยกให้ชัดว่า Key ชุดนี้ใช้เฉพาะฝั่ง Server เท่านั้น ห้ามหลุดไปอยู่ใน Client Bundle เด็ดขาด

Connection Pooling: จุดที่โปรเจกต์ล่มตอนมีผู้ใช้เยอะขึ้น

ปัญหาอีกเรื่องที่ทีมมักไม่คิดถึงตอนเริ่มโปรเจกต์คือการจัดการ Connection ไปยัง Postgres เมื่อ Deploy บน Vercel ที่ใช้ Serverless Function หรือ Edge Function แต่ละ Function Instance อาจเปิด Connection ใหม่ของตัวเองทุกครั้งที่ถูกเรียก ถ้ามี Traffic สูงพร้อมกันจำนวนมาก Connection ที่เปิดพร้อมกันอาจชนกับ Limit ของฐานข้อมูลจนเกิด Error ทันที

Supabase มีทางแก้เรื่องนี้ผ่าน Connection Pooler (PgBouncer) ที่ทำหน้าที่จัดคิว Connection แทนที่จะให้ทุก Function เปิด Connection ตรงไปที่ Postgres เอง ทีมที่ Deploy บน Vercel ควรตั้งค่าให้ใช้ Connection String ที่ผ่าน Pooler สำหรับงานที่เรียกถี่ ในขณะที่งาน Migration หรือ Query ที่ต้องการ Session ยาว ๆ อาจต้องใช้ Connection String แบบตรงแยกออกไป

ทีมที่ข้ามขั้นตอนนี้มักเจอปัญหาแบบเดียวกันคือแอปทำงานปกติดีตอน Traffic น้อย แต่พอมีแคมเปญการตลาดหรือมีผู้ใช้เข้ามาพร้อมกันเยอะ ๆ ระบบเริ่ม Error แบบสุ่มโดยไม่มีใครเข้าใจสาเหตุทันที เพราะ Log ที่เห็นเป็นแค่ Connection Timeout ทั่วไปที่ไม่ได้บอกตรง ๆ ว่าปัญหามาจากการไม่ได้ใช้ Pooler

ผูก Supabase Auth เข้ากับ Middleware ของ Next.js ให้ปลอดภัย

Supabase มีระบบ Auth ในตัวที่ทำงานร่วมกับ Next.js Middleware ได้ดี โดยใช้ Middleware ตรวจ Session ก่อนที่ Request จะถึง Server Component หรือ Route Handler ทำให้กันหน้าที่ต้อง Login ก่อนได้ตั้งแต่ชั้นแรก ไม่ต้องรอให้ Component แต่ละหน้าตรวจเอง

จุดที่ทีมมักพลาดคือลืม Refresh Session Token ให้ทันเวลาใน Middleware ทำให้ผู้ใช้ที่ Login ค้างไว้นานถูก Logout กลางคันโดยไม่รู้ตัว ทาง Supabase แนะนำให้ใช้ Helper Library ที่จัดการ Refresh Token อัตโนมัติแทนการเขียน Logic ตรวจ Session เองทั้งหมด เพราะรายละเอียดปลีกย่อยเรื่อง Cookie และ Token Rotation ผิดพลาดง่ายถ้าเขียนเอง

อีกจุดที่ควรวางแผนล่วงหน้าคือกรณีที่ต้องรองรับหลาย Role เช่น Admin, Member และ Guest ควรตัดสินใจตั้งแต่ต้นว่าจะเก็บ Role ไว้ใน Custom Claim ของ JWT หรือ Query จากตารางแยกทุกครั้ง วิธีแรกเร็วกว่าเพราะไม่ต้อง Query เพิ่ม แต่ต้องมี Mechanism อัปเดต Claim เมื่อ Role เปลี่ยน ส่วนวิธีที่สอง Query ตรงจากฐานข้อมูลเสมอทำให้ข้อมูล Role อัปเดตทันทีแต่แลกกับ Latency ที่เพิ่มขึ้นเล็กน้อยในทุก Request

ตัวอย่างสมมติ: ทีมสองแบบที่เลือกต่างกันแล้วเจอผลต่างกัน

ลองสมมติทีม A เป็นทีม 2 คนที่กำลังสร้าง MVP เพื่อทดสอบไอเดียกับผู้ใช้กลุ่มแรก เลือกให้ Server Component เรียก Supabase ตรง ๆ ทั้งหมด ทำให้ Ship Feature ได้เร็วภายในไม่กี่สัปดาห์ และเมื่อไอเดียยังไม่ได้รับการตอบรับที่ดีพอ การเปลี่ยนทิศทางหรือ Pivot ก็ทำได้เร็วเพราะ Code ไม่ซับซ้อน (สถานการณ์นี้เป็นตัวอย่างสมมติเพื่อประกอบการอธิบาย ไม่ใช่กรณีศึกษาที่มีการเก็บสถิติจริง)

ในทางกลับกัน ทีม B เป็นทีม 8 คนที่รู้อยู่แล้วว่าจะต้องเปิด Public API ให้ Partner เชื่อมต่อภายในปีแรก เลือกแยก API Route ตั้งแต่ต้นแม้จะช้ากว่าตอนเริ่ม แต่พอถึงเวลาต้องเปิด Public API จริง ทีมแค่เพิ่ม Authentication Layer สำหรับ API Key ภายนอกเข้าไปในชั้น Route ที่มีอยู่แล้ว ไม่ต้องรื้อ Logic การตรวจสิทธิ์ทั้งระบบใหม่

บทเรียนจากสองทีมนี้ไม่ใช่ว่าแบบไหนดีกว่าแบบไหนเสมอ แต่คือควรเลือกตามแผนระยะกลางของ Product จริง ๆ ถ้ายังไม่รู้ว่า Product จะไปทางไหน การเริ่มด้วย Server Component เรียกตรงแล้วค่อย Refactor เป็น API Route ทีหลังเมื่อ Logic เริ่มซับซ้อน มักเป็นทางเลือกที่สมเหตุสมผลกว่าการวางโครงสร้างใหญ่ตั้งแต่วันแรกโดยยังไม่รู้ว่าจำเป็นจริงหรือเปล่า

เตรียม Environment ให้พร้อมก่อน Deploy จริงบน Vercel

เมื่อวาง Architecture เสร็จแล้ว ขั้นตอนก่อน Deploy จริงที่ทีมมักลืมคือแยก Supabase Project สำหรับ Preview Environment กับ Production ให้ชัดเจน เพราะถ้าใช้ Database เดียวกันทั้ง Preview และ Production ทุก Pull Request ที่เปิด Preview อาจไปแก้ข้อมูลจริงโดยไม่ตั้งใจระหว่างทดสอบ

  1. สร้าง Supabase Project แยกสำหรับ Staging หรือ Preview อย่างน้อยหนึ่งชุด ไม่ใช้ Database เดียวกับ Production
  2. ตั้งค่า Environment Variables ให้ Preview Deployment ชี้ไปที่ Supabase Project ของ Staging โดยอัตโนมัติ แยกจากตัวแปรที่ใช้ใน Production
  3. ทดสอบ RLS Policy ทุกตัวบน Staging ด้วย User หลาย Role ก่อนเปิดใช้จริง ไม่ใช่เชื่อว่า Policy ที่เขียนไว้ถูกต้องโดยไม่ทดสอบ
  4. ตรวจว่า Migration Script รันผ่าน CI ก่อน Merge เข้า Branch หลัก เพื่อป้องกัน Schema ที่ไม่ตรงกันระหว่าง Environment

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

  • ใช้ Service Role Key ในโค้ดฝั่ง Client โดยไม่ตั้งใจ ทำให้ผู้ใช้ทุกคนมีสิทธิ์อ่านเขียนข้อมูลทุกแถวผ่าน Browser Console ได้ทันที
  • ปิด RLS ไว้ชั่วคราวตอน Debug แล้วลืมเปิดกลับก่อน Deploy Production เป็นสาเหตุของช่องโหว่ที่พบบ่อยที่สุด
  • ไม่ใช้ Connection Pooler สำหรับ Serverless Function ทำให้ระบบล่มตอน Traffic สูงโดยไม่รู้สาเหตุที่แท้จริง
  • เขียน Logic ตรวจสิทธิ์กระจายในหลาย Component โดยไม่มีจุดรวมศูนย์ ทำให้เมื่อพบช่องโหว่ต้องไล่แก้ทีละไฟล์และมักแก้ไม่ครบทุกจุดในครั้งเดียว
  • ผสม Pattern สองแบบแบบไม่มีกฎชัดเจน บาง Feature เรียก Supabase ตรง บาง Feature ผ่าน API Route แบบสุ่ม ทำให้ทีมใหม่ที่เข้ามาต่อ Code งงว่าควรทำตามแบบไหนกันแน่ ควรตกลงเป็นมาตรฐานทีมตั้งแต่ต้นและเขียนไว้ในเอกสารว่า Feature ประเภทไหนใช้ Pattern ไหน

สรุป

การเลือกให้ Server Component เรียก Supabase ตรง ๆ หรือแยกผ่าน API Route ไม่มีคำตอบตายตัว ทีมเล็กที่ต้องการความเร็วช่วง MVP มักได้ประโยชน์จากการเรียกตรง ส่วนทีมที่รู้แผนระยะกลางว่าจะเปิด Public API หรือ Mobile App ควรวางชั้น API Route ไว้ตั้งแต่ต้น

สิ่งที่ไม่ว่าจะเลือกทางไหนก็ต้องทำให้ถูกคือ Row Level Security, Connection Pooling และการแยก Environment ระหว่าง Preview กับ Production เพราะสามเรื่องนี้เป็นจุดที่ทำให้ Architecture ที่ดูดีตอนเริ่มต้นพังตอนมีผู้ใช้จริงเยอะขึ้น

  • Server Component เรียกตรงเร็วกว่าตอนเริ่ม แต่ Logic กระจายเมื่อโปรเจกต์โต
  • API Route รวมศูนย์ Logic ได้ดีกว่า เหมาะกับทีมที่มีแผนเปิด API ภายนอก
  • RLS ต้องเปิดไว้เสมอเป็นเกราะชั้นที่สอง ไม่ใช่พึ่งแค่ Logic ฝั่ง Application
  • ตั้ง Connection Pooler และแยก Supabase Project ระหว่าง Preview กับ Production ตั้งแต่ต้น

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

Server Component เรียก Supabase ตรง ๆ ปลอดภัยไหมถ้าไม่มี API Route

ปลอดภัยได้ถ้าตั้ง Row Level Security ไว้ถูกต้องและใช้ Anon Key ผ่าน Session ของผู้ใช้ ไม่ใช้ Service Role Key ในจุดที่ผูกกับ Request ของผู้ใช้ทั่วไป RLS คือด่านป้องกันหลักไม่ว่าจะเลือก Pattern ไหน

ควรเปลี่ยนจาก Server Component เรียกตรงเป็น API Route ตอนไหน

สัญญาณที่ชัดคือเมื่อ Logic ตรวจสิทธิ์เริ่มซับซ้อนขึ้นจนต้องคัดลอกไปหลายไฟล์ หรือเมื่อมีแผนเปิด Mobile App หรือ Partner API ที่ต้องเรียกข้อมูลชุดเดียวกัน

Connection Pooler ของ Supabase จำเป็นแค่ไหนสำหรับโปรเจกต์เล็ก

โปรเจกต์ที่ Traffic ยังน้อยอาจยังไม่เจอปัญหา แต่แนะนำให้ตั้งใช้ Pooler ไว้ตั้งแต่ต้นเพราะเปลี่ยนภายหลังตอน Traffic เยอะแล้วมักต้องแก้ Connection String หลายจุดพร้อมกัน

RLS กับการตรวจสิทธิ์ใน API Route ต้องเลือกอย่างใดอย่างหนึ่งไหม

ไม่ต้องเลือก แนะนำให้ใช้ทั้งสองชั้นพร้อมกัน RLS เป็นเกราะป้องกันจากฝั่งฐานข้อมูล ส่วน Logic ใน API Route เป็นด่านตรวจเพิ่มเติมที่จัดการ Business Rule ที่ซับซ้อนกว่าที่ RLS Policy ทำได้สะดวก

ใช้ Middleware ของ Next.js ตรวจ Session ของ Supabase ต้องระวังอะไรบ้าง

ต้องใช้ Helper Library ที่จัดการ Refresh Token อัตโนมัติ ไม่เขียน Logic ตรวจ Cookie เองทั้งหมด เพราะรายละเอียดเรื่อง Token Rotation ผิดพลาดง่ายและอาจทำให้ผู้ใช้ถูก Logout กลางคันโดยไม่ตั้งใจ

แยก Supabase Project ระหว่าง Preview กับ Production จำเป็นแค่ไหน

จำเป็นมากสำหรับทีมที่เปิด Preview Deployment บ่อย เพราะถ้าใช้ Database เดียวกัน การทดสอบใน Preview อาจไปกระทบข้อมูลจริงของ Production โดยไม่ตั้งใจ

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

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

ติดต่อทีม Sales

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

รวมหลาย AI Model Provider ไว้ที่จุดเดียวด้วย Vercel AI Gateway

รวมหลาย AI Model Provider ไว้ที่จุดเดียวด้วย Vercel AI Gateway

ทีมที่ใช้ AI Model จากหลายผู้ให้บริการพร้อมกันมักเจอปัญหาโค้ดกระจัดกระจายและ Key หลุดง่าย Vercel AI Gateway แก้ปัญหานี้ด้วยจุดเชื่อมต่อเดียว บทความนี้อธิบายว่ามันทำงานยังไง และเหมาะกับทีมแบบไหนจริง ๆ
นักพัฒนาคนเดียวดูแล API วันละหมื่น Request จะเลือก Vercel หรือ Cloudflare Workers ดี

นักพัฒนาคนเดียวดูแล API วันละหมื่น Request จะเลือก Vercel หรือ Cloudflare Workers ดี

Vercel กับ Cloudflare Workers ทำงานบน Edge Network เหมือนกันในบางส่วน แต่มี Runtime และ Pricing Model ต่างกันชัดเจน บทความนี้เทียบให้เห็นว่าแต่ละแบบเหมาะกับ Workload แบบไหน โดยเฉพาะทีมที่ต้องดูแล API ที่มี Traffic สูงด้วยกำลังคนจำกัด
ทำไมทีมพัฒนาเริ่มย้าย AI Workflow มารันบน Supabase Edge Functions

ทำไมทีมพัฒนาเริ่มย้าย AI Workflow มารันบน Supabase Edge Functions

เมื่อ AI Model กลายเป็นส่วนหนึ่งของแอปแทบทุกตัว คำถามคือควรเรียกจากไหน บทความนี้เจาะเหตุผลที่ทีมจำนวนมากเลือกวาง AI Workflow ไว้ที่ Edge Functions แทนการยิงตรงจาก client