ํ™”. 8์›” 12th, 2025

G: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์˜ ๋ณต์žกํ•จ์— ์ง€์น˜์…จ๋‚˜์š”? ๐Ÿคฏ ์‚ฌ์šฉ์ž ์ธ์ฆ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์€ ์›น ๋ฐ ๋ชจ๋ฐ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์—์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ถ€๋ถ„์ด๋ฉด์„œ๋„ ๊ฐ€์žฅ ๋ฒˆ๊ฑฐ๋กœ์šด ์ž‘์—… ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •, ์•”ํ˜ธํ™”, ์„ธ์…˜ ๊ด€๋ฆฌ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋“ฑ ๊ณ ๋ คํ•ด์•ผ ํ•  ๊ฒƒ์ด ํ•œ๋‘ ๊ฐ€์ง€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๊ฑฑ์ • ๋งˆ์„ธ์š”! ์˜ค๋Š˜๋‚  ์šฐ๋ฆฌ๋Š” Supabase๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ 5๋ถ„ ๋งŒ์— ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š” ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฏฟ๊ธฐ์ง€ ์•Š๋Š”๋‹ค๊ตฌ์š”? ๊ทธ๋Ÿผ ์ €์™€ ํ•จ๊ป˜ ์ง€๊ธˆ ๋ฐ”๋กœ ์‹œ์ž‘ํ•ด ๋ณผ๊นŒ์š”? ๐Ÿš€


๐Ÿ’ก ์™œ Supabase์ธ๊ฐ€์š”? Firebase์˜ ์˜คํ”ˆ์†Œ์Šค ๋Œ€์•ˆ!

Supabase๋Š” ์˜คํ”ˆ์†Œ์Šค ๊ธฐ๋ฐ˜์˜ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค๋กœ, ํ”ํžˆ ‘Firebase์˜ ์˜คํ”ˆ์†Œ์Šค ๋Œ€์•ˆ’์œผ๋กœ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹จ์ˆœํžˆ ๋Œ€์•ˆ์„ ๋„˜์–ด์„  ๊ฐ•๋ ฅํ•œ ํŠน์ง•๋“ค์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:

  • PostgreSQL ๊ธฐ๋ฐ˜: ์นœ์ˆ™ํ•œ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ธ PostgreSQL์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. SQL์— ์ต์ˆ™ํ•˜๋‹ค๋ฉด ๋”์šฑ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜
  • ํ†ตํ•ฉ ์ธ์ฆ(Authentication): ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ, ์†Œ์…œ ๋กœ๊ทธ์ธ(Google, GitHub, Facebook ๋“ฑ), OTP ๋กœ๊ทธ์ธ ๋“ฑ ๋‹ค์–‘ํ•œ ์ธ์ฆ ๋ฐฉ์‹์„ ์†์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘
  • ์‹ค์‹œ๊ฐ„(Realtime) ๊ตฌ๋…: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ํ‘ธ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โšก๏ธ
  • ์Šคํ† ๋ฆฌ์ง€(Storage): ํŒŒ์ผ ์—…๋กœ๋“œ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฐ์ฒด ์Šคํ† ๋ฆฌ์ง€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ“ฆ
  • ์—ฃ์ง€ ํ•จ์ˆ˜(Edge Functions): Deno ๊ธฐ๋ฐ˜์˜ ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋ฐฑ์—”๋“œ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โš™๏ธ
  • ์‰ฌ์šด ์‚ฌ์šฉ์„ฑ: ์ง๊ด€์ ์ธ ๋Œ€์‹œ๋ณด๋“œ์™€ ์ž˜ ์ •๋ฆฌ๋œ ๋ฌธ์„œ, ๊ทธ๋ฆฌ๊ณ  ๊ฐ•๋ ฅํ•œ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(JavaScript, Dart/Flutter ๋“ฑ)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ‘

์ด ๋ชจ๋“  ๊ธฐ๋Šฅ๋“ค์ด ์œ ๊ธฐ์ ์œผ๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์–ด, ๊ฐœ๋ฐœ์ž๋Š” ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ํŠนํžˆ ์ธ์ฆ ๊ธฐ๋Šฅ์€ Supabase์˜ ํ•ต์‹ฌ ๊ฐ•์  ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.


๐Ÿ› ๏ธ 1๋‹จ๊ณ„: Supabase ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ธฐ

๊ฐ€์žฅ ๋จผ์ € Supabase ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. Supabase ๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ:

    • Supabase ์›น์‚ฌ์ดํŠธ (supabase.com)์— ์ ‘์†ํ•˜์—ฌ GitHub ๊ณ„์ •์ด๋‚˜ ์ด๋ฉ”์ผ๋กœ ๊ฐ€์ž…/๋กœ๊ทธ์ธํ•ฉ๋‹ˆ๋‹ค.
    • Supabase ๋Œ€์‹œ๋ณด๋“œ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ:

    • ๋Œ€์‹œ๋ณด๋“œ์—์„œ New project ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
    • Name: ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: MyLoginApp)
    • Database Password: ๊ฐ•๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•˜๊ณ  ๊ธฐ์–ตํ•ด๋‘ก๋‹ˆ๋‹ค. ๐Ÿ”’
    • Region: ์„œ๋ฒ„ ์œ„์น˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋“ค๊ณผ ๊ฐ€๊นŒ์šด ๊ณณ์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
    • Organization: ์กฐ์ง์„ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • Create new project๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์ž ์‹œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. (์•ฝ 1~2๋ถ„ ์†Œ์š”)
  3. API ํ‚ค ํ™•์ธ:

    • ํ”„๋กœ์ ํŠธ ๋Œ€์‹œ๋ณด๋“œ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
    • ์™ผ์ชฝ ๋ฉ”๋‰ด์—์„œ Project Settings (ํ†ฑ๋‹ˆ๋ฐ”ํ€ด ์•„์ด์ฝ˜ โš™๏ธ)๋ฅผ ํด๋ฆญํ•œ ๋‹ค์Œ API๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
    • ์—ฌ๊ธฐ์„œ Project URL๊ณผ anon public ํ‚ค๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋‘ ๊ฐ’์€ ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Supabase์™€ ํ†ต์‹ ํ•  ๋•Œ ํ•„์š”ํ•˜๋‹ˆ ์ž˜ ๊ธฐ๋กํ•ด ๋‘์„ธ์š”! ๐Ÿ“‹
      • Project URL: https://[your-project-id].supabase.co ํ˜•ํƒœ
      • anon public (or public anon) key: eyJ... ํ˜•ํƒœ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ธด ๋ฌธ์ž์—ด

โš™๏ธ 2๋‹จ๊ณ„: ์ธ์ฆ(Authentication) ์„ค์ •ํ•˜๊ธฐ

์ด์ œ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ค€๋น„๋˜์—ˆ์œผ๋‹ˆ, ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ์ธ ์ธ์ฆ ๋ฐฉ์‹์„ ์„ค์ •ํ•ด ๋ด…์‹œ๋‹ค.

  1. ์ธ์ฆ ์„ค์ • ํŽ˜์ด์ง€ ์ ‘์†:

    • Supabase ๋Œ€์‹œ๋ณด๋“œ์—์„œ ์™ผ์ชฝ ๋ฉ”๋‰ด์˜ Authentication (์ž๋ฌผ์‡  ์•„์ด์ฝ˜ ๐Ÿ”‘)์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
    • Settings ํƒญ์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  2. ๋กœ๊ทธ์ธ ๊ณต๊ธ‰์ž ํ™œ์„ฑํ™”:

    • ๊ธฐ๋ณธ์ ์œผ๋กœ Email ๋กœ๊ทธ์ธ์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
    • Enable email signup์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    • Confirm email ์˜ต์…˜๋„ ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. (์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์ž… ์‹œ ์ด๋ฉ”์ผ ์ธ์ฆ์„ ํ•˜๋„๋ก ํ•˜์—ฌ ์ŠคํŒธ ๊ณ„์ •์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.) ๐Ÿ“ง
    • ์›ํ•œ๋‹ค๋ฉด Google, GitHub, Facebook ๋“ฑ ๋‹ค๋ฅธ ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณต๊ธ‰์ž๋“ค๋„ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๊ณต๊ธ‰์ž๋ฅผ ํด๋ฆญํ•˜์—ฌ ์•ˆ๋‚ด์— ๋”ฐ๋ผ API ํ‚ค๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. (์ด ์˜ˆ์‹œ์—์„œ๋Š” ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ๋งŒ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)
  3. SMTP ์„ค์ • (์„ ํƒ ์‚ฌํ•ญ์ด์ง€๋งŒ ์ค‘์š”!):

    • ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์ด๋ฉ”์ผ, ์ด๋ฉ”์ผ ์ธ์ฆ ์ด๋ฉ”์ผ ๋“ฑ์„ ๋ฐœ์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด SMTP ์„ค์ •์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • Email Templates ์„น์…˜ ์•„๋ž˜์˜ SMTP Settings์—์„œ SendGrid, Mailgun ๋“ฑ ์ด๋ฉ”์ผ ์„œ๋น„์Šค ์ œ๊ณต์ž์˜ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. (์ด ๋‹จ๊ณ„๋Š” 5๋ถ„ ์•ˆ์— ์™„๋ฃŒํ•˜๊ธฐ๋Š” ์–ด๋ ต์ง€๋งŒ, ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค!) ๐Ÿ“ฉ

๐Ÿ’ป 3๋‹จ๊ณ„: ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ (5๋ถ„ ์•ˆ์— ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ ์™„์„ฑ!)

๊ฐ€์žฅ ํ•ต์‹ฌ์ ์ธ ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” Supabase์—์„œ ์ œ๊ณตํ•˜๋Š” React์šฉ Auth UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋†€๋ž๋„๋ก ๋น ๋ฅด๊ฒŒ ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. (React ๋Œ€์‹  ์ผ๋ฐ˜ HTML/JS๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋ฉฐ, Vue, Next.js ๋“ฑ ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋„ ์œ ์‚ฌํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.)

์ด ์˜ˆ์‹œ์—์„œ๋Š” React ์•ฑ์—์„œ Supabase Auth UI๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ์ƒˆ React ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (์ด๋ฏธ ์žˆ๋‹ค๋ฉด ์Šคํ‚ต):

    npx create-react-app supabase-login-app
    cd supabase-login-app
  2. Supabase ํด๋ผ์ด์–ธํŠธ ๋ฐ Auth UI ์„ค์น˜:

    npm install @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shared
    # ๋˜๋Š” yarn add @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shared
  3. Supabase ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”: src ํด๋” ์•ˆ์— supabaseClient.js ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. YOUR_SUPABASE_URL๊ณผ YOUR_SUPABASE_ANON_KEY๋ฅผ 1๋‹จ๊ณ„์—์„œ ์–ป์€ ๊ฐ’์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์„ธ์š”.

    // src/supabaseClient.js
    import { createClient } from '@supabase/supabase-js'
    
    const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
    const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
    
    export const supabase = createClient(supabaseUrl, supabaseAnonKey)

    ์ฃผ์˜: ์‹ค์ œ ๊ฐ’์„ ์ฝ”๋“œ์— ์ง์ ‘ ๋„ฃ๋Š” ๋Œ€์‹  .env ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. supabase-login-app ๋ฃจํŠธ ํด๋”์— .env ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

    REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL
    REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

    ์ดํ›„ ์•ฑ์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

  4. Auth ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ: src/App.js ํŒŒ์ผ์„ ์—ด๊ณ  ๋ชจ๋“  ๋‚ด์šฉ์„ ์ง€์šด ํ›„ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ถ™์—ฌ๋„ฃ์œผ์„ธ์š”.

    
    // src/App.js
    import React, { useState, useEffect } from 'react';
    import { Auth } from '@supabase/auth-ui-react';
    import { ThemeSupa } from '@supabase/auth-ui-shared'; // ํ…Œ๋งˆ ์ž„ํฌํŠธ
    import { supabase } from './supabaseClient'; // 3๋‹จ๊ณ„์—์„œ ๋งŒ๋“  supabaseClient ์ž„ํฌํŠธ
    
    function App() {
      const [session, setSession] = useState(null);
    
      useEffect(() => {
        // ์„ธ์…˜ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.
        supabase.auth.getSession().then(({ data: { session } }) => {
          setSession(session);
        });
    
        const {
          data: { subscription },
        } = supabase.auth.onAuthStateChange((_event, session) => {
          setSession(session);
        });
    
        return () => subscription.unsubscribe();
      }, []);
    
      if (!session) {
        // ์„ธ์…˜์ด ์—†์œผ๋ฉด ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… UI๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
        return (
          <div style={{ width: '100%', maxWidth: '400px', margin: '50px auto' }}>

๐Ÿš€ Supabase ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…

        <Auth
          supabaseClient={supabase}
          appearance={{ theme: ThemeSupa }} // ๊ธฐ๋ณธ ํ…Œ๋งˆ ์‚ฌ์šฉ
          providers={['google', 'github', 'discord']} // ํ™œ์„ฑํ™”ํ•  ์†Œ์…œ ๋กœ๊ทธ์ธ ์„ ํƒ (์„ ํƒ ์‚ฌํ•ญ)
          localization={{
            variables: {
              sign_in: {
                email_label: '์ด๋ฉ”์ผ ์ฃผ์†Œ',
                password_label: '๋น„๋ฐ€๋ฒˆํ˜ธ',
                email_input_placeholder: '๋‹น์‹ ์˜ ์ด๋ฉ”์ผ',
                password_input_placeholder: '๋น„๋ฐ€๋ฒˆํ˜ธ',
                button_label: '๋กœ๊ทธ์ธ',
                social_provider_text: '๋‹ค์Œ์œผ๋กœ ๋กœ๊ทธ์ธ',
                link_text: '์ด๋ฏธ ๊ณ„์ •์ด ์žˆ์œผ์‹ ๊ฐ€์š”? ๋กœ๊ทธ์ธ',
              },
              sign_up: {
                email_label: '์ด๋ฉ”์ผ ์ฃผ์†Œ',
                password_label: '๋น„๋ฐ€๋ฒˆํ˜ธ',
                email_input_placeholder: '๋‹น์‹ ์˜ ์ด๋ฉ”์ผ',
                password_input_placeholder: '๋น„๋ฐ€๋ฒˆํ˜ธ',
                button_label: 'ํšŒ์›๊ฐ€์ž…',
                social_provider_text: '๋‹ค์Œ์œผ๋กœ ํšŒ์›๊ฐ€์ž…',
                link_text: '๊ณ„์ •์ด ์—†์œผ์‹ ๊ฐ€์š”? ํšŒ์›๊ฐ€์ž…',
              },
              // ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •, ์—…๋ฐ์ดํŠธ ๋“ฑ์˜ ํ…์ŠคํŠธ๋„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ
            },
          }}
        />
      </div>
    );
  } else {
    // ์„ธ์…˜์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
    return (
      <div style={{ width: '100%', maxWidth: '400px', margin: '50px auto', textAlign: 'center' }}>

ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰

๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ: {session.user.email}

        <button
          onClick={() => supabase.auth.signOut()}
          style={{
            padding: '10px 20px',
            backgroundColor: '#ef4444',
            color: 'white',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            marginTop: '20px'
          }}
        >
          ๋กœ๊ทธ์•„์›ƒ ๐Ÿšช
        </button>
      </div>
    );
  }
}

export default App;
```
  1. ์•ฑ ์‹คํ–‰:

    npm start

    ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์—ด๋ฆฌ๋ฉด์„œ http://localhost:3000์— Supabase ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… UI๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! โœจ

    ์ด๋ฉ”์ผ ์ฃผ์†Œ์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด, Supabase ๋Œ€์‹œ๋ณด๋“œ์˜ Authentication ํƒญ์— ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๊ฐ€ ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด๋ฉ”์ผ ํ™•์ธ์„ ํ™œ์„ฑํ™”ํ–ˆ๋‹ค๋ฉด, ํ•ด๋‹น ์ด๋ฉ”์ผ๋กœ ํ™•์ธ ๋ฉ”์ผ์ด ๋ฐœ์†ก๋ฉ๋‹ˆ๋‹ค.

    ์†Œ์š” ์‹œ๊ฐ„: 5๋ถ„! โฑ๏ธ ์–ด๋–ค๊ฐ€์š”? ์ด ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ ๋ช‡ ์ค„๋งŒ์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ์„ธ์…˜ ๊ด€๋ฆฌ, ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ๊นŒ์ง€ ๋ชจ๋‘ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค! ๋ฌผ๋ก  ์ด๋ฉ”์ผ ํ™•์ธ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋“ฑ์˜ ๊ธฐ๋Šฅ๋„ Auth ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค.


๐ŸŽจ (์˜ต์…˜) ์ˆ˜๋™์œผ๋กœ ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… UI ๊ตฌํ˜„ํ•˜๊ธฐ (๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด)

์œ„์˜ Auth ์ปดํฌ๋„ŒํŠธ๋Š” ๋งค์šฐ ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ๋””์ž์ธ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์— ์ œํ•œ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ข€ ๋” ์ปค์Šคํ„ฐ๋งˆ์ด์ง•๋œ UI๋ฅผ ์›ํ•œ๋‹ค๋ฉด, Supabase JS ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// src/components/AuthForm.js (์ƒˆ ํŒŒ์ผ ์ƒ์„ฑ)
import React, { useState } from 'react';
import { supabase } from '../supabaseClient'; // 3๋‹จ๊ณ„์—์„œ ๋งŒ๋“  supabaseClient ์ž„ํฌํŠธ

function AuthForm() {
  const [loading, setLoading] = useState(false);
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async (event) => {
    event.preventDefault();
    setLoading(true);
    const { error } = await supabase.auth.signInWithPassword({ email, password });

    if (error) alert(error.message);
    setLoading(false);
  };

  const handleSignup = async (event) => {
    event.preventDefault();
    setLoading(true);
    const { error } = await supabase.auth.signUp({ email, password });

    if (error) alert(error.message);
    else alert('ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต! ์ด๋ฉ”์ผ์„ ํ™•์ธํ•˜์—ฌ ์ธ์ฆ์„ ์™„๋ฃŒํ•ด์ฃผ์„ธ์š”.');
    setLoading(false);
  };

  return (
    <div style={{ maxWidth: '400px', margin: '50px auto', padding: '20px', border: '1px solid #ddd', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
      <h2 style={{ textAlign: 'center', marginBottom: '30px', color: '#333' }}>๋กœ๊ทธ์ธ ๋˜๋Š” ํšŒ์›๊ฐ€์ž…</h2>
      <form onSubmit={handleLogin}>
        <div style={{ marginBottom: '15px' }}>
          <label htmlFor="email" style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>์ด๋ฉ”์ผ:</label>
          <input
            id="email"
            type="email"
            placeholder="๋‹น์‹ ์˜ ์ด๋ฉ”์ผ ์ฃผ์†Œ"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px' }}
          />
        </div>
        <div style={{ marginBottom: '20px' }}>
          <label htmlFor="password" style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>๋น„๋ฐ€๋ฒˆํ˜ธ:</label>
          <input
            id="password"
            type="password"
            placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px' }}
          />
        </div>
        <button
          onClick={handleLogin}
          disabled={loading}
          style={{
            width: '100%',
            padding: '12px',
            backgroundColor: '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            fontSize: '16px',
            cursor: 'pointer',
            marginBottom: '10px'
          }}
        >
          {loading ? '๋กœ๊ทธ์ธ ์ค‘...' : '๋กœ๊ทธ์ธ'}
        </button>
        <button
          onClick={handleSignup}
          disabled={loading}
          style={{
            width: '100%',
            padding: '12px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            fontSize: '16px',
            cursor: 'pointer'
          }}
        >
          {loading ? 'ํšŒ์›๊ฐ€์ž… ์ค‘...' : 'ํšŒ์›๊ฐ€์ž…'}
        </button>
      </form>
    </div>
  );
}

export default AuthForm;

์ด์ œ App.js์—์„œ Auth ์ปดํฌ๋„ŒํŠธ ๋Œ€์‹  AuthForm์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// src/App.js
import React, { useState, useEffect } from 'react';
import { supabase } from './supabaseClient';
import AuthForm from './components/AuthForm'; // ์ƒˆ๋กœ ๋งŒ๋“  AuthForm ์ž„ํฌํŠธ

function App() {
  const [session, setSession] = useState(null);

  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session);
    });

    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session);
    });

    return () => subscription.unsubscribe();
  }, []);

  if (!session) {
    return <AuthForm />; // AuthForm ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง
  } else {
    return (
      <div style={{ width: '100%', maxWidth: '400px', margin: '50px auto', textAlign: 'center' }}>

<h2>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰</h2>

<p>๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ: <strong>{session.user.email}</strong></p>
        <button
          onClick={() => supabase.auth.signOut()}
          style={{
            padding: '10px 20px',
            backgroundColor: '#ef4444',
            color: 'white',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            marginTop: '20px'
          }}
        >
          ๋กœ๊ทธ์•„์›ƒ ๐Ÿšช
        </button>
      </div>
    );
  }
}

export default App;

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์›ํ•˜๋Š” ๋Œ€๋กœ ๋””์ž์ธ์„ ์ž์œ ๋กญ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ Supabase์˜ ๊ฐ•๋ ฅํ•œ ์ธ์ฆ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐ŸŒŸ ๋‹ค์Œ ๋‹จ๊ณ„: ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ๋„˜์–ด!

5๋ถ„ ๋งŒ์— ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ–ˆ์ง€๋งŒ, Supabase์˜ ์ง„์ •ํ•œ ํž˜์€ ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค!

  1. Row Level Security (RLS) ์„ค์ •:

    • ๊ฐ€์žฅ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค! Supabase ํ…Œ์ด๋ธ”์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณดํ˜ธํ•˜๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ RLS๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • Database -> Table Editor์—์„œ ํŠน์ • ํ…Œ์ด๋ธ”์„ ์„ ํƒํ•˜๊ณ  Policies ํƒญ์œผ๋กœ ์ด๋™ํ•˜์—ฌ RLS ์ •์ฑ…์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, “์‚ฌ์šฉ์ž ๋ณธ์ธ์˜ ํ”„๋กœํ•„๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ”๊ณผ ๊ฐ™์€ ์ •์ฑ…์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”’
  2. ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ํ…Œ์ด๋ธ” ์—ฐ๋™:

    • auth.users ํ…Œ์ด๋ธ”์€ Supabase๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์ธ์ฆ ์ •๋ณด์ž…๋‹ˆ๋‹ค.
    • ์‚ฌ์šฉ์ž ์ด๋ฆ„, ํ”„๋กœํ•„ ์‚ฌ์ง„, ๊ธฐํƒ€ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ public.profiles ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค๊ณ  auth.users ํ…Œ์ด๋ธ”์˜ id์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. (One-to-one ๊ด€๊ณ„) ๐Ÿง‘โ€๐Ÿ’ป
  3. ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋ฐ ๋ณ€๊ฒฝ:

    • Supabase ์ธ์ฆ์€ ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์ด๋ฉ”์ผ ์ „์†ก ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. supabase.auth.resetPasswordForEmail('user@example.com') ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ”‘
  4. ์†Œ์…œ ๋กœ๊ทธ์ธ ์—ฐ๋™:

    • Auth ์ปดํฌ๋„ŒํŠธ์˜ providers ์†์„ฑ์„ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜, supabase.auth.signInWithOAuth({ provider: 'google' })์™€ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์—ฌ Google, GitHub ๋“ฑ ๋‹ค์–‘ํ•œ ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐ŸŒ
  5. ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ํ™•์žฅ:

    • ์‹ค์‹œ๊ฐ„ ๊ตฌ๋…(Realtime): ์ฑ„ํŒ… ์•ฑ์ด๋‚˜ ์•Œ๋ฆผ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ’ฌ
    • ์Šคํ† ๋ฆฌ์ง€(Storage): ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์‚ฌ์ง„, ๊ฒŒ์‹œ๋ฌผ ์ด๋ฏธ์ง€ ๋“ฑ์„ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ–ผ๏ธ
    • ์—ฃ์ง€ ํ•จ์ˆ˜(Edge Functions): ๋ณต์žกํ•œ ๋ฐฑ์—”๋“œ ๋กœ์ง์ด๋‚˜ ์„œ๋“œํŒŒํ‹ฐ API ์—ฐ๋™์„ ์œ„ํ•œ ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜๋ฅผ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ’ก

โœจ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์˜ค๋Š˜ ์šฐ๋ฆฌ๋Š” Supabase๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ 5๋ถ„ ๋งŒ์— ๊ธฐ๋ณธ์ ์ธ ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋†€๋ผ์šด ๊ฒฝํ—˜์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ๋ฐฑ์—”๋“œ ์„ค์ • ์—†์ด๋„, Supabase์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ์ฃ .

Supabase๋Š” ๋‹จ์ˆœํ•œ Firebase ๋Œ€์•ˆ์ด ์•„๋‹ˆ๋ผ, ์˜คํ”ˆ์†Œ์Šค์˜ ์ด์ ์„ ์‚ด๋ฆฌ๋ฉด์„œ๋„ ํ˜„๋Œ€์ ์ธ ์›น/์•ฑ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐฑ์—”๋“œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์™„๋ฒฝํ•œ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ์ด์ œ ์ด ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ ๋ฉ‹์ง„ ์•„์ด๋””์–ด๋ฅผ ํ˜„์‹ค๋กœ ๋งŒ๋“ค์–ด ๋ณด์„ธ์š”! ๐Ÿš€

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ Supabase ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•ด๋ณด์„ธ์š”. ์ฆ๊ฑฐ์šด ์ฝ”๋”ฉ ๋˜์„ธ์š”! Happy Hacking! ๐Ÿ˜Š

๋‹ต๊ธ€ ๋‚จ๊ธฐ๊ธฐ

์ด๋ฉ”์ผ ์ฃผ์†Œ๋Š” ๊ณต๊ฐœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•„์ˆ˜ ํ•„๋“œ๋Š” *๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค