Supabase como Backend Completo para Next.js
O problema que o Supabase resolve (e o que ele não resolve)
Montar um backend para uma aplicação Next.js exige decisões sobre autenticação, banco de dados, storage de arquivos e, dependendo do caso, comunicação em tempo real. A abordagem clássica envolve configurar um PostgreSQL, um serviço de auth (Auth0, Clerk, NextAuth), um S3 para arquivos e um servidor WebSocket separado. São quatro serviços, quatro faturas, quatro pontos de falha.
O Supabase empacota tudo isso em cima de um PostgreSQL real: auth com JWT, storage com políticas de acesso, realtime via WebSockets e uma API REST gerada automaticamente pelo PostgREST. A proposta é eliminar boilerplate de infraestrutura sem abrir mão do controle que um banco relacional oferece.
Onde o Supabase não encaixa: se você precisa de lógica de negócio complexa no backend (filas, workers, orquestração de serviços), ele não substitui um servidor dedicado. Nesses casos, o Supabase funciona como camada de dados e auth, e a lógica fica em API Routes do Next.js ou em um serviço separado.
Instalação e configuração inicial
Crie um projeto no dashboard do Supabase e copie a SUPABASE_URL e a SUPABASE_ANON_KEY. A anon key é pública por design: a segurança vem do Row Level Security, não do sigilo dessa chave.
npm install @supabase/supabase-js @supabase/ssr
Crie o arquivo de variáveis de ambiente:
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://seuprojetoid.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
O prefixo NEXT_PUBLIC_ expõe essas variáveis no client. Isso é intencional: a anon key só permite operações que as políticas RLS autorizam.
Criando os clientes: server e browser
O @supabase/ssr oferece helpers para criar clientes que gerenciam cookies corretamente no App Router. A separação entre client de servidor e client de browser é obrigatória porque o Next.js executa código nos dois ambientes.
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function createSupabaseServer() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
// getAll/setAll é a API recomendada pelo @supabase/ssr v0.5+
// evita problemas de sincronização com cookies individuais
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
} catch {
// setAll falha em Server Components (read-only)
// funciona em Server Actions e Route Handlers
}
},
},
}
);
}
// src/lib/supabase/browser.ts
import { createBrowserClient } from "@supabase/ssr";
export function createSupabaseBrowser() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
O middleware renova tokens expirados antes que a request chegue ao Server Component:
// src/middleware.ts
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
export async function middleware(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request });
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
// Propaga cookies tanto na request quanto na response
// sem isso, Server Components recebem sessão stale
cookiesToSet.forEach(
---
Leia o artigo completo em [https://www.vivodecodigo.com.br/nextjs/supabase-backend-completo-nextjs-auth-database-rls-storage](https://www.vivodecodigo.com.br/nextjs/supabase-backend-completo-nextjs-auth-database-rls-storage)