본문으로 건너뛰기

Next.js에 Supabase 적용하기

Next.js(App Router) 프로젝트에 Supabase를 연동하는 방법을 안내합니다. 인증, 데이터베이스 CRUD, 실시간 구독까지 다룹니다.

1. Supabase 프로젝트 생성

  1. supabase.com에 접속하여 Start your project 클릭
  2. GitHub 계정으로 로그인
  3. New Project 클릭
  4. Organization 선택, 프로젝트 이름, DB 비밀번호, Region 설정
  5. Create new project 클릭 (생성까지 약 2분)

API 키 확인

프로젝트 생성 후 SettingsAPI에서 확인합니다.

  • Project URLhttps://xxxxx.supabase.co
  • anon public key — 클라이언트용 공개 키
  • service_role key — 서버 전용 비공개 키

2. 패키지 설치

npm install @supabase/supabase-js @supabase/ssr
  • @supabase/supabase-js — Supabase 클라이언트 SDK
  • @supabase/ssr — Next.js 서버/클라이언트 환경에 맞는 유틸리티

3. 환경 변수 설정

.env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
주의

service_role 키는 서버에서만 사용해야 합니다. NEXT_PUBLIC_ 접두사를 붙이지 마세요.

4. Supabase 클라이언트 설정

브라우저용 클라이언트

lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
}

서버용 클라이언트

lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
const cookieStore = await cookies();

return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options),
);
},
},
},
);
}

미들웨어 설정

세션을 자동으로 갱신하기 위해 미들웨어를 추가합니다.

middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request });

const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value),
);
supabaseResponse = NextResponse.next({ request });
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options),
);
},
},
},
);

await supabase.auth.getUser();

return supabaseResponse;
}

export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

5. 인증 구현

회원가입

app/signup/page.tsx
'use client';

import { createClient } from '@/lib/supabase/client';
import { useState } from 'react';

export default function SignUpPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const supabase = createClient();

const handleSignUp = async () => {
const { error } = await supabase.auth.signUp({
email,
password,
});

if (error) {
alert(error.message);
} else {
alert('인증 이메일을 확인해 주세요.');
}
};

return (
<form onSubmit={(e) => { e.preventDefault(); handleSignUp(); }}>
<input type="email" placeholder="이메일" value={email}
onChange={(e) => setEmail(e.target.value)} />
<input type="password" placeholder="비밀번호" value={password}
onChange={(e) => setPassword(e.target.value)} />
<button type="submit">회원가입</button>
</form>
);
}

로그인 / 로그아웃

// 로그인
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});

// 로그아웃
await supabase.auth.signOut();

소셜 로그인 (Google 예시)

Supabase Dashboard → AuthenticationProviders에서 Google을 활성화한 후:

const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});

OAuth 콜백 처리

app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/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);
}

서버 컴포넌트에서 사용자 확인

app/dashboard/page.tsx
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();

if (!user) {
redirect('/login');
}

return <div>{user.email}님의 대시보드</div>;
}

6. 데이터베이스 CRUD

Supabase Dashboard의 Table Editor에서 테이블을 생성하거나, SQL Editor에서 직접 생성합니다.

SQL Editor
CREATE TABLE posts (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
user_id UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT now()
);

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "누구나 조회 가능"
ON posts FOR SELECT
TO authenticated, anon
USING (true);

CREATE POLICY "본인만 작성 가능"
ON posts FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);

조회

const { data: posts, error } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false });

생성

const { error } = await supabase
.from('posts')
.insert({ title: '제목', content: '내용', user_id: user.id });

수정

const { error } = await supabase
.from('posts')
.update({ title: '수정된 제목' })
.eq('id', postId);

삭제

const { error } = await supabase
.from('posts')
.delete()
.eq('id', postId);

7. 실시간 구독

데이터 변경을 실시간으로 감지합니다.

'use client';

import { createClient } from '@/lib/supabase/client';
import { useEffect, useState } from 'react';

export default function RealtimePosts() {
const [posts, setPosts] = useState([]);
const supabase = createClient();

useEffect(() => {
// 초기 데이터 로드
supabase.from('posts').select('*').then(({ data }) => {
setPosts(data || []);
});

// 실시간 구독
const channel = supabase
.channel('posts-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
if (payload.eventType === 'INSERT') {
setPosts((prev) => [payload.new, ...prev]);
}
},
)
.subscribe();

return () => {
supabase.removeChannel(channel);
};
}, []);

return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
실시간 활성화

Supabase Dashboard → DatabaseReplication에서 해당 테이블의 Realtime을 활성화해야 합니다.

전체 흐름 요약

1. Supabase 프로젝트 생성 & API 키 확인
2. npm install @supabase/supabase-js @supabase/ssr
3. .env.local에 URL과 anon key 설정
4. lib/supabase/에 client.ts, server.ts 생성
5. middleware.ts로 세션 자동 갱신
6. supabase.auth로 인증, supabase.from()으로 DB 조작

참고 자료