O caminho mais rápido: o template oficial
O template Next.js já vem com tudo desta página funcionando: login e cadastro, sessão que se renova sozinha e uma lista de tarefas protegida por RLS. Ele é compilado a cada mudança contra a versão publicada do SDK.
npx create-next-app@latest meu-app --example https://github.com/superdb-br/templates/tree/main/nextjs
cd meu-app
cp .env.example .env.local
npm run dev
Preencha o .env.local com os dados do painel (projeto → Conectar), rode o arquivo superdb/schema.sql do template no SQL Editor e abra http://localhost:3000. A tela inicial confere a conexão e diz exatamente o que falta.
Há templates para sete stacks: Next.js, React, Vue, SvelteKit, Nuxt, HTML puro e Expo. O mesmo app em todos.
Como funciona
No Next, quem busca os dados é o servidor (Server Components e Server Actions). O servidor não enxerga o localStorage do navegador, mas recebe os cookies em toda requisição. Por isso a sessão mora em cookies httpOnly, que nenhum JavaScript da página consegue ler.
O SDK @superdb/client guarda a sessão num armazenamento que você escolhe (getItem/setItem/removeItem). O template passa um armazenamento feito de cookies, e o resto do SDK funciona sem saber disso. Não existe pacote de SSR à parte: é esse adaptador de uma página.
Passo a passo (o que o template faz por você)
1Instalar o SDK
O @superdb/client segue a API do supabase-js: .auth, .from(), .rpc() e .storage.
npm install @superdb/client server-only
2Variáveis de ambiente
Os três dados vêm do painel: projeto → Conectar. O slug é o que aparece no endereço do projeto.
A chave anon é pública: vai no código do app. Quem protege os dados é o RLS. A service key nunca vai num app.
NEXT_PUBLIC_SUPERDB_URL=https://auth.superdb.com.br
NEXT_PUBLIC_SUPERDB_ANON_KEY=cole-a-chave-anon-aqui
NEXT_PUBLIC_SUPERDB_PROJECT=slug-do-projeto
3Criar a tabela com RLS
No SQL Editor do projeto. Toda tabela nova no SuperDB nasce fechada (RLS ligado, nenhuma policy): sem as últimas linhas, ninguém além do dono do projeto enxerga nada. Elas abrem para cada pessoa só as próprias linhas, e o revoke tira da chave anon até a permissão na tabela.
create table if not exists tarefas (
id uuid primary key default gen_random_uuid(),
titulo text not null check (char_length(btrim(titulo)) between 1 and 200),
feita boolean not null default false,
criada_em timestamptz not null default now(),
user_id uuid not null default auth.uid()
);
-- A lista do app sempre busca "as minhas, das mais novas para as mais antigas".
create index if not exists tarefas_por_dono on tarefas (user_id, criada_em desc);
alter table tarefas enable row level security;
-- Uma regra só, para ler, criar, alterar e apagar: a linha é sua ou não existe.
-- "using" filtra o que você vê; "with check" recusa gravar linha de outra pessoa.
drop policy if exists tarefas_do_dono on tarefas;
create policy tarefas_do_dono on tarefas
for all to authenticated
using (user_id = auth.uid())
with check (user_id = auth.uid());
revoke all on tarefas from anon;
grant select, insert, update, delete on tarefas to authenticated;
4Um cliente por requisição, lendo os cookies
Crie o cliente dentro de cada Server Component ou Server Action: ele lê a sessão dos cookies desta requisição. Nunca o guarde numa variável de módulo, porque no servidor ela seria compartilhada entre visitantes.
A sessão vai em quatro cookies (acesso, renovação, dados e vencimento) porque a sessão inteira em JSON passaria de 4 KB, o limite de um cookie. O adaptador completo está em lib/superdb/cookies.ts.
import 'server-only'
import { cookies } from 'next/headers'
import { createClient } from '@superdb/client'
import { OPCOES_DO_COOKIE, armazenamentoEmCookies } from './cookies'
export async function superdbDoServidor() {
const pote = await cookies()
const armazenamento = armazenamentoEmCookies({
ler: (nome) => pote.get(nome)?.value,
// Só grava em Server Action e Route Handler; numa página o SDK só lê.
gravar: (nome, valor) => pote.set(nome, valor, OPCOES_DO_COOKIE),
apagar: (nome) => pote.delete(nome),
})
return createClient(
process.env.NEXT_PUBLIC_SUPERDB_URL!,
process.env.NEXT_PUBLIC_SUPERDB_ANON_KEY!,
// Quem renova é o proxy.ts: renovar aqui trocaria o refresh token sem
// poder regravar o cookie, e o próximo pedido cairia como reuso.
{ project: process.env.NEXT_PUBLIC_SUPERDB_PROJECT!, storage: armazenamento, autoRefreshToken: false },
)
}
5Login e cadastro com Server Action
A senha vai direto do formulário para o servidor, e os tokens voltam só como cookies httpOnly. O erro do SDK vem em inglês (invalid_credentials); o template traduz em erros.ts.
'use server'
import { redirect } from 'next/navigation'
import { superdbDoServidor } from '@/lib/superdb/servidor'
export async function entrar(_: unknown, form: FormData) {
const superdb = await superdbDoServidor()
const { error } = await superdb.auth.signInWithPassword({
email: String(form.get('email')),
password: String(form.get('senha')),
})
if (error) return { erro: error.code }
redirect('/tarefas')
}
export async function sair() {
const superdb = await superdbDoServidor()
await superdb.auth.signOut()
redirect('/')
}
6Renovar a sessão no proxy
O token vale 1 hora. No servidor, quem renova é o proxy.ts, e não o SDK: renovar troca o refresh token, e só o proxy consegue regravar o cookie antes de a página ler. Por isso o client do servidor nasce com autoRefreshToken: false (a opção existe desde a 0.3 do SDK). O proxy.ts (o antigo middleware.ts, renomeado no Next 16) roda antes de cada página e de cada Server Action, e é o lugar em que dá para regravar cookies antes de a página ler.
A armadilha: o SuperDB troca o refresh token a cada renovação e trata reuso como roubo, derrubando a sessão. Uma página dispara várias requisições quase juntas com o mesmo cookie. O renovar.ts do template faz uma renovação por refresh token e entrega a sessão nova, por 30 segundos, a quem chegar com o token antigo.
import type { NextRequest } from 'next/server'
import { renovarSessao } from './lib/superdb/renovar'
export function proxy(request: NextRequest) {
return renovarSessao(request)
}
export const config = {
matcher: ['/((?!_next/static|_next/image|.*\\.(?:svg|png|jpg|ico|css|js)$).*)'],
}
7Página protegida, com os dados buscados no servidor
A página confere a sessão por conta própria: o proxy renova, não protege. A consulta sai com o token de dados de quem entrou, e o RLS devolve só as linhas dessa pessoa, sem filtro nenhum no código.
import { redirect } from 'next/navigation'
import { superdbDoServidor } from '@/lib/superdb/servidor'
export default async function PaginaTarefas() {
const superdb = await superdbDoServidor()
const { data: { session } } = await superdb.auth.getSession()
if (!session) redirect('/')
const { data: tarefas, error } = await superdb
.from('tarefas')
.select('id,titulo,feita')
.order('criada_em', { ascending: false })
if (error) return <p>{error.message}</p>
return <ul>{tarefas?.map((t) => <li key={t.id}>{t.titulo}</li>)}</ul>
}
8Rodar
Crie uma conta na tela inicial e adicione tarefas. Server Actions de escrita seguem o mesmo molde: conferem a sessão, chamam superdb.from(...) e devolvem { erro } em vez de lançar exceção.
npm run dev
# → http://localhost:3000
Próximos passos
- O template completo, com o README explicando cada arquivo
- Row Level Security: policies para cada caso
- Referência do SDK
- MCP: deixe seu agente de IA criar as tabelas