Supabase로 사이드프로젝트 백엔드 구축하기 2026 — 무료로 시작하는 완전 가이드
Supabase로 사이드 프로젝트의 백엔드를 무료로 구축하는 방법. PostgreSQL, 인증, RLS, 실시간 구독, Edge Functions, Next.js 연동까지 단계별로 정리했습니다.
Supabase — 사이드 프로젝트에 최적화된 백엔드
Supabase는 Firebase의 오픈소스 대안으로, PostgreSQL 기반의 인증·스토리지·실시간 구독·Edge Functions를 제공합니다. 직접 백엔드 서버를 구축하지 않고도 완전한 백엔드를 무료로 시작할 수 있어 사이드 프로젝트에 최적입니다.
무료 플랜 한도
| 항목 | 무료 한도 | 충분한가? |
|---|---|---|
| 데이터베이스 | 500MB | 초기 사이드 프로젝트에 넉넉 |
| 스토리지 | 1GB | 이미지 수백 장 가능 |
| 대역폭 | 5GB/월 | 소규모 트래픽 충분 |
| Edge Functions | 500,000 호출/월 | 충분 |
| 인증 사용자 | 50,000 MAU | 매우 충분 |
| 프로젝트 수 | 2개 | 사이드 프로젝트 여러 개 가능 |
무료 프로젝트는 7일 이상 활성 상태가 없으면 일시 정지됩니다. 월 1회 이상 접속으로 유지하세요.
프로젝트 생성 및 초기 설정
1단계: 프로젝트 생성
supabase.com가입- "New Project" 클릭
- 리전 선택: Northeast Asia (Seoul) 이 가장 가깝습니다
- DB 비밀번호 설정 (반드시 안전하게 보관)
- 생성까지 약 1~2분 소요
2단계: 환경 변수 확인
Project Settings → API에서 확인:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY(서버 사이드 전용, 절대 클라이언트에 노출 금지)
Next.js 연동
설치
npm install @supabase/supabase-js @supabase/ssr
클라이언트 설정
// src/lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
import { Database } from "@/types/supabase";
export function createClient() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { Database } from "@/types/supabase";
export async function createClient() {
const cookieStore = await cookies();
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}
데이터베이스 설계와 RLS
테이블 생성 예시 (할 일 앱)
-- todos 테이블 생성
create table todos (
id uuid default gen_random_uuid() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
title text not null,
completed boolean default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- updated_at 자동 갱신 트리거
create or replace function update_updated_at()
returns trigger as $$
begin
new.updated_at = now();
return new;
end;
$$ language plpgsql;
create trigger todos_updated_at
before update on todos
for each row execute function update_updated_at();
-- 인덱스 추가 (user_id 기반 조회 빠르게)
create index todos_user_id_idx on todos(user_id);
Row Level Security (RLS) — 필수 설정
-- RLS 활성화 (반드시!)
alter table todos enable row level security;
-- 본인 데이터만 조회
create policy "Users can view own todos"
on todos for select
using (auth.uid() = user_id);
-- 본인 데이터만 생성
create policy "Users can insert own todos"
on todos for insert
with check (auth.uid() = user_id);
-- 본인 데이터만 수정
create policy "Users can update own todos"
on todos for update
using (auth.uid() = user_id);
-- 본인 데이터만 삭제
create policy "Users can delete own todos"
on todos for delete
using (auth.uid() = user_id);
RLS 없이 배포하면 누구나 모든 데이터에 접근할 수 있습니다. 반드시 활성화하세요.
CRUD 구현
TypeScript 타입 자동 생성
npx supabase gen types typescript --project-id your-project-id > src/types/supabase.ts
기본 CRUD
const supabase = createClient();
// 조회 (RLS 자동 적용)
const { data: todos, error } = await supabase
.from("todos")
.select("*")
.order("created_at", { ascending: false });
// 생성
const { data, error } = await supabase
.from("todos")
.insert({ title: "Supabase 마스터하기" })
.select()
.single();
// 수정
const { error } = await supabase
.from("todos")
.update({ completed: true })
.eq("id", todoId);
// 삭제
const { error } = await supabase
.from("todos")
.delete()
.eq("id", todoId);
조건 조회
// 완료된 항목만
const { data } = await supabase
.from("todos")
.select("*")
.eq("completed", true)
.order("updated_at", { ascending: false })
.limit(10);
// 검색
const { data } = await supabase
.from("todos")
.select("*")
.ilike("title", `%${searchTerm}%`);
// 조인
const { data } = await supabase
.from("posts")
.select(`
id,
title,
author:profiles(id, username, avatar_url)
`);
인증 구현
이메일 + 소셜 로그인
// 이메일 회원가입
const { error } = await supabase.auth.signUp({
email: "user@example.com",
password: "secure-password",
options: {
data: { username: "devuser" }, // 사용자 메타데이터
},
});
// 이메일 로그인
const { data, error } = await supabase.auth.signInWithPassword({
email: "user@example.com",
password: "secure-password",
});
// Google OAuth
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
// GitHub OAuth
const { error } = await supabase.auth.signInWithOAuth({
provider: "github",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
// 로그아웃
await supabase.auth.signOut();
// 현재 사용자
const { data: { user } } = await supabase.auth.getUser();
인증 콜백 라우트 (Next.js App Router)
// app/auth/callback/route.ts
import { NextResponse } from "next/server";
import { createClient } from "@/lib/supabase/server";
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get("code");
if (code) {
const supabase = await createClient();
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(`${origin}/dashboard`);
}
실시간 구독
// 할 일 목록 실시간 동기화
const channel = supabase
.channel("todos-channel")
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "todos",
filter: `user_id=eq.${userId}`,
},
(payload) => {
if (payload.eventType === "INSERT") {
setTodos((prev) => [payload.new as Todo, ...prev]);
} else if (payload.eventType === "UPDATE") {
setTodos((prev) =>
prev.map((t) => (t.id === payload.new.id ? (payload.new as Todo) : t))
);
} else if (payload.eventType === "DELETE") {
setTodos((prev) => prev.filter((t) => t.id !== payload.old.id));
}
}
)
.subscribe();
// 정리
return () => {
supabase.removeChannel(channel);
};
Edge Functions
서버 사이드 로직을 Supabase 인프라에서 실행합니다.
// supabase/functions/send-notification/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
serve(async (req) => {
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
const { userId, message } = await req.json();
// 데이터베이스에 알림 저장
await supabase.from("notifications").insert({ user_id: userId, message });
return new Response(JSON.stringify({ success: true }), {
headers: { "Content-Type": "application/json" },
});
});
# 배포
supabase functions deploy send-notification
# 호출
curl -X POST https://your-project.supabase.co/functions/v1/send-notification \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"userId": "uuid", "message": "안녕하세요!"}'
스토리지 (파일 업로드)
// 프로필 이미지 업로드
const { data, error } = await supabase.storage
.from("avatars")
.upload(`${user.id}/avatar.jpg`, file, {
upsert: true,
contentType: "image/jpeg",
});
// 공개 URL 가져오기
const { data: { publicUrl } } = supabase.storage
.from("avatars")
.getPublicUrl(`${user.id}/avatar.jpg`);
사이드 프로젝트 아키텍처 예시
Next.js (Vercel)
↕ supabase-js
Supabase
├── PostgreSQL (데이터베이스)
├── Auth (인증)
├── Storage (파일)
├── Realtime (실시간 동기화)
└── Edge Functions (서버 로직)
이 스택으로 만들 수 있는 사이드 프로젝트:
- 북마크·링크 관리 앱
- 팀 투표·설문 도구
- 습관 트래커
- 개발자 TIL 블로그
- 단순 SaaS (대기 목록, 피드백 수집)
- 실시간 채팅 앱
실전 체크리스트
배포 전 필수 확인:
□ 모든 테이블에 RLS 활성화 되어 있는가?
□ anon key가 서버 전용 로직에 사용되지 않는가?
□ service_role_key가 클라이언트에 노출되지 않는가?
□ TypeScript 타입이 최신 스키마와 동기화되어 있는가?
□ Edge Functions 환경 변수가 설정되어 있는가?
□ 스토리지 버킷 접근 정책이 올바르게 설정되어 있는가?
관련 글: Vercel 무료 배포 완전 가이드 · Next.js SSG 완전 가이드 · 개발자 포트폴리오 만들기
관련 글
Next.js SSG 완벽 가이드 2026 — App Router 정적 사이트 생성 실전
Next.js App Router에서 SSG를 제대로 활용하는 방법. generateStaticParams, revalidate, 동적 라우트 정적 생성, Core Web Vitals 최적화까지 실전 코드로 정리했습니다.
React vs Next.js 선택 가이드 2026 — 프로젝트에 맞는 프레임워크 고르기
React와 Next.js의 차이점, 장단점, 선택 기준을 2026년 기준으로 비교합니다. SPA vs SSR/SSG, 라우팅, 성능, SEO, 배포 환경까지 실제 프로젝트 상황별로 어떤 것을 선택해야 할지 정리했습니다.
Next.js + MDX로 개발자 블로그 만들기 2026 — 완전 실전 가이드
Next.js App Router와 MDX를 활용해 개발자 블로그를 처음부터 만드는 방법을 단계별로 설명합니다. 정적 생성, SEO 메타데이터, 코드 하이라이팅, RSS 피드, JSON-LD Schema까지 완전 정리했습니다.