ํฐ์คํ ๋ฆฌ ๋ทฐ
[NEXTFLIX_Day4] ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ(NEXT_PUBLIC_์ ์ฌ์ฉ)
์ฑ._. 2025. 3. 29. 02:21zustand์ supabase๋ฅผ ํ์ฉํด์ ๋ก๊ทธ์ธ ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ํ๋ ค๊ณ ํ๋ค.
๋ก๊ทธ์ธ ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ์ด ๋ฌด์์ผ๊น?
๋ก๊ทธ์ธ ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ์ด๋?
๐์ฌ์ฉ์๊ฐ ๋ณด์ ํ ์ธ์ฆ ํ ํฐ์ด ์ ํจํ์ง ํ์ธํ๋ ๊ณผ์
์ฆ, ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ๊ณ ์๋์ง ํ์ธํ๋ ์ญํ ์ด๋ค.
Supabase์์๋ auth.getSession() ๋๋ auth.refreshSession()์ ์ด์ฉํด์ ํ ํฐ์ ๊ฒ์ฆํ ์ ์๋ค.
๋ก๊ทธ์ธ ํ ํฐ ๊ฒ์ฆ ๋ก์ง ํ๋ฆ
๐๋ก๊ทธ์ธ ๊ณผ์
- ์ฌ์ฉ์๊ฐ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ์ฌ ๋ก๊ทธ์ธ ์์ฒญ์ ๋ณด๋
- supabase.auth.signInWithPassword()๋ฅผ ํตํด ์ธ์ฆ (๋น๋ฐ๋ฒํธ๋ฅผ ํตํด ์ฌ์ฉ์ ์ธ์ฆํ๋ ๋ฐฉ์)
- ์ธ์ฆ์ด ์ฑ๊ณตํ๋ฉด ์์ธ์ค ํ ํฐ(Access Token)๊ณผ ๋ฆฌํ๋ ์ ํ ํฐ(Refresh Token) ์ด ๋ฐ๊ธ๋จ
- Zustand ์คํ ์ด์ ๋ก๊ทธ์ธ ์ํ(isSignedIn)์ ์ ์ ์ ๋ณด(user) ์ ์ฅ
๐๋ก๊ทธ์ธ ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ ๊ณผ์
์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๊ฑฐ๋, ์ฑ์ ๋ค์ ์คํํ ๋
- Supabase auth.getSession()์ ํธ์ถํ์ฌ ํ์ฌ ๋ก๊ทธ์ธ ์ธ์ ์ด ์ ํจํ์ง ํ์ธ
- ์ธ์
์ด ์ ํจํ๋ฉด
- Zustand ์ํ(isSignedIn, user)๋ฅผ ๊ฐฑ์ ํ์ฌ ๋ก๊ทธ์ธ ์ํ ์ ์ง.
- ์ธ์
์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์์ผ๋ฉด
- auth.refreshSession()์ ํธ์ถํ์ฌ ์๋ก์ด ํ ํฐ์ ๋ฐ๊ธ ์๋ (์ ์ฉ ์ ํจ)
- ๋ฆฌํ๋ ์ ํ ํฐ๋ ๋ง๋ฃ๋์๋ค๋ฉด ์ฌ์ฉ์๋ฅผ ๋ก๊ทธ์์ ์ฒ๋ฆฌ
supabase API
supabase Docs๋ฅผ ๋ณด๋ฉด ๋ค์ํ API๋ค์ด ์๋ค.
๊ทธ ์ค ๋ด๊ฐ ์ฌ์ฉํ API๋ฅผ ์ ๋ฆฌํด๋ณด๊ฒ ๋ค.
๐signInWithPassword()
![]() |
![]() |
//์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ
const { data, error } = await supabase.auth.signInWithPassword({
email: 'example@email.com',
password: 'example-password',
})
// ์ ํ๋ฒํธ์ ๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ
const { data, error } = await supabase.auth.signInWithPassword({
email: 'example@email.com',
password: 'example-password',
})
๐getSession()
![]() |
![]() |
// ์ธ์
๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const { data, error } = await supabase.auth.getSession()
Zustand + Supabase
zustand๋ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์ญ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค.
supabase์ ๋ก๊ทธ์ธ ๋ก์ง๊ณผ ํจ๊ป ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธ/๋ก๊ทธ์์/ํ ํฐ ์ ํจ์ฑ์ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ๋ค.
๋ก์ง์ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ํ์๋ค.
๋ก๊ทธ์ธ ํจ์ : signInWithPassword๋ฅผ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธํ๊ณ , ๊ทธ์ ํด๋นํ๋ ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ user ์ํ์ set
๋ก๊ทธ์์ ํจ์ : signOut๋ฅผ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์์
ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ ํจ์ : getSession์ ์ฌ์ฉํ์ฌ ์ ํจํ๋ฉด ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ setํ๊ณ ์๋ค๋ฉด ๋ก๊ทธ์์ ์ํ์ฒ๋ผ set
๊ทธ๋ฌ๋, store์ supabase ๋ก์ง์ ์ง์ ์ฌ์ฉํ๋๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.

.env ํ์ผ์์ ํ๊ฒฝ๋ณ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๊ณ ์์๋ค.
SUPABASE_URL= ''
SUPABASE_KEY= ''
์์๋ ์ ์๋ฏ zustand๋ 'ํด๋ผ์ด์ธํธ'์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ค.
ํ์ง๋ง supabase์ ํค๋ ์๋ฒ ํ๊ฒฝ๋ณ์๋ก ์ฌ์ฉ๋๊ณ ์๋ค.
์ด์ ๊ณผ์ ์์ ์๊ฒ ๋ ๋ค์๊ณผ ๊ฐ์ ๋ด์ฉ์ด ์๋ค.

Next.js๋ NEXT_PUBLIC_ ์ ๋์ฌ๋ฅผ ๋ถ์ฌ์ผ๋ง ํด๋ผ์ด์ธํธ์์ ํ๊ฒฝ๋ณ์๋ฅผ ๋ ธ์ถํฉ๋๋ค.
์ด๋ฅผ ํตํด ์๋ฒ ํ๊ฒฝ๋ณ์์ ํด๋ผ์ด์ธํธ ํ๊ฒฝ๋ณ์๋ฅผ ๋ช ํํ ๊ตฌ๋ถํ ์ ์์ต๋๋ค.
ํด๋ผ์ด์ธํธ(zustand)์์ ์ฌ์ฉํ ๊ฒฝ์ฐ์๋ NEXT_PUBLIC_์ ๊ผญ ๋ถ์ฌ์ฃผ์ด์ผ ํ๋ค!
NEXT_PUBLIC_์ ๋ถ์ฌ์ฃผ๋ฉด ํด๋ผ์ด์ธํธ์ ๋ ธ์ถ๋๋ค๋ ๊ฑด๋ฐ, API ํค๋ฅผ ๋ ธ์ถํด๋ ๋๋ ๊ฑธ๊น?
๋ผ๋ ์๊ฐ์ด ๋ค์๋ค.
supabase ๋ก์ง์ ์๋ฒ ์ก์
์ผ๋ก ๋ถ๋ฆฌํ๋ฉด ์๋ฒ์์ ์๋ฒ ํค๋ฅผ ์ฌ์ฉํ๋ฉด ๋์ง ์์๊น?
๋ผ๋ ์๊ฐ์ผ๋ก NEXT_PUBLIC_์ ์ฌ์ฉํ์ง ์๊ณ ์๋ฒ ์ก์
์ผ๋ก ๋ถ๋ฆฌํ์ฌ ๊ตฌํํ์๋ค.
ํ์ง๋ง ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ์ ์ค๋ฅ๊ฐ ๊ณ์๋์๋ค.
๐ค ์ ์๋ฒ ์ก์ ์ ์จ๋ ์๋๋์ง ์๋๊ฑธ๊น?
- ๋ง์ฝ ์ ๋ง๋ก ‘์๋ฒ ์ก์ ์์๋ง’ ์ด ๋ณ์๋ฅผ ์ฌ์ฉํ๊ณ , ํด๋ผ์ด์ธํธ(React ์ปดํฌ๋ํธ๋ zustand ์คํ ์ด)์์๋ ์ ํ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด NEXT_PUBLIC_ ์์ด๋ ์๋ฒ ์ธก์์๋ง ์ ๊ทผ ๊ฐ๋ฅํ๋ค.
- ํ์ง๋ง Zustand Store(ํด๋ผ์ด์ธํธ ์ฌ์ด๋)์์ Supabase API๋ฅผ ์ง์ ํธ์ถํ๋ค๋ฉด(๋๋ Supabase client๋ฅผ importํด ์ฌ์ฉํ๋ ๋ก์ง์ด ํด๋ผ์ด์ธํธ ์ฝ๋์๋ ์๋ค๋ฉด) ๊ฒฐ๊ตญ ํด๋ผ์ด์ธํธ ๋ฒ๋ค์ ํ๊ฒฝ ๋ณ์๊ฐ ํ์ํ๊ฒ ๋๋ค. ์ด๋๋ NEXT_PUBLIC_์ ๋ถ์ฌ์ผ๋ง ์ฐธ์กฐ๊ฐ ๊ฐ๋ฅํ๋ค.
๐ค API ํค๊ฐ ํด๋ผ์ด์ธํธ์ ๋ ธ์ถํด๋ ๋์ง ์์๊น?
- ๋ ธ์ถ๋๋ค. ์ ํํ ๋งํ๋ฉด, NEXT_PUBLIC_์ผ๋ก ์์๋๋ ํ๊ฒฝ ๋ณ์๋ ์ต์ข ์ ์ผ๋ก ํด๋ผ์ด์ธํธ JS ๋ฒ๋ค์ ๊ฐ์ด ์ฝ์ ๋๋ฏ๋ก, ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ ์์ค ๋งต ๋ฑ์ ํตํด ์ผ๋ง๋ ์ง ํ์ธ์ด ๊ฐ๋ฅํ๋ค.
- Supabase์ ๊ฒฝ์ฐ, anon key๋ ์๋ ํด๋ผ์ด์ธํธ ๊ณต๊ฐ์ฉ ํค๋ก ์ค๊ณ๋์ด ์๋ค. ์ฆ, ์๋น์ค ๋กค ํค(Service Role Key)์ฒ๋ผ ๋ฏผ๊ฐํ ๊ถํ์ด ์๋ ํค๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ๋
ธ์ถ์ด ๋์ด๋ ํฐ ๋ฌธ์ ๊ฐ ์๋๋ก ๋ง๋ค์ด์ง ํค.
- anon key๋ ์ด๋ฆ ๊ทธ๋๋ก ์ต๋ช ์ฌ์ฉ์๊ฐ ํธ์ถํ ์ ์๋ API ๋ฒ์๋ง์ ํ์ฉํ๋ “๊ณต๊ฐ์ฉ ํค”
- ๋ฏผ๊ฐํ API(์: ๊ด๋ฆฌ์ ๊ถํ์ด ํ์ํ API)๋ฅผ ๋ค๋ฃจ๋ ค๋ฉด Supabase์์ ์ ๊ณตํ๋ Service Key๋ฅผ ์๋ฒ ์ ์ฉ ๋ก์ง์ ์ฌ์ฉํด์ผ ํ๊ณ , ๊ทธ ํค๋ ์ ๋ ํด๋ผ์ด์ธํธ์ ๋ ธ์ถ๋๋ฉด ์ ๋๋ค.
๐ค ์๋ฒ ์ก์ ์ธ๋ฐ๋ ํด๋ผ์ด์ธํธ ๋ ธ์ถ์ด ํ์ํ ์ด์ ?
- ์๋ฒ ์ก์ ํจ์๋ฅผ ํธ์ถํ๋ ๋ถ๋ถ์ด ํด๋ผ์ด์ธํธ ์ฝ๋์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
- ๋ง์ฝ ํด๋ผ์ด์ธํธ์์ Supabase์ ๋ก์ฐ ๋ ๋ฒจ API๋ฅผ ์ง์ ํธ์ถํ๋ค๋ฉด, ๊ฒฐ๊ตญ ํด๋ผ์ด์ธํธ ์ชฝ์์๋ ํ๋ก์ ํธ URL๊ณผ anon key๋ฅผ ์์์ผ ํ๋ฏ๋ก NEXT_PUBLIC_์ด ํ์ํ๋ค.
- ์ ๋ง ์๋ฒ์์๋ง ์ฒ๋ฆฌํ๊ณ , ํด๋ผ์ด์ธํธ์์๋ ์๋ฒ ์ก์ ์ ๋จ์ ํธ์ถ๋ง ํ๋ค๋ฉด(์: ํผ ์ ์ก), ๋ด๋ถ์ ์ผ๋ก Supabase Client๋ ์ ๋ถ ์๋ฒ์์๋ง ์ฌ์ฉ๋๋ฏ๋ก ํด๋ผ์ด์ธํธ ํ๊ฒฝ ๋ณ์๊ฐ ํ์ ์๋ค. ๊ทธ๋ด ๊ฒฝ์ฐ NEXT_PUBLIC_์์ด๋ ๋์ํ๋ค.
// store > useAuthStore.ts
import { checkSession, getUserByEmail, logOutSupabase } from '@/services/signIn';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuthState {
isSignedIn: boolean;
user: User | null;
signIn: (email: string) => void;
logout: () => void;
}
interface User {
email: string;
nickname: string;
id: string;
}
export const useAuthStore = create(
persist<AuthState>(
(set) => ({
isSignedIn: false,
user: null,
// ๋ก๊ทธ์ธ ํจ์
signIn: async (email: string) => {
const userData = await getUserByEmail(email); //supabase์์ userData ๊ฐ์ ธ์ค๊ธฐ
if (!userData) return;
set({ isSignedIn: true, user: { email: userData.email, nickname: userData.nickname, id: userData.id } }); //๋ก๊ทธ์ธ ํ ์ ์ ์ ๋ณด ๋ด์
},
// ๋ก๊ทธ์์ ํจ์
logout: async () => {
await logOutSupabase();
localStorage.removeItem('auth_token'); //๋ก๊ทธ์์ ์ ๋ก์ปฌ์คํ ๋ฆฌ์ง ํ ํฐ ์ญ์
set({ isSignedIn: false, user: null });
},
// ํ ๊ทผ ์ ํจ ๊ฒ์ฆ ํจ์
checkAuth: async (email: string) => {
const userData = await checkSession();
if (userData) {
set({ isSignedIn: true, user: { email, nickname: userData.nickname, id: userData.id } }); //์ ํจํ ๋ ์ ์ ์ ๋ณด
} else {
set({ isSignedIn: false, user: null }); //์ ํจํ์ง ์์ ๋ ๋ก๊ทธ์์
}
},
}),
{ name: 'auth-storage' }
)
);
// services > signIn.ts
'use server';
import { EMAIL } from '@/constants/signUp';
import { supabase } from '@/utils/supabaseClient';
export interface SignInProps {
email: string;
password: string;
}
// supabase ๋ก๊ทธ์ธ ์๋ฒ ์ก์
export const signInSupabase = async ({ email, password }: SignInProps) => {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
throw new Error(error.message);
}
return data;
};
// ์ ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ (nickname ํฌํจ)
export const getUserByEmail = async (email: string) => {
const { data, error } = await supabase.from('users').select('id, email, nickname').eq(EMAIL, email).single();
if (error) {
console.error('์ ์ ์ ๋ณด ์กฐํ ์คํจ', error.message);
return null;
}
return data;
};
// supabase ๋ก๊ทธ์์ ์๋ฒ ์ก์
export const logOutSupabase = async () => {
await supabase.auth.signOut();
};
// supabase ์ธ์
ํ์ธ ์๋ฒ ์ก์
export const checkSession = async () => {
const { data } = await supabase.auth.getSession();
if (data?.session) {
const email = data.session.user.email;
const { data: userData, error } = await supabase
.from('users')
.select('id, nickname, email')
.eq(EMAIL, email)
.single();
if (error) {
console.log('์ ์ ์กฐํ ์คํจ', error.message);
return null;
}
return userData; //data๊ฐ ์๋ค๋ฉด userData
}
return null; //์ธ์
์ด ์๋ค๋ฉด null
};
// supabase ์ธ์
ํ์ธ ์๋ฒ ์ก์
export const checkUser = async () => {
const { data } = await supabase.auth.getUser();
if (data) {
const email = data.user?.email;
const { data: userData, error } = await supabase
.from('users')
.select('id, nickname, email')
.eq(EMAIL, email)
.single();
if (error) {
console.log('์ ์ ์กฐํ ์คํจ', error.message);
return null;
}
return userData; //data๊ฐ ์๋ค๋ฉด userData
}
return null; //์ธ์
์ด ์๋ค๋ฉด null
};



