1

Upload de arquivos com Supabase Storage no Next.js: 6 cuidados antes de ir para produção

Upload de arquivo parece simples até o primeiro usuário enviar um PDF de 80 MB, ou até alguém descobrir que dá para baixar o avatar de qualquer outra pessoa trocando o ID na URL. Se o seu app usa Next.js (App Router) com Supabase, estes são os seis cuidados que valem a pena colocar desde o início.

1. Bucket privado por padrão

Crie o bucket como privado e só torne público o que realmente precisa ser público (por exemplo, imagens de marketing). Arquivos de usuário (documentos, notas fiscais, fotos de perfil em apps fechados) devem ficar em bucket privado e ser servidos com URL assinada.

2. Organize os caminhos pelo ID do usuário

Use uma convenção como uid/arquivo.ext. Isso deixa as políticas simples:

create policy "usuario le os proprios arquivos"
on storage.objects for select
to authenticated
using (
  bucket_id = 'documentos'
  and (storage.foldername(name))[1] = auth.uid()::text
);

create policy "usuario envia para a propria pasta"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'documentos'
  and (storage.foldername(name))[1] = auth.uid()::text
);

Sem essas políticas, o Storage nega tudo, o que é bom. O erro comum é "resolver" o 403 liberando o bucket inteiro.

3. Limite tamanho e tipo no bucket, não só no front

Validar no <input accept="image/*"> ajuda o usuário, mas não protege nada. Configure no próprio bucket o tamanho máximo e os MIME types permitidos. Assim um upload direto pela API com a chave anon também é barrado.

4. Upload direto do navegador, não pelo seu servidor

Na Vercel, funções serverless têm limite de tamanho de corpo da requisição. Mandar o arquivo para uma Route Handler e de lá para o Supabase costuma estourar esse limite e gastar tempo de execução. Prefira:

  1. O navegador, já autenticado, envia direto com supabase.storage.from('documentos').upload(...).
  2. As políticas do item 2 garantem que ele só escreve na própria pasta.
  3. Depois, o app grava o caminho do arquivo na tabela correspondente.

Para casos em que o usuário não está logado, gere no servidor uma signed upload URL (createSignedUploadUrl) e entregue só ela ao cliente.

5. URLs assinadas com validade curta

Para exibir arquivos privados, gere no servidor:

const { data } = await supabase.storage
  .from('documentos')
  .createSignedUrl(caminho, 60 * 10) // 10 minutos

Evite validades de dias "para não dar trabalho". Se o link vazar, ele continua funcionando até expirar.

6. Limpe o que ficou órfão

Quando o registro na tabela é apagado, o arquivo no Storage continua lá ocupando espaço. Duas opções simples: apagar o arquivo na mesma ação do servidor que apaga o registro, ou rodar periodicamente uma função que compara storage.objects com a sua tabela.

Checklist rápido

  • Bucket privado, público só quando necessário
  • Caminho uid/... e políticas de select/insert por pasta
  • Tamanho e MIME limitados no bucket
  • Upload direto do navegador (ou signed upload URL)
  • URLs assinadas curtas para leitura
  • Rotina para remover arquivos órfãos

Transparência: eu faço parte do time do Massvai (massvai.com), um construtor de apps com IA que gera apps full-stack em Next.js, Supabase e Vercel. Este checklist saiu dos problemas que mais vemos nos projetos gerados, mas vale para qualquer app com essa stack.

Carregando publicação patrocinada...