본문으로 건너뛰기

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 키 확인​

프로젝트 생성 후 Settings → API에서 확인합니다.

[캡처- Supabase Dashboard > Settings > API 페이지에서 Project URL, anon key 표시 화면]

  • Project URL — https://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

:::danger 주의 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 → Authentication → Providers에서 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>
);
}

:::tip 실시간 활성화 Supabase Dashboard → Database → Replication에서 해당 테이블의 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 조작

참고 자료​

다음 단계​

Claude Code에서 자연어로 Supabase를 조작하고 싶다면 Claude Code에서 Supabase MCP 연동을 참고하세요.