อัปโหลดรูปและไฟล์ด้วย Supabase Storage จาก Next.js ตั้งแต่สร้าง bucket เขียน Policy ให้เข้าถึงเฉพาะโฟลเดอร์ตัวเอง ไปจนถึงแสดงรูปด้วย signed URL

Kittipong Chankhotr
ทีมงาน KittiStack

ถ้าแอปของคุณให้ผู้ใช้อัปโหลดรูปโปรไฟล์ ไฟล์แนบ หรือรูปสินค้า คุณต้องมีที่เก็บไฟล์ Supabase Storage คือบริการเก็บไฟล์ของ Supabase ที่ควบคุมสิทธิ์ด้วยหลักเดียวกับฐานข้อมูล บทความนี้พาสร้าง bucket เขียน Policy และอัปโหลดจาก Next.js ทีละขั้น
Supabase Storage เก็บไฟล์ไว้ใน bucket หรือถังเก็บไฟล์ แต่ละ bucket กำหนดได้ว่าเป็นแบบ public หรือ private จำกัดขนาดไฟล์และชนิดไฟล์ได้ ส่วนสิทธิ์การอัปโหลด ลบ และดาวน์โหลดควบคุมด้วย RLS Policy บนตาราง storage.objects ซึ่งเป็นหลักการเดียวกับที่อธิบายไว้ใน Supabase RLS คืออะไร
ประเภท bucket | ใครเปิดไฟล์ได้ | เหมาะกับ |
|---|
public | ทุกคนที่มี URL ของไฟล์ (การอัปโหลดและลบยังต้องผ่าน Policy) | รูปโปรไฟล์ รูปสินค้า รูปประกอบบทความ |
private | เฉพาะผู้ที่ผ่าน Policy หรือถือ signed URL ที่ยังไม่หมดอายุ | เอกสารส่วนตัว ไฟล์แนบที่ไม่ควรเปิดสาธารณะ |
bucket ใหม่เป็น private โดยปริยาย และตามเอกสารของ Supabase Storage จะไม่ยอมให้อัปโหลดเลยจนกว่าจะมี Policy อนุญาต
สร้างได้จากหน้า Storage ใน Dashboard หรือใช้ SQL ตัวอย่างนี้สร้าง bucket ชื่อ avatars แบบ public จำกัดไฟล์ละ 2 MB และรับเฉพาะรูปภาพ
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values (
'avatars',
'avatars',
true,
2097152,
array['image/jpeg', 'image/png', 'image/webp']
);ขนาดสูงสุดต่อไฟล์ตั้งได้ที่ระดับ bucket แต่ต้องไม่เกินค่า global ของโปรเจกต์ ซึ่งบนแผนฟรีตั้งได้สูงสุด 50 MB
รูปแบบที่ใช้บ่อยคือให้ทุกคนมีโฟลเดอร์ชื่อ user id ของตัวเอง แล้ว Policy ตรวจว่าโฟลเดอร์แรกใน path ตรงกับ id ของผู้ที่ล็อกอินอยู่
create policy "avatars_insert_own_folder"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'avatars'
and (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);
create policy "avatars_delete_own_folder"
on storage.objects
for delete
to authenticated
using (
bucket_id = 'avatars'
and (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);ถ้าต้องการให้อัปโหลดทับไฟล์เดิมได้ (upsert) ต้องมี Policy แบบ SELECT และ UPDATE เพิ่มด้วย ตามที่เอกสารของ Supabase ระบุ ส่วนใน bucket แบบ public การเปิดดูไฟล์ผ่าน URL ไม่ต้องใช้ Policy SELECT
โค้ดนี้ใช้ในฝั่ง client หลังผู้ใช้ล็อกอินแล้ว (วิธีทำระบบล็อกอินดู Supabase Auth Next.js) ส่วน createClient คือฟังก์ชันสร้าง Supabase client ที่โปรเจกต์ของคุณตั้งไว้
"use client";
import { createClient } from "@/lib/supabase/client";
export async function uploadAvatar(file: File, userId: string) {
const supabase = createClient();
const ext = file.name.split(".").pop()?.toLowerCase() ?? "jpg";
const path = `${userId}/${crypto.randomUUID()}.${ext}`;
const { error } = await supabase.storage
.from("avatars")
.upload(path, file, { contentType: file.type, cacheControl: "3600" });
if (error) throw error;
return path;
}ตั้งชื่อไฟล์ใหม่เองแทนการใช้ชื่อเดิมที่ผู้ใช้ตั้ง เพราะเอกสารของ Supabase ระบุชุดอักขระที่ใช้ในชื่อไฟล์ได้ (อังกฤษ ตัวเลข และเครื่องหมายบางตัว) ซึ่งไม่มีตัวอักษรไทย ชื่อไฟล์ภาษาไทยจึงอาจอัปโหลดไม่ผ่าน การใช้ UUID ยังกันชื่อซ้ำให้ด้วย
bucket แบบ public ขอ URL ได้ตรง ๆ ส่วน bucket แบบ private ต้องสร้าง signed URL ที่หมดอายุตามเวลาที่กำหนด (หน่วยเป็นวินาที)
// bucket แบบ public
const { data } = supabase.storage.from("avatars").getPublicUrl(path);
const url = data.publicUrl;
// bucket แบบ private: ลิงก์ใช้ได้ 60 วินาที
const { data: signed, error } = await supabase.storage
.from("documents")
.createSignedUrl(path, 60);ตามเอกสารของ Supabase ตอนเขียนบทความนี้ แผนฟรีมีพื้นที่เก็บไฟล์ 1 GB และขนาดไฟล์สูงสุดต่อไฟล์ 50 MB ถ้าใช้เกินโควต้าจะได้รับอีเมลแจ้งเตือนและเข้าสู่ช่วงผ่อนผัน ตัวเลขเหล่านี้เปลี่ยนได้ ควรตรวจกับ หน้าราคาของ Supabase ก่อนใช้งานจริง ภาพรวมแผนและราคาอ่านได้ที่ Supabase คืออะไร
ใช้ได้ภายในโควต้าของแผนฟรี (ตอนเขียนคือเก็บไฟล์ได้ 1 GB และไฟล์ละไม่เกิน 50 MB) ถ้าเกินต้องอัปเกรดแผนหรือจ่ายตามส่วนที่ใช้เพิ่ม ตรวจเงื่อนไขล่าสุดจากหน้าราคาของ Supabase
ได้ถ้าเป็นรูปที่ตั้งใจให้คนอื่นเห็น เช่น รูปโปรไฟล์ แต่ใครมี URL ก็เปิดดูได้ ส่วนเอกสารหรือไฟล์ส่วนตัวควรใช้ private bucket แล้วให้ลิงก์แบบ signed URL
ถ้าใช้ service key จากเซิร์ฟเวอร์ที่เชื่อถือได้ คำสั่งจะข้าม Policy ทั้งหมด จึงต้องตรวจสิทธิ์ของผู้ใช้ในโค้ดเอง และห้ามส่ง key นี้ไปถึงฝั่งหน้าเว็บ
Supabase Storage ใช้กฎสิทธิ์ตัวเดียวกับฐานข้อมูล เริ่มจากเลือกว่า bucket เป็น public หรือ private เขียน Policy ให้ผู้ใช้เข้าถึงเฉพาะโฟลเดอร์ของตัวเอง แล้วทดสอบด้วยสองบัญชีก่อนเปิดใช้จริง ถ้าต้องการระบบอัปโหลดไฟล์ที่ตั้งสิทธิ์ครบอยู่ในแอปของคุณ ดูบริการ รับทำเว็บแอปพลิเคชัน
Oct 28, 2026