Session 2 추가자료 — Supabase 연동으로 동적 사이트 준비하기
1주차에서 만든 사이트에 "데이터베이스"를 붙여, 방문자가 글을 쓰면 저장되는 진짜 동적 사이트로 바꿉니다.
대상 세션: Session 2 — 데이터베이스 붙이기 & 마스터 프롬프트 설계 (추가자료) 대상 독자: 비개발자 입문자. 1주차 배포 미션을 끝내
vercel.app주소를 이미 받은 사람 기준. 화면 문구 안내: Supabase·Vercel·GitHub 웹 화면의 버튼 이름과 위치는 서비스 업데이트에 따라 달라질 수 있습니다. 이 문서의 버튼명은 참고용이며, 비슷한 뜻의 버튼을 찾으면 됩니다. 캡처로 대조하지 않은 항목은 확인 불가로 표기했습니다.
이 자료 쓰는 법
1주차는 "코드 안에 박아둔 가짜 데이터"만 보여줬습니다. 새로고침해도, 남이 접속해도 내용이 그대로였죠. 이번엔 Supabase(수파베이스)라는 인터넷 데이터베이스를 붙입니다. 그러면 누가 글을 쓰면 그게 저장되고, 다른 사람 화면에도 보입니다. 이게 "동적 사이트"입니다.
오늘의 순서는 이렇습니다.
- 웹에서 손으로 준비 — Supabase 프로젝트를 만들고, 열쇠(키 3개)를 챙기고, 저장할 표(테이블)를 만듭니다 (STEP 1 · STEP 2)
- 마스터 프롬프트 붙여넣기 — 코드로 연결하고, 실수로 열쇠가 새어나가지 않는지 점검하고, GitHub에 올리는 일은 Claude가 합니다 (STEP 3)
- 다시 웹에서 손으로 마무리 — Vercel에 열쇠를 등록하고 재배포, 그리고 데이터베이스 문단속(RLS)을 합니다 (STEP 4 · STEP 5)
코드는 여전히 몰라도 됩니다. 웹 화면 클릭(STEP 1·2·4·5)만 손으로 하고, 코드 작업(STEP 3)은 마스터 프롬프트가 Claude에게 시킵니다.
1주차와 달라진 핵심: 이번엔 "열쇠(비밀 키)"를 다룹니다. 열쇠를 잘못 두면 남이 내 데이터베이스를 통째로 열어볼 수 있습니다. 그래서 어떤 열쇠를 어디에 두느냐가 오늘 자료의 절반입니다. 겁먹을 필요는 없고, 규칙 몇 개만 지키면 됩니다.
목차
- 시작 전 준비
- 큰 그림 — 오늘 뭘 왜 하나
- STEP 1 — Supabase 프로젝트 만들고 키 챙기기
- STEP 2 — 게시판 테이블 만들기
- 🚀 여기부터 통째로 복사하세요 — 마스터 프롬프트
- 붙여넣은 다음 무슨 일이 벌어지나
- STEP 4 — Vercel 환경변수 등록하고 재배포하기
- STEP 5 — RLS 켜고 정책 좁히기
- 마무리 — 동적 사이트 개발 준비 완료
- 부록 A — 막힐 때 쓰는 보조 프롬프트
- 부록 B — 자주 나는 에러 & 해결
- 부록 C — 환경변수 보안 두 규칙
- 부록 D — GitHub push 전 점검 명령
- 부록 E — 완료 체크리스트
- 참고 링크
시작 전 준비
1주차에서 이어집니다
이 자료는 1주차에서 만든 my-dashboard 프로젝트를 그대로 이어서 씁니다. 새 폴더를 만들지 마세요.
세 가지가 준비돼 있어야 합니다.
| 항목 | 확인 방법 |
|---|---|
1주차 my-dashboard 폴더 | 바탕화면(또는 1주차에 만든 위치)에 폴더가 그대로 있는지 |
| GitHub 저장소 | 1주차에 올린 my-dashboard 저장소가 GitHub에 있는지 |
| Vercel 배포 | 1주차에 받은 https://...vercel.app 주소가 지금도 열리는지 |
1주차 폴더가 없거나 지웠다면, 이번 자료를 따라가기 어렵습니다. 먼저 Session 1 추가자료로 배포까지 끝내고 오세요.
새로 필요한 계정 1개 — Supabase
| 항목 | 하는 일 | 어디서 |
|---|---|---|
| Supabase 계정 | 인터넷에 데이터를 저장하는 창고(데이터베이스). 가입할 때 "Continue with GitHub" 를 고르면 편합니다 | supabase.com 에서 Sign up |
GitHub으로 가입하면 계정이 하나로 묶여 나중에 헷갈리지 않습니다. 이메일로 가입해도 됩니다.
Supabase 무료 요금제로 이번 실습은 충분합니다. 카드 등록을 요구하지 않습니다. (요금제 화면 구성은 버전에 따라 다를 수 있음 — 확인 불가)
폴더에서 터미널 열고 Claude 켜기 (1주차와 동일)
1주차와 똑같이, my-dashboard 폴더 안에서 터미널을 열고 claude 를 실행합니다. 방법이 기억나지 않으면 1주차 자료의 "폴더에서 터미널 열기"를 다시 보세요. 요점만 옮기면:
- Windows:
my-dashboard폴더를 열고 → 주소창 클릭 →cmd입력 → Enter → 검은 창 첫 줄이...\my-dashboard>로 끝나는지 확인 →claude - macOS: 터미널에서
cd ~/Desktop/my-dashboard→claude
주의: 1주차 이후 시간이 지났다면, Claude Code를 켰을 때 로그인·업데이트 안내가 다시 나올 수 있습니다. 나오는 안내를 그대로 따르면 됩니다. (문구·순서는 버전에 따라 다름 — 확인 불가)
큰 그림 — 오늘 뭘 왜 하나
낯선 낱말이 몇 개 나옵니다. 딱 이만큼만 알면 됩니다.
| 낱말 | 쉬운 뜻 |
|---|---|
| 데이터베이스(DB) | 인터넷에 있는 표 저장 창고. 방명록·게시판 글이 여기에 쌓입니다 |
| Supabase | 그 창고를 무료로 빌려주고, 웹 화면으로 관리하게 해주는 서비스 |
환경변수 / .env.local | 창고 열쇠(비밀 값)를 코드 밖에 따로 적어두는 쪽지. 코드에 직접 쓰면 GitHub에 그대로 노출되니까 분리합니다 |
| 키(key) | 창고를 여는 열쇠. 종류에 따라 공개해도 되는 것과 절대 안 되는 것이 있습니다(아래 표) |
| RLS | 창고 문에 다는 잠금장치. "누가 무엇을 읽고 쓸 수 있는지" 규칙을 정합니다. 안 달면 열쇠만 있으면 아무나 다 열 수 있습니다 |
오늘 다루는 열쇠 3개 — 이 구분이 오늘의 핵심입니다.
| 키 이름 | 성격 | 어디에 두나 | NEXT_PUBLIC_ 붙여도 되나 |
|---|---|---|---|
| Project URL | 내 창고 주소 | .env.local + Vercel | 붙여도 됨 (공개돼도 무방) |
| anon key (공개 키) | 브라우저에서 쓰라고 설계상 공개되는 열쇠 | .env.local + Vercel | 붙여도 됨 |
| service_role key (비밀 키) | 모든 잠금을 무시하는 마스터 열쇠 | 서버에서만. 이번 실습에선 안 씀 | 절대 붙이면 안 됨 |
⚠️ 오늘 가장 중요한 한 문장:
service_role키에NEXT_PUBLIC_을 붙이면 그 마스터 열쇠가 브라우저로 새어나가 데이터베이스 전체가 무방비가 됩니다. 왜 그런지는 부록 C에서 설명합니다. 이번 방명록 실습은service_role을 아예 쓰지 않으니, 실수할 일 자체를 만들지 않습니다.
오늘의 완주 그림:
STEP 1 Supabase 프로젝트 만들고 → 키 3개 확인 (웹, 손)
STEP 2 방명록 테이블 만들기 (웹, 손)
STEP 3 마스터 프롬프트 → 코드 연결·점검·GitHub 올리기 (Claude)
STEP 4 Vercel에 키 등록 + 재배포 (웹, 손)
STEP 5 RLS로 문단속 (웹, 손)
= 동적 사이트 개발 준비 완료STEP 1 — Supabase 프로젝트 만들고 키 챙기기
여기는 사람이 웹에서 직접 합니다. Claude가 대신 못 하는 구간입니다(내 계정으로 로그인해 버튼을 눌러야 하기 때문).
1-1. 프로젝트 만들기
- supabase.com 로그인 → New project(새 프로젝트) 클릭
- 팀/조직을 고르라고 하면 기본으로 만들어진 것을 선택
- 입력할 것
- Name(이름):
my-dashboard(아무 이름이나 됩니다) - Database Password(데이터베이스 비밀번호): 아무거나 강하게 정하고 메모장에 복사해 두세요. (이번 실습에서 직접 쓰진 않지만, 분실하면 곤란합니다)
- Region(지역): 가까운 곳(예: Northeast Asia (Seoul) / Tokyo 계열)을 고르면 빠릅니다. (선택지 이름은 버전에 따라 다를 수 있음 — 확인 불가)
- Name(이름):
- Create new project 클릭
- 1~2분 기다립니다. "Setting up project..." 같은 안내가 사라지고 대시보드가 뜨면 완료입니다
프로젝트가 준비되는 동안 화면이 멈춘 것처럼 보여도 정상입니다. 커피 한 모금 하고 오세요.
1-2. 키 3개 확인하기 — Settings > API
- 왼쪽 메뉴 맨 아래 Settings(톱니바퀴) → API 클릭
- 이 화면에 오늘 쓸 값이 다 있습니다. 세 가지를 찾으세요.
| 화면에서 찾을 것 | 우리가 쓸 이름 | 생김새 |
|---|---|---|
| Project URL | 창고 주소 | https://xxxxxxxx.supabase.co |
Project API keys → anon public | 공개 키 | eyJ... 로 시작하는 아주 긴 글자 |
Project API keys → service_role secret | 비밀 키 | 역시 eyJ... 로 시작하는 긴 글자. secret / Reveal(가리기 해제) 표시가 붙어 있음 |
화면 문구 주의: Supabase는 API 키 화면 구성을 개편한 적이 있어,
anon/service_role대신publishable/secret같은 다른 이름으로 보일 수 있습니다. 핵심 구분은 "브라우저에 공개해도 되는 키"와 "서버 전용 비밀 키" 두 종류라는 것입니다. 어떤 이름이든service_role(또는secret)이라고 적힌 쪽이 절대 공개하면 안 되는 마스터 열쇠입니다. 정확한 라벨은 버전에 따라 다름 — 확인 불가.
- Project URL 과 anon key 두 개를 메모장에 복사해 두세요. STEP 3에서 Claude가 물어봅니다.
- service_role key 는 이번 실습에서 쓰지 않습니다. 지금은 "이런 게 있고, 이건 절대 코드나 브라우저에 넣으면 안 되는 거구나" 정도만 알고 넘어가면 됩니다. 실수로 복사해서 아무 데나 붙여넣지 마세요.
왜 anon key는 복사해서 코드에 넣어도 되나요? Supabase는 anon key를 원래 브라우저에 공개하도록 설계했습니다. 이건 유출이 아니라 정상입니다. 진짜 방어선은 이 키가 아니라, STEP 5에서 켤 RLS(잠금장치) 입니다. 이 원리는 부록 C에 자세히 있습니다.
STEP 2 — 게시판 테이블 만들기
여기도 사람이 웹에서 직접 합니다. 글이 쌓일 표(테이블) 를 만듭니다. 이번엔 방명록(guestbook)을 만듭니다 — 이름과 한 줄 메시지를 남기는 간단한 게시판입니다.
가장 쉬운 방법은 SQL Editor(쿼리를 붙여넣는 화면)에 아래를 그대로 붙여넣는 것입니다. SQL을 몰라도 됩니다. 복사 → 붙여넣기 → 실행만 하면 됩니다.
- Supabase 왼쪽 메뉴에서 SQL Editor 클릭 → New query(새 쿼리)
- 아래를 통째로 붙여넣고 Run(실행, 보통 오른쪽 아래 또는
Ctrl/⌘ + Enter) 클릭
-- 방명록 테이블 만들기
create table guestbook (
id bigint generated always as identity primary key,
name text not null,
message text not null,
created_at timestamptz not null default now()
);
-- 잠금장치(RLS) 켜기: 이제부터 정책으로 허용한 것만 통과한다
alter table guestbook enable row level security;
-- 정책 1: 누구나 목록을 읽을 수 있다 (SELECT 허용)
create policy "anyone can read guestbook"
on guestbook for select
to anon
using (true);
-- 정책 2: 누구나 글을 남길 수 있다 (INSERT 허용) — 단 메시지가 비어 있으면 거부
create policy "anyone can insert guestbook"
on guestbook for insert
to anon
with check (char_length(message) > 0);
-- UPDATE / DELETE 정책은 일부러 만들지 않는다 = 기본 거부.
-- 남의 글 수정·삭제는 원천 차단된다.- "Success. No rows returned" 같은 성공 메시지가 나오면 됩니다
- 왼쪽 Table Editor(표 편집기)에 들어가
guestbook표가 보이면 성공입니다
방금 무슨 일이 벌어졌나요?
- 이름·메시지·작성시각을 담는 표를 만들었고,
- 그 표에 잠금장치(RLS)를 켜고,
- "읽기는 누구나 / 쓰기는 누구나(단 빈 메시지는 거부) / 수정·삭제는 아무도 못 함" 이라는 문 규칙(정책) 을 걸었습니다.
이게 STEP 5에서 자세히 설명할 RLS 정책입니다. 처음부터 안전하게 켜두고 시작하는 겁니다.
화면 문구 주의: 최근 Supabase는 새 테이블에 RLS를 기본으로 켜두는 경우가 많지만, 생성 경로·버전에 따라 다릅니다. 위 SQL에는
enable row level security를 명시해 뒀으니, 이미 켜져 있어도 문제되지 않습니다. 실제로 켜졌는지는 STEP 5에서 눈으로 확인합니다.
🚀 여기부터 통째로 복사하세요 — 마스터 프롬프트
여기가 STEP 3입니다. 지금까지 웹에서 만든 창고를 코드로 연결하고, 열쇠가 GitHub로 새어나가지 않는지 점검한 뒤, GitHub에 올리는 일을 Claude에게 시킵니다.
아래 코드블록 전체를 복사해서 Claude Code에 그대로 붙여넣으세요. 한 줄도 빼지 마세요.
바꿀 곳은 없습니다. Supabase 주소와 anon key는 Claude가 필요한 순간에 직접 물어봅니다. 그때 STEP 1에서 메모해둔 값을 붙여넣으면 됩니다.
아래 내용을 통째로 복사해 Claude Code에 붙여넣으세요. (114줄)
너는 지금부터 "비개발자 스터디 참가자의 Supabase 연동 코치"다.
나는 지난주에 Next.js 대시보드를 만들어 GitHub에 올리고 Vercel에 배포까지 끝낸 완전 초보다.
코드는 전혀 모른다. 코드 작업과 터미널 명령은 전부 네가 직접 하고, 나에게는 확인과 붙여넣기만 요청해라.
[지금 상황]
이 폴더는 지난주에 만든 my-dashboard 프로젝트다.
Next.js(App Router) + TypeScript + Tailwind 로 되어 있고, 이미 GitHub 저장소와 Vercel 배포가 연결돼 있다.
오늘은 여기에 Supabase(인터넷 데이터베이스)를 붙여서, 방문자가 글을 남기면 저장되는 "방명록" 기능을 추가한다.
[내가 이미 웹에서 해둔 것 — 다시 하라고 시키지 마라]
1. Supabase 프로젝트를 만들었다.
2. Settings > API 에서 Project URL 과 anon key 를 복사해 뒀다.
3. SQL Editor 에서 guestbook 테이블을 만들었다.
컬럼은 id / name(text) / message(text) / created_at(timestamptz) 이다.
RLS(행 수준 보안)를 켰고, anon 에게 SELECT 와 INSERT 만 허용하는 정책을 걸었다.
UPDATE/DELETE 정책은 일부러 만들지 않았다.
[말투 규칙]
1. 나는 완전 초보다. 전문용어를 최대한 쓰지 마라. 꼭 써야 하면 괄호로 한 줄 설명을 붙여라.
2. 영어 로그나 코드를 그대로 길게 붙여넣지 마라. 무슨 뜻인지 한국어로 두세 줄로 요약해라.
3. 내가 "모르겠어" 라고 하면 같은 설명을 반복하지 말고 더 쉬운 말과 비유로 다시 설명해라.
4. 나에게 질문할 때는 한 번에 하나만 물어라.
[진행 방식 규칙]
1. 반드시 한 번에 한 단계씩만 진행해라. 여러 단계를 몰아서 하지 마라.
2. 매 단계 첫 줄에 지금 어디쯤인지 알려줘라. 형식: "7단계 중 3단계: 방명록 화면 만들기"
3. 각 단계가 끝나면 무엇이 완료됐는지 한국어 두세 줄로 요약하고, "다음으로 갈까요?" 라고 물은 뒤 내 대답을 기다려라.
4. 내가 "됐어" 또는 "완료" 라고 말하기 전에는 다음 단계로 넘어가지 마라.
[누가 무엇을 하는가]
1. 터미널 명령과 코드 작성은 네가 해라. 나에게 명령어를 대신 치라고 시키지 마라.
2. 네가 명령을 실행하기 전에 "이 명령을 실행해도 되냐" 묻는 허용 창이 뜰 수 있다.
그런 게 뜰 것 같으면 미리 알려주고, 무슨 명령인지 한 줄로 쉽게 설명해라.
3. 비밀 값(키)을 물어볼 때는, 그 값이 어디에 저장되고 왜 안전한지 한 줄로 먼저 말해줘라.
[환경변수 규칙 — 오늘 가장 중요하다. 반드시 지켜라]
1. Supabase 접속에 필요한 값은 .env.local 파일에 저장하고, 코드에 직접 박아넣지 마라.
2. 브라우저에서 써야 하는 값 두 개에는 이름 앞에 NEXT_PUBLIC_ 을 붙여라.
- NEXT_PUBLIC_SUPABASE_URL (Project URL)
- NEXT_PUBLIC_SUPABASE_ANON_KEY (anon key)
3. service_role 키는 이번 방명록에 절대 쓰지 마라. 나에게 물어보지도 마라.
혹시라도 쓰게 되는 상황이 오면, 그 키에는 NEXT_PUBLIC_ 을 절대 붙이지 말고
SUPABASE_SERVICE_ROLE_KEY 라는 이름으로 서버 코드에서만 쓰겠다고 먼저 나에게 설명하고 허락을 받아라.
4. .env.local 은 절대 GitHub에 올라가면 안 된다. 아래 [올리기 전 점검]을 반드시 지켜라.
[이번에 만들 기능 — 방명록 한 화면]
- 새 페이지 경로: /guestbook (메인 페이지에서 이 페이지로 가는 링크도 하나 추가해라)
- 이 페이지에 아래를 배치
(1) 이름 입력칸 + 메시지 입력칸 + "남기기" 버튼
(2) 버튼을 누르면 guestbook 테이블에 INSERT 되고, 목록이 갱신된다
(3) 그 아래에 지금까지 남긴 글 목록을 최신순으로 보여준다 (이름 / 메시지 / 작성시각)
- Supabase 연결은 @supabase/supabase-js 패키지를 설치해서 쓴다.
- 읽기(SELECT)와 쓰기(INSERT)는 anon key 로 한다. 우리가 만든 RLS 정책이 이걸 허용한다.
- 디자인은 지난주 대시보드와 톤을 맞춰라(밝은 테마, 카드, 반응형, 한글).
- 빈 메시지는 보내지 못하게 화면에서도 막아라(테이블 정책도 빈 메시지를 거부한다).
[진행할 단계 — 전체 7단계, 이 순서대로]
1) 패키지 설치: @supabase/supabase-js 를 설치해라. 설치에 잠깐 걸릴 수 있다고 미리 알려줘라.
2) 환경변수 파일 만들기:
- .env.local 파일을 만들고, 위 [환경변수 규칙]대로 두 값의 자리를 준비해라.
- 나에게 Project URL 을 먼저 물어보고, 그다음 anon key 를 물어봐서 채워라. 한 번에 하나씩 물어라.
- 값을 다 넣은 뒤, 이 파일이 어떤 파일인지(창고 열쇠를 코드 밖에 적어둔 쪽지) 한 줄로 설명해라.
3) 접속 코드 작성: Supabase에 연결하는 클라이언트 코드를 만들어라.
환경변수가 비어 있으면 화면이 조용히 죽지 않고, 원인을 알 수 있게 처리해라.
4) 방명록 화면 만들기: 위 [이번에 만들 기능]대로 /guestbook 페이지와 메인 링크를 만들어라.
5) .gitignore 점검: .gitignore 파일에 .env* 줄이 있는지 확인하고, 없으면 추가해라.
이게 "실수로 올리면 안 되는 열쇠 파일을 걸러두는 목록"이라는 걸 쉬운 말로 설명해라.
6) 로컬에서 실제로 되는지 확인: 개발 서버를 실행해라.
화면에 출력된 Local 주소를 정확히 읽어서 나에게 알려주고, /guestbook 으로 들어가라고 안내해라.
내가 직접 이름과 메시지를 넣고 "남기기"를 눌러 목록에 글이 뜨는지 눈으로 확인하게 해라.
안 되면 원인을 찾아 네가 직접 고쳐라. 될 때까지는 다음으로 넘어가지 마라.
(참고: .env.local 값을 방금 넣었거나 바꿨으면 개발 서버를 한 번 껐다 켜야 반영된다. 필요하면 네가 재시작해라.)
7) 올리기 전 점검 후 GitHub에 올리기 — 아래 [올리기 전 점검]을 그대로 따라라.
[올리기 전 점검 — 7)단계에서 절대 건너뛰지 마라]
GitHub에 올리기 직전에, .env.local 이 커밋에 섞여 들어가지 않는지 반드시 검사해라.
1. git status 를 실행해 .env.local 이 "커밋할 목록"이나 "새 파일"로 잡히지 않는지 확인해라.
만약 .env.local 이 목록에 보이면, .gitignore 를 고쳐서 제외되게 만든 뒤 다시 확인해라.
2. git ls-files 로 이미 추적 중인 파일 목록을 보고, 거기에 .env.local 이 없는지 확인해라.
혹시 지난주 작업에서 .env 류가 이미 올라가 있으면, git rm --cached 로 추적에서 빼고 커밋해라.
그리고 "GitHub 이력에 한 번 올라간 키는 파일을 지워도 이력에 남으니, 그런 경우엔 그 키를 폐기하고
새로 발급해야 한다"는 점을 나에게 반드시 알려줘라.
3. .env.local 이 안전하게 제외된 걸 확인한 뒤에만, 변경사항을 커밋하고 origin main 으로 push 해라.
push 할 때 지난주처럼 로그인 창이 뜰 수 있으면 미리 알려줘라.
4. push가 끝나면, GitHub 저장소에 .env.local 이 올라가지 않았는지 다시 한번 확인하고 결과를 알려줘라.
[에러 대응 규칙]
에러가 나면 나에게 영어 에러를 해석시키지 마라. 네가 원인을 찾아 직접 고치고,
무엇이 문제였고 어떻게 고쳤는지 쉬운 한국어 한 줄로만 알려줘라.
특히 "fetch failed", "supabaseUrl is required", "Invalid API key" 류가 나오면
환경변수(.env.local)나 키 값을 먼저 의심하고, 개발 서버 재시작이 필요한지도 점검해라.
같은 에러가 두 번 반복되면 다른 방법을 시도하고 그 사실을 나에게 말해줘라.
[보안 규칙]
- .gitignore 에 .env* 가 반드시 포함돼야 한다.
- 어떤 키도 코드 파일에 직접 박지 마라. 전부 .env.local 을 거쳐라.
- service_role 키에는 절대 NEXT_PUBLIC_ 을 붙이지 마라. 이번 실습에선 service_role 자체를 쓰지 마라.
- 방명록에 들어가는 데이터는 테스트용 가짜여도 된다. 실제 개인정보는 넣지 마라.
[마무리]
모든 단계가 끝나면, 아직 남은 두 가지가 있다는 걸 나에게 알려줘라.
"이제 Vercel에 환경변수를 등록하고 재배포해야 배포된 사이트에서도 방명록이 작동한다. 그리고 RLS 문단속을 최종 확인해야 한다.
이 두 가지는 웹 화면에서 사람이 직접 하는 작업이라, 추가자료의 STEP 4와 STEP 5를 보고 진행하면 된다."
그런 다음 아래 양식을 채워서 출력해줘라. 내가 모르는 항목은 나에게 물어봐라.
[Session 2 연동 상태]
- 설치한 패키지:
- 만든 페이지 경로:
- .env.local 에 넣은 변수 이름(값 말고 이름만):
- .env.local 이 GitHub에서 제외됐는지:
- 로컬에서 방명록 읽기/쓰기 성공 여부:
- 남은 작업: Vercel 환경변수 등록 + 재배포 / RLS 최종 확인
준비됐으면 1)번 단계부터 시작해라.붙여넣고 Enter를 치면 Claude가 "7단계 중 1단계"부터 시작합니다.
키를 물어보면 STEP 1에서 메모해둔 Project URL 과 anon key 를 붙여넣으세요. 이 값들은 내 컴퓨터의
.env.local파일에만 저장되고, GitHub에는 올라가지 않습니다(그걸 7단계에서 검사합니다).service_role 키는 물어보지 않습니다. 만약 Claude가 service_role 을 요구하면 뭔가 잘못된 것이니 "이번엔 service_role 쓰지 마"라고 말하세요.
확인 창("이 명령을 실행해도 될까요?")이 뜨면 허용하세요. 패키지 설치·파일 생성·git 명령에 필요합니다. (창 문구는 버전에 따라 다를 수 있음 — 확인 불가)
붙여넣은 다음 무슨 일이 벌어지나
| 단계 | Claude가 하는 일 | 내가 하는 일 |
|---|---|---|
| 웹 준비 (사람이 함) | — | STEP 1 Supabase 프로젝트+키 / STEP 2 테이블 생성 |
| 1 패키지 설치 | @supabase/supabase-js 설치 | 명령 허용 클릭, 기다리기 |
| 2 환경변수 | .env.local 생성 후 값 물어봄 | Project URL·anon key 붙여넣기 (한 번에 하나씩) |
| 3 접속 코드 | Supabase 연결 코드 작성 | 없음 |
| 4 방명록 화면 | /guestbook 페이지 + 메인 링크 작성 | 없음 |
5 .gitignore | .env* 들어있는지 확인·보완 | 없음 |
| 6 로컬 확인 | 개발 서버 실행 + 접속 주소 안내 | 직접 글 남겨보고 목록에 뜨는지 눈으로 확인 |
| 7 올리기 전 점검 + push | git status·git ls-files 로 .env.local 제외 확인 후 커밋·push | 로그인 창 뜨면 처리(1주차와 동일) |
| 웹 마무리 (사람이 함) | — | STEP 4 Vercel 키 등록+재배포 / STEP 5 RLS 확인 |
여기서 끝이 아닙니다. 로컬(내 컴퓨터)에서는 방명록이 되지만, 배포된
vercel.app주소에서는 아직 안 됩니다. 이유는 다음 문단에 있습니다. 반드시 STEP 4를 하세요.
⚠️ 실제로 겪은 함정 — 꼭 읽으세요. Vercel에 열쇠(환경변수)를 등록하지 않은 채로 배포하면, 홈 화면 같은 정적 페이지는 멀쩡히 뜹니다. 그래서 "다 됐네" 하고 착각하기 쉽습니다. 그런데 방명록처럼 데이터베이스를 쓰는 기능만 통째로 죽습니다. 실제 이 스터디 사이트에서도 데이터를 불러오는 API가
HTTP 500 fetch failed를 내며 실패했습니다. 화면이 떠서 다 된 줄 알았는데 게시판만 안 되는 전형적인 증상입니다. 원인은 딱 두 가지 — Vercel에 키를 안 넣었거나, 넣고도 재배포를 안 했거나. STEP 4가 이걸 막습니다.
STEP 4 — Vercel 환경변수 등록하고 재배포하기
사람이 웹에서 직접 합니다. 내 컴퓨터의 .env.local 값을 Vercel에도 똑같이 알려줘야 배포된 사이트가 창고에 접속할 수 있습니다.
4-1. 왜 이 단계가 꼭 필요한가
.env.local 은 내 컴퓨터에만 있는 파일입니다. GitHub에도 안 올라갔죠(그게 정상입니다). 그래서 Vercel은 이 값을 모릅니다. 따로 등록해줘야 합니다.
4-2. 등록하기
- vercel.com 로그인 →
my-dashboard프로젝트 클릭 - 위쪽 Settings → 왼쪽 Environment Variables(환경 변수) 클릭
- 아래 두 개를 하나씩 추가합니다. 각 항목에 Key(이름) 칸과 Value(값) 칸이 있습니다.
| Key (이름) | Value (값) |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | STEP 1의 Project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | STEP 1의 anon key |
- 이름은 한 글자도 틀리면 안 됩니다.
.env.local에 있는 이름과 똑같아야 합니다. Claude에게 ".env.local에 있는 변수 이름을 그대로 알려줘"라고 물어 복사해도 됩니다. - 적용 범위(Environment)를 고르는 칸이 있으면 Production / Preview / Development 모두 체크되게 두세요. (라벨은 버전에 따라 다를 수 있음 — 확인 불가)
- service_role 키는 등록하지 마세요. 이번 실습엔 쓰지 않습니다.
- 각각 Save(저장) 클릭
4-3. ⚠️ 가장 중요 — 재배포(Redeploy) 하기
NEXT_PUBLIC_로 시작하는 값은 "빌드(배포 준비) 시점에 코드 안에 구워집니다." 쉽게 말하면, 배포할 때 그 값을 통째로 복사해서 사이트에 박아버립니다. 그래서 키를 등록·변경만 하고 재배포를 안 하면, 이미 만들어진 사이트에는 예전 상태(키가 없던 상태) 그대로 남아 계속 깨져 있습니다. 등록했는데 왜 안 되냐는 착각이 여기서 나옵니다.
그래서 등록 뒤에는 반드시 재배포합니다.
- 상단 Deployments(배포) 탭 클릭
- 맨 위(가장 최근) 배포 항목의 오른쪽 ⋯(점 세 개) 메뉴 → Redeploy(재배포) 클릭
- 확인 창이 뜨면 그대로 Redeploy — 1~3분 기다립니다
재배포 대신, GitHub에 아무 커밋이나 새로 push해도 Vercel이 자동으로 다시 배포하면서 새 환경변수를 반영합니다. 둘 중 편한 방법을 쓰세요. 핵심은 "등록 후 한 번은 다시 배포된다" 는 것입니다.
4-4. 등록·반영됐는지 확인하기
- Settings > Environment Variables 화면에 두 변수가 보이면 등록된 것입니다(값은 가려져 보일 수 있음 — 정상).
- 재배포가 끝난 뒤, 내
vercel.app/guestbook주소에 들어가 글을 남겨보세요. 목록에 뜨고, 새로고침해도 남아 있고, 휴대폰에서 열어도 보이면 성공입니다. 이제 진짜 인터넷 창고에 저장되는 겁니다. - 여전히 방명록만 안 된다면 → 부록 B의 "배포는 됐는데 방명록만 죽음" 항목을 보세요. 대개 이름 오타 아니면 재배포 누락입니다.
STEP 5 — RLS 켜고 정책 좁히기
사람이 웹에서 확인·조정합니다. 우리는 STEP 2에서 이미 RLS를 켜고 정책을 걸었습니다. 이 단계는 왜 그게 필요했는지 이해하고, 실제로 켜져 있는지 눈으로 확인하는 단계입니다.
5-1. RLS를 안 켜면 무슨 일이 벌어지나
앞서 anon key는 브라우저에 공개돼도 되는 키라고 했습니다. 그럼 궁금해집니다 — "공개된 열쇠가 있으면 아무나 내 데이터를 열어보는 거 아냐?"
RLS(잠금장치)를 안 켜면, 정확히 그런 일이 벌어집니다. 공개된 anon key만 알면, 웹사이트 화면을 거치지 않고도 창고 주소로 직접 요청을 보내(https://xxxx.supabase.co/rest/v1/테이블이름 형태) 인증 없이 데이터를 통째로 읽거나 쓸 수 있습니다. 실제로 이 원리로 보안 점검을 해봤고, RLS가 꺼진 표는 그대로 뚫렸습니다.
RLS를 켜고 정책을 좁히면 그게 막힙니다. "읽기는 허용하되 쓰기는 조건부, 수정·삭제는 금지" 같은 규칙이 문 앞에서 요청을 걸러내기 때문입니다.
핵심 정리: anon key가 브라우저에 보이는 것 자체는 취약점이 아닙니다(설계상 공개용). 진짜 방어선은 RLS입니다. 그래서 우리는 STEP 2에서 테이블을 만들자마자 RLS부터 켰습니다.
5-2. RLS가 켜져 있는지 눈으로 확인
- Supabase → Table Editor →
guestbook표 선택 - 표 이름 근처에 "RLS enabled"(RLS 켜짐) 같은 표시가 있는지 확인. 또는 왼쪽 Authentication → Policies(정책) 화면에서
guestbook에 정책 2개(읽기/쓰기)가 있는지 확인- (화면 위치·라벨은 버전에 따라 다를 수 있음 — 확인 불가. "RLS", "Policies" 같은 단어를 찾으면 됩니다)
- 만약 RLS가 꺼져 있거나 정책이 안 보이면, STEP 2의 SQL을 SQL Editor에서 다시 실행하세요(이미 있으면 이름 중복 오류가 날 수 있는데, 그건 이미 만들어졌다는 뜻입니다).
5-3. 우리 정책이 하는 일 — 개념만
| 작업 | 허용? | 이유 |
|---|---|---|
| 목록 읽기 (SELECT) | ✅ 누구나 | 방명록은 원래 공개해서 보는 것 |
| 글 남기기 (INSERT) | ✅ 누구나, 단 빈 메시지는 거부 | 아무나 남기되, 최소한의 조건은 검사 |
| 남의 글 수정 (UPDATE) | ❌ 아무도 못 함 | 정책을 안 만들었다 = 기본 거부 |
| 글 삭제 (DELETE) | ❌ 아무도 못 함 | 마찬가지로 기본 거부 |
정책의 기본값은 "거부"입니다. RLS를 켜면, 명시적으로 허용한 것 외에는 전부 막힙니다. 그래서 UPDATE/DELETE 정책을 "일부러 안 만든 것"만으로 수정·삭제가 원천 차단됩니다.
5-4. 그럼 관리자는 어떻게 지우나 — service_role의 자리
방명록에서 스팸 글을 지워야 할 때가 옵니다. 하지만 우리는 DELETE를 아무에게도 허용하지 않았죠. 이럴 때 쓰는 게 service_role 키입니다.
- service_role 키는 RLS(잠금장치)를 무시하는 마스터 열쇠입니다. 그래서 정책과 상관없이 뭐든 할 수 있습니다.
- 바로 그 이유 때문에 절대 브라우저에 두면 안 됩니다. service_role 은 서버(사람 눈에 안 보이는 뒤편)에서 실행되는 코드에서만 써야 합니다. Next.js에서는 이걸 "서버 라우트" 또는 "서버 액션"이라고 부릅니다.
- 즉 삭제 같은 민감한 작업은 "브라우저 → 우리 서버 코드(service_role 사용) → Supabase" 경로로만 처리합니다. 이번 실습 범위를 넘으니 개념만 알아두세요. 필요해지면 Claude에게 "삭제 기능을 service_role 서버 라우트로 만들어줘. 키에는 절대 NEXT_PUBLIC_ 붙이지 마"라고 요청하면 됩니다.
다시 강조: service_role 에
NEXT_PUBLIC_을 붙이는 순간 그 마스터 열쇠가 브라우저 코드에 실려 전 세계에 공개됩니다. 그러면 RLS를 아무리 잘 짜도 소용없습니다 — 마스터 열쇠는 잠금장치를 무시하니까요. 부록 C 참고.
마무리 — 동적 사이트 개발 준비 완료
여기까지 오면, 지난주의 "가짜 데이터 사이트"가 진짜 동적 사이트로 바뀌었습니다.
오늘 확보한 4가지:
- Supabase 연동 — 프로젝트 생성 → 키 확보 →
.env.local연결 → 테이블 읽기/쓰기까지 완성 - 안전한 GitHub 업로드 — 열쇠(
.env.local)가 저장소에 새어나가지 않도록 점검하는 습관 - Vercel 환경변수 + 재배포 —
NEXT_PUBLIC_값은 빌드 때 구워지므로, 등록 뒤 반드시 재배포해야 반영된다는 것 - RLS 문단속 — 공개 키가 아니라 정책이 진짜 방어선이라는 것, 그리고 마스터 키(service_role)의 자리
이제 여러분의 사이트는 데이터를 저장하고, 불러오고, 여러 사람이 함께 쓰는 사이트가 되었습니다. 여기에 로그인·대시보드·게시판 무엇을 붙이든, 오늘 배운 "키를 어디에 두고, 문을 어떻게 잠그느냐" 가 그대로 적용됩니다. 동적 사이트 개발 준비가 끝났습니다.
부록 A — 막힐 때 쓰는 보조 프롬프트
마스터 프롬프트로 진행하다 흐름이 끊기면 아래를 골라 붙여넣으세요.
A-1. 방명록이 로컬에서 안 될 때 (만능)
방명록이 화면에서 작동하지 않아. 나는 비개발자라 에러를 해석하지 못해.
먼저 .env.local 에 NEXT_PUBLIC_SUPABASE_URL 과 NEXT_PUBLIC_SUPABASE_ANON_KEY 가 제대로 들어 있는지,
값에 공백이나 줄바꿈이 섞이지 않았는지 확인해줘.
그다음 개발 서버를 껐다 켜서 환경변수를 다시 읽게 하고, 원인을 찾아 직접 고쳐줘.
무엇이 문제였고 어떻게 고쳤는지 한 줄로만 알려줘.A-2. .env.local 이 실수로 GitHub에 올라갔을 때
.env.local(또는 다른 키가 든 파일)이 GitHub에 올라간 것 같아.
git ls-files 로 정말 올라가 있는지 확인하고, 올라가 있으면 git rm --cached 로 추적에서 빼고
.gitignore 에 .env* 가 있는지도 확인해서 없으면 추가한 뒤 커밋하고 push 해줘.
그리고 이미 이력에 남은 키는 어떻게 처리해야 하는지(폐기·재발급) 한국어로 알려줘.A-3. 배포된 사이트에서만 방명록이 안 될 때
내 컴퓨터에서는 방명록이 되는데, 배포된 vercel.app 주소에서는 안 돼.
이건 Vercel 환경변수 문제일 가능성이 높다고 들었어.
내가 Vercel에 등록해야 할 환경변수 이름을 .env.local 기준으로 정확히 알려주고,
등록한 뒤 왜 재배포를 꼭 해야 하는지도 한 줄로 설명해줘.A-4. 대화가 끊겨서 다시 이어서 할 때
아까 하던 Supabase 방명록 연동을 이어서 하고 싶어.
지금 이 폴더의 상태(@supabase/supabase-js 설치 여부, .env.local 존재 여부,
/guestbook 페이지 존재 여부, 마지막 커밋·push 여부)를 먼저 확인하고,
몇 번째 단계까지 됐는지 알려준 다음 그다음 단계부터 이어서 진행해줘.부록 B — 자주 나는 에러 & 해결
| # | 증상 (화면에 뜨는 말) | 원인 | 해결 |
|---|---|---|---|
| 1 | Error: supabaseUrl is required / supabaseKey is required | .env.local 이 없거나 변수 이름이 틀림. 또는 값을 넣고 개발 서버를 재시작 안 함 | .env.local 에 NEXT_PUBLIC_SUPABASE_URL·NEXT_PUBLIC_SUPABASE_ANON_KEY 가 정확히 있는지 확인 → 개발 서버를 껐다 켜기(Next.js는 시작할 때 환경변수를 읽음) |
| 2 | Invalid API key / 401 | anon key가 틀렸거나 복사할 때 일부 빠짐 | Supabase Settings > API 에서 anon key를 다시 통째로 복사해 .env.local 에 붙여넣고 서버 재시작 |
| 3 | 목록이 빈 채로 뜨고 에러도 없음 | RLS는 켜졌는데 SELECT(읽기) 정책이 없음 → 조용히 0건 반환 | STEP 2의 "anyone can read" 정책이 있는지 확인. 없으면 SQL 다시 실행 → STEP 5 |
| 4 | 글 남기기가 실패 / new row violates row-level security policy | INSERT 정책이 없거나, 조건(빈 메시지 거부)에 걸림 | "anyone can insert" 정책 확인. 메시지를 비우지 말고 입력 → STEP 5 |
| 5 | 배포는 됐는데 방명록만 죽음 / fetch failed / 500 | Vercel에 환경변수 미등록, 또는 등록 후 재배포 안 함 (실제로 겪은 대표 함정) | STEP 4대로 두 변수 등록 → Redeploy. 이름 오타 여부도 확인 |
| 6 | Vercel에 등록했는데도 그대로 안 됨 | NEXT_PUBLIC_ 값은 빌드 때 구워지므로, 등록·변경만 하고 재배포를 안 하면 반영 안 됨 | Deployments → 최신 배포 → Redeploy. 또는 아무 커밋이나 push해서 자동 재배포 유도 |
| 7 | 변수 이름이 Vercel과 코드에서 다름 | Vercel Key와 .env.local 변수명이 한 글자라도 다름 | 둘을 똑같이 맞춤. Claude에게 ".env.local 변수 이름 그대로 알려줘"라고 물어 복사 |
| 8 | .env.local 이 GitHub에 올라감 | .gitignore 에 .env* 가 없었음 | .gitignore 에 .env* 추가 → git rm --cached .env.local → 커밋·push. 그 안에 있던 키는 반드시 Supabase에서 폐기하고 새로 발급 → 부록 D |
| 9 | 로컬에서 값 바꿨는데 화면에 반영 안 됨 | Next.js는 서버 시작 시점에 환경변수를 읽음 | 개발 서버를 Ctrl + C 로 끄고 다시 실행 |
| 10 | Module not found: @supabase/supabase-js | 패키지 설치가 안 됐거나 실패함 | Claude에게 "@supabase/supabase-js 를 다시 설치해줘"라고 요청 |
원칙: 이 표를 외울 필요 없습니다. 대부분은 부록 A 프롬프트를 붙여넣으면 해결됩니다. 이 표는 Claude가 못 고치고 반복될 때 참고용입니다.
부록 C — 환경변수 보안 두 규칙
오늘 자료 전체가 결국 이 두 규칙으로 압축됩니다.
규칙 1 — NEXT_PUBLIC_ 은 "브라우저에 공개해도 되는 값"에만 붙인다
NEXT_PUBLIC_ 이 붙은 변수는 빌드(배포 준비) 때 코드에 통째로 구워져 브라우저로 전달됩니다. 즉 사이트를 여는 누구나 그 값을 볼 수 있습니다. 두 가지 결과가 따라옵니다.
- 결과 A (기능): Vercel에서 이 값을 등록·변경하면 재배포해야만 반영됩니다. 이미 구워진 사이트는 안 바뀝니다. → STEP 4
- 결과 B (보안): 공개돼도 되는 값에만 붙여야 합니다. Supabase Project URL·anon key는 원래 공개용이라 괜찮습니다.
| 값 | NEXT_PUBLIC_ | 이유 |
|---|---|---|
| Project URL | ✅ | 창고 주소 자체는 비밀이 아님 |
| anon key | ✅ | Supabase가 브라우저 공개용으로 설계한 키 |
| service_role key | ❌ 절대 금지 | 잠금장치(RLS)를 무시하는 마스터 열쇠 |
규칙 2 — service_role 키는 브라우저 근처에도 두지 않는다
service_role 키에 NEXT_PUBLIC_ 을 붙이는 순간, 그 마스터 열쇠가 브라우저 번들에 실려 전 세계에 공개됩니다. 그러면:
- 누구나 그 키로 RLS를 무시하고 데이터베이스 전체를 읽고, 고치고, 지울 수 있습니다.
- RLS 정책을 아무리 잘 짜도 소용없습니다. 마스터 열쇠는 잠금장치를 우회하니까요.
그래서 service_role 은 반드시 서버 전용으로만 씁니다. 변수 이름도 SUPABASE_SERVICE_ROLE_KEY 처럼 NEXT_PUBLIC_ 없이 짓습니다. 이번 방명록 실습은 service_role 을 아예 쓰지 않아, 이 실수를 할 여지조차 없습니다.
덤 — 키 말고도 새어나가는 것
.env 만 조심하면 되는 게 아닙니다. 내부 설정·로그 파일에 이메일 주소나 조직 ID 같은 정보가 들어 있는 경우, 그것도 공개 저장소에 그대로 올라갈 수 있습니다. push 하기 전에 커밋 목록을 눈으로 한 번 훑는 습관을 들이세요(부록 D). 특히 Public 저장소는 한 번 올라간 것이 커밋 이력에 영구히 남아, 파일을 지워도 이력에서 되살릴 수 있습니다. 키가 이렇게 노출됐다면 답은 하나 — 그 키를 폐기(revoke)하고 새로 발급하는 것뿐입니다.
부록 D — GitHub push 전 점검 명령
마스터 프롬프트를 쓰면 Claude가 알아서 검사합니다. 직접 손으로 확인하고 싶은 경우에만 참고하세요.
D-1. .gitignore 에 .env* 가 있는지 확인
.gitignore 파일을 열어 아래 줄이 있는지 봅니다(1주차 배포 미션에서 이미 들어갔을 수 있습니다).
node_modules
.next
.env*.env* 는 .env, .env.local 등 .env 로 시작하는 모든 파일을 GitHub에서 제외한다는 뜻입니다.
D-2. .env.local 이 커밋에 안 섞였는지 확인
Windows (PowerShell)
git status
git ls-files | Select-String envmacOS
git status
git ls-files | grep envgit status결과에.env.local이 보이면 안 됩니다. 보인다면.gitignore가 제대로 적용되지 않은 것입니다.git ls-files는 이미 GitHub 추적 대상인 파일 목록입니다. 여기에.env.local이 나오면 안 됩니다. 아무것도 안 나오면 안전한 것입니다.
D-3. 이미 실수로 올렸을 때 되돌리기
git rm --cached .env.local
git commit -m "remove .env.local from repo"
git pushgit rm --cached는 파일은 내 컴퓨터에 남기고, GitHub 추적에서만 빼는 명령입니다.- 하지만 이것으로 끝이 아닙니다. Public 저장소라면 과거 커밋 이력에 값이 그대로 남습니다. 그러므로 노출된 키는 반드시 Supabase에서 폐기하고 새로 발급하세요. 파일을 지운 것과 키가 안전해진 것은 별개입니다.
원칙: 위 명령을 외울 필요 없습니다. 부록 A-2 프롬프트를 붙여넣으면 Claude가 확인부터 되돌리기까지 해줍니다.
부록 E — 완료 체크리스트
준비
- 1주차
my-dashboard폴더·GitHub 저장소·Vercel 배포가 그대로 있음 - Supabase 계정 생성 완료 (GitHub 연동 권장)
STEP 1 — Supabase 프로젝트 (웹, 손)
- New project 로 프로젝트 생성 (Database Password 메모)
- Settings > API 에서 Project URL 확인·복사
- anon key 확인·복사
- service_role key 는 "절대 공개 금지 키"임을 인지 (이번엔 복사·사용 안 함)
STEP 2 — 테이블 (웹, 손)
- SQL Editor 에서
guestbook생성 SQL 실행 (RLS·정책 포함) - Table Editor 에
guestbook표가 보임
STEP 3 — 마스터 프롬프트 (Claude)
-
@supabase/supabase-js설치됨 -
.env.local에NEXT_PUBLIC_SUPABASE_URL·NEXT_PUBLIC_SUPABASE_ANON_KEY들어감 -
/guestbook페이지 + 메인 링크 생성됨 -
.gitignore에.env*포함 확인 - 로컬에서 글을 남기면 목록에 뜨고 새로고침해도 남아 있음
- push 전에
git status·git ls-files로.env.local제외 확인 - 커밋·push 성공, GitHub에
.env.local없음 확인
STEP 4 — Vercel 환경변수 (웹, 손)
- Settings > Environment Variables 에 두 변수 등록 (이름 정확히 일치)
- Redeploy(재배포) 실행 ← 이걸 해야 반영됨
- 배포된
vercel.app/guestbook에서 글 남기기·목록 뜸 (휴대폰에서도 확인)
STEP 5 — RLS (웹, 손)
-
guestbook에 RLS 켜짐 확인 - 읽기/쓰기 정책 존재, 수정/삭제 정책 없음(기본 거부) 확인
- service_role 은 서버 전용이며
NEXT_PUBLIC_을 붙이면 안 된다는 것 이해
마무리
- "동적 사이트 개발 준비 완료" — 저장·조회·다중 사용자까지 되는 사이트 확보
참고 링크
- Supabase 공식 문서
- Supabase — Row Level Security(RLS) 가이드
- Vercel — 환경변수(Environment Variables) 문서
- Next.js — 환경변수 설정 문서
- Session 1 추가자료 — GitHub + Vercel 배포 미션