publishable 키는 페이지에 두고, 행 권한은 RLS가 막습니다.
서버 파일 lib/supabase/server.js는 없다. 브라우저가 supabase-js (새 창)를 받아 createClient를 호출한다. URL과 publishable 키는 Supabase 연결 단원과 같은 값이다.
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="/webdev/static/app.js"></script>
const supabase = window.supabase.createClient(
'https://프로젝트.supabase.co',
'publishable 키'
)
이 키는 페이지 소스에 보인다. 그렇게 설계된 공개 키다. secret 키는 이 파일에 적지 않는다. 정적 사이트에는 서버 환경 변수가 없어서, 적는 순간 방문자도 읽는다.
표와 select 정책은 Next.js 예제와 같다. 정책 SQL은 Supabase · 읽기·쓰기에 있다. 차이는 호출이 브라우저에서 일어나고, 돌아온 행을 DOM에 붙인다는 점이다.
async function loadPosts() {
const { data, error } = await supabase
.from('posts')
.select('id, title')
const list = document.querySelector('#list')
if (error) {
list.textContent = '불러오지 못했습니다.'
return
}
list.replaceChildren(...data.map((post) => {
const li = document.createElement('li')
li.textContent = post.title
return li
}))
}
loadPosts()
data가 []이면 오류가 아니다. 행이 없거나 RLS가 이 키에 행을 안 보여 주는 것이다. 제목은 textContent로 넣는다. HTML 문자열로 붙이면 글 제목에 들어 있던 태그가 실행된다.
첫 HTML의 ul은 비어 있다. 행은 스크립트가 끝난 뒤에 채워진다. 검색 엔진과 스크립트를 끈 브라우저는 그 목록을 보지 못한다.
insert도 정책이 있어야 한다. 정책이 없으면 키를 알아도 거절된다.
const { error } = await supabase
.from('posts')
.insert({ title: document.querySelector('#title').value })
이메일 로그인도 브라우저에서 호출한다. 성공하면 세션이 localStorage에 남고, 다음 from() 요청에 그 사용자 토큰이 실린다. RLS가 토큰을 보고 행을 가른다.
const { error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: '비밀번호'
})
Next.js 강좌에서 서버의 getSession()으로 권한을 판단하지 말라고 한 이유는, 서버가 쿠키 값을 검증 없이 믿기 때문이다. 정적 사이트에는 그 서버가 없다. 브라우저의 getSession()은 화면에 이메일을 보여 주는 용도다. 행을 허용할지는 Supabase가 토큰과 RLS로 정한다. proxy.js는 만들지 않는다.