← 블로그 목록

Supabase RLS 때문에 데이터가 안 보일 때, 비개발자 체크 순서

Supabase에는 분명히 데이터가 들어가 있는데 화면에는 빈 배열만 떠 있다면, 거의 RLS(행 수준 보안) 정책이 막고 있는 경우입니다. 정책을 정리하는 순서를 비개발자 시점에서 풀어봤습니다.

Supabase 대시보드를 열면 테이블에는 데이터가 분명히 들어가 있습니다. 그런데 사이트의 화면에는 빈 목록만 떠 있습니다. 콘솔에 에러가 찍히지도 않고, 그냥 데이터가 0건이라는 응답만 돌아옵니다.

이런 증상이라면 거의 100% RLS가 원인입니다.

RLS는 Row Level Security의 줄임말로, "이 사용자가 이 행을 볼 수 있는가"를 행 단위로 통제하는 기능입니다. Supabase는 보안을 위해 모든 테이블에 RLS를 켜는 걸 권장하기 때문에, AI가 만든 프로젝트에도 기본으로 켜져 있는 경우가 많습니다.

문제는, 켜져 있는데 정책이 비어 있으면 모든 요청이 막힌다는 점입니다.

먼저 RLS가 켜져 있는지 확인

Supabase 대시보드에서 해당 테이블을 누르고 우측 상단의 Authentication 또는 RLS 표시를 확인하세요.

  • "RLS enabled" 라면 정책이 적용되고 있는 상태입니다
  • "RLS disabled" 라면 그 테이블은 누구나 읽고 쓸 수 있는 상태입니다

RLS가 켜져 있는데 정책(Policy)이 하나도 등록되어 있지 않다면, 일반 사용자는 그 테이블에서 아무것도 읽을 수 없습니다. 데이터가 있어도 select 결과는 빈 배열로 돌아옵니다.

AI가 처음 테이블을 만들 때 RLS만 켜놓고 정책은 빼먹는 경우가 흔합니다. "보안을 위해 RLS를 활성화했습니다" 까지만 하고 끝나는 거죠. 그럼 코드는 멀쩡한데 데이터가 안 나옵니다.

Service Role Key vs Anon Key

다음으로 어떤 키로 Supabase에 접속하고 있는지를 봐야 합니다.

Supabase에는 키가 두 종류 있습니다.

  • anon key: 일반 사용자(브라우저)에서 사용하는 키. RLS가 적용된다.
  • service role key: 서버에서만 쓰는 관리자 키. RLS를 무시하고 모든 데이터를 읽고 쓸 수 있다.

Supabase 대시보드의 SQL Editor에서 select가 잘 되는데 사이트에서는 빈 배열이 나온다면, 대시보드는 service role 권한으로 동작하지만 사이트는 anon 키로 동작하기 때문입니다.

중요한 두 가지:

  • 일반 사용자 화면에서 service role key를 쓰면 절대 안 됩니다 (모든 데이터가 노출됩니다)
  • 그러니 화면 데이터를 anon key로 잘 가져오려면 RLS 정책이 필요합니다

"왜 SQL Editor에서는 되는데 사이트에서는 안 보이지?" 라는 질문은 거의 이 차이에서 출발합니다.

최소 정책 한 줄 만들기

읽기 전용 테이블이고, 누구나 읽어도 되는 데이터(예: 공개 게시글, 상품 목록)라면 정책 하나면 충분합니다.

Supabase 대시보드의 Authentication → Policies 메뉴에서 "New Policy"를 누르고 다음과 같이 만듭니다.

  • Policy name: Allow public read
  • Allowed operation: SELECT
  • Target roles: anon (그리고 authenticated도 같이)
  • Using expression: true

이렇게 하면 익명 사용자도 그 테이블의 모든 행을 select 할 수 있습니다. 정책을 저장하고 사이트에서 다시 데이터를 불러보면 그제야 목록이 채워집니다.

Supabase는 정책 템플릿도 제공합니다. "Enable read access for all users" 같은 템플릿을 고르면 위 설정이 자동으로 입력됩니다. 비개발자라면 템플릿에서 시작하는 편이 안전합니다.

사용자별 데이터일 때는 user_id로 제한

로그인한 사용자만 자기 데이터를 볼 수 있어야 하는 경우는 다릅니다. 주문 내역, 개인 메모, 마이페이지 정보 같은 것들이죠.

이 경우 정책 조건은 단순합니다.

Using expression: auth.uid() = user_id

뜻은 "현재 로그인한 사용자의 id가 이 행의 user_id 컬럼과 같을 때만 보여줘" 입니다. 이러면 다른 사람의 데이터는 절대 노출되지 않습니다.

주의할 것:

  • 테이블에 user_id 컬럼이 실제로 있어야 합니다 (없으면 추가)
  • 데이터를 insert할 때 user_id에 auth.uid() 값을 넣어줘야 합니다
  • 정책은 SELECT, INSERT, UPDATE, DELETE 각각 따로 만들어야 합니다

AI가 만든 코드에서 "내 데이터인데 안 보여요" 라는 증상이 나오면, 보통 이 중 하나가 빠져 있습니다. insert는 되는데 select가 빈 배열이라면, INSERT 정책은 있지만 SELECT 정책이 없는 경우입니다.

확인할 때 흔히 막히는 지점

정책을 만들었는데도 여전히 빈 배열이 돌아온다면 다음을 차례로 확인해 보세요.

  • 정책 저장 후 Apply 버튼을 눌렀는가 (Supabase는 가끔 적용을 한 번 더 요구합니다)
  • 사이트에서 사용 중인 anon key가 최신 키인가 (키를 회전한 적이 있다면 옛날 키가 코드에 남아 있을 수 있습니다)
  • 로그인 상태에서 호출하고 있는가 (auth.uid() 정책은 로그인이 안 되면 항상 null입니다)
  • 테이블 이름과 컬럼 이름의 대소문자가 정확한가 (Supabase는 대소문자를 구분합니다)

특히 세 번째가 자주 빠집니다. 비로그인 상태에서 마이페이지 같은 화면을 만들고 데이터가 안 보인다면, 로그인 흐름부터 다시 봐야 합니다.

AI에게 도움 받을 때 짚어줄 말

RLS 문제를 AI에게 던질 때는 코드를 고쳐달라고 하기 전에 다음을 명확히 알려주세요.

  • 어떤 테이블에서 데이터가 안 나오는지
  • RLS가 켜져 있는지
  • 현재 등록된 정책이 있는지, 있다면 어떤 조건인지
  • 사용 중인 키가 anon key인지 service role key인지
  • 호출하는 사용자가 로그인 상태인지

AI는 이 정보 없이 코드만 보면 select 쿼리를 자꾸 다른 방식으로 다시 짜기 시작합니다. 정책 문제는 코드를 어떻게 짜도 풀리지 않습니다. 정책을 고쳐야만 풀립니다.

관련 글: Supabase 데이터 저장 안됨: 바이브코딩 프로젝트 점검 순서, AI 코딩 에이전트가 고치지 못하는 버그의 3가지 특징

AI·바이브코딩으로 해결되지 않는 개발 이슈, 먼저 원인부터 확인하세요

Cursor 에러, Vercel·Next.js 배포 실패, 결제·예약 연동 오류, 소규모 기능 개발을 단건 중심으로 진단합니다. 월 구독 파트너는 정식 상품을 준비 중입니다.