Voltar ao Blog
Jul 16, 2026
Engineering
Equipe Editorial Greta

Lidando com Upload de Arquivos e Armazenamento em um App Construído com IA

Passar arquivos pelo seu próprio servidor é o padrão de upload que funciona em uma demonstração e quebra em produção. Veja por que URLs assinadas e upload direto para o armazenamento são o padrão que realmente escala.

Lidando com Upload de Arquivos e Armazenamento em um App Construído com IA

Lidando com Upload de Arquivos e Armazenamento em um App Construído com IA

Resposta rápida

Nunca deixe um arquivo passar pelo seu próprio servidor. O padrão certo é uma URL assinada: seu backend pede ao provedor de armazenamento uma permissão temporária de curta duração, o navegador envia o arquivo direto para esse provedor, e o seu servidor só vê o caminho final e os metadados do arquivo. Faça de qualquer outra forma e você terá construído um endpoint de upload lento e faminto por memória, que quebra na primeira vez que alguém tentar enviar um vídeo de 200MB.

Por que "simplesmente aceitar o arquivo no servidor" quebra

O primeiro instinto da maioria das pessoas é o mais simples: adicionar um campo de formulário, enviá-lo para uma rota de API, gravar os bytes em disco ou em uma coluna do banco de dados. Funciona em uma demonstração. Envie uma foto de perfil, veja ela renderizar, publique.

Aí alguém envia um PDF de 40MB. Sua função serverless tem um limite de payload — muitas vezes 4,5MB, às vezes menos — e a requisição simplesmente falha, sem um erro útil, só um timeout ou um 413. Ou dá certo, mas agora o processo do seu servidor está segurando o arquivo inteiro na memória enquanto o reenvia para outro lugar, o que é tranquilo para um usuário e um problema de verdade para cinquenta simultâneos.

Já vi times perderem horas depurando isso achando que era um bug no código. Não é. É um problema de arquitetura: o servidor nunca deveria estar no meio dessa transferência.

O sinal de que você está fazendo errado

Se o trabalho da sua rota de API inclui as palavras "receber" e "repassar" na mesma frase — receber o arquivo, repassar para o armazenamento — você adicionou um salto que não precisava existir e um gargalo que escala com o seu tráfego, não com o do provedor de armazenamento.

O padrão que realmente escala

Um fluxo de upload que funciona tem três etapas, e nenhuma delas passa o arquivo pelo seu servidor de aplicação.

Etapa um — pedir uma URL assinada. O navegador diz ao seu backend "quero enviar um arquivo chamado invoice.pdf, é um PDF, tem uns 2MB". Seu backend verifica se esse usuário sequer tem permissão para fazer upload ali, depois pede ao provedor de armazenamento (S3, Supabase Storage, Cloudflare R2 — qualquer um que você use) uma URL pré-assinada válida por alguns minutos, restrita exatamente àquele arquivo.

Etapa dois — upload direto. O navegador usa essa URL para enviar o arquivo direto para o armazenamento via PUT. Seu servidor não é acionado em nenhum momento dessa etapa. Um vídeo de 500MB consome os mesmos recursos do servidor que uma miniatura de 5KB: zero.

Etapa três — confirmar e registrar. Quando o upload termina, o navegador (ou um webhook do provedor de armazenamento, se ele suportar) avisa ao seu backend "terminou, aqui está o caminho final". Seu backend grava esse caminho no banco de dados, roda qualquer validação que não pôde ser feita antes, e o arquivo agora faz parte dos dados do seu app.

Esse é o mesmo formato quer você esteja guardando avatares de usuário ou lidando com importações em massa de CSV — só a etapa de validação muda.

O que "validação que não pôde ser feita antes" realmente significa

Uma URL assinada trava o nome e o tamanho aproximado do arquivo, mas não consegue inspecionar um conteúdo que ainda não viu. É aí que entra uma segunda camada: depois que o upload chega, verifique o tipo real do arquivo pelos bytes (não pela extensão que o usuário digitou), rejeite qualquer coisa que não bata com o que foi declarado, e limite o armazenamento total por conta para que um usuário não encha silenciosamente o seu bucket. Nada disso é exótico — são algumas linhas na etapa de confirmação — mas pular isso é como apps acabam servindo um .exe renomeado para .jpg.

Uma comparação: upload via servidor vs. direto para o armazenamento

AspectoUpload via servidor (proxy)Direto para o armazenamento (URL assinada)
Tamanho máximo prático do arquivoLimitado pelo teto de payload da função (geralmente ~4,5MB)Limitado só pelo provedor de armazenamento (múltiplos GB)
Carga no servidor durante o uploadEscala com uploads simultâneosPraticamente zero — o servidor não fica no caminho da transferência
Tempo até o primeiro byte em arquivos grandesLento — o servidor armazena em buffer e depois repassaRápido — conexão direta com a borda de CDN do armazenamento
Complexidade de configuraçãoMenor no início (um único endpoint)Um pouco maior (pedido de URL assinada + etapa de confirmação)
Onde quebraNo momento em que o tamanho dos arquivos ou a concorrência cresceRaramente — é assim que apps de verdade em produção e em escala fazem

Onde isso vive em um app construído com a Greta

Como a Greta monta um app Next.js com Prisma e armazenamento em nuvem já conectado desde o início, o padrão de URL assinada não é uma infraestrutura extra que você monta depois — é o formato padrão de um recurso de upload. Uma rota como app/api/uploads/sign/route.ts cuida da checagem de permissão e pede a URL assinada ao armazenamento; uma tabela documents ou attachments no schema do Prisma guarda o caminho final, o tipo de conteúdo e o dono assim que o upload é confirmado. As transformações de imagem e a entrega via CDN que fazem as miniaturas carregarem rápido são cuidadas pela camada de armazenamento, não algo que você escreve do zero.

Isso importa para qualquer coisa com volume real de arquivos — um CRM em que cada contato tem documentos anexados, um marketplace em que cada anúncio tem fotos, uma ferramenta interna em que alguém envia uma planilha toda semana. O padrão não muda; só mudam os tipos de arquivo e as regras de validação.

Perguntas frequentes

Posso simplesmente guardar arquivos no banco de dados como uma coluna blob? Pode, e para um punhado de arquivos pequenos isso não vai quebrar nada de forma visível. Mas armazenamento em banco de dados custa mais por gigabyte do que armazenamento de objetos, os backups ficam mais lentos conforme a tabela de blobs cresce, e você perde a entrega via CDN que faz imagens e vídeos carregarem rápido. Use o banco de dados para os metadados e o caminho do arquivo, não para os bytes em si.

E quanto a limites de tamanho de arquivo para usuários gratuitos vs. pagos? Isso é uma regra de negócio, não técnica — verifique na etapa de "pedir uma URL assinada", antes mesmo do upload começar, para que um usuário acima da sua cota receba uma mensagem clara em vez de um upload desperdiçado.

Preciso de verificação de vírus? Para um app de consumo em que os usuários enviam os próprios arquivos para uso próprio, geralmente não no primeiro dia. Para qualquer coisa em que o arquivo de um usuário depois é aberto por outro usuário — um workspace compartilhado, um anexo de chamado de suporte — sim, e essa verificação deve acontecer como parte da etapa de confirmação, antes de o arquivo ficar disponível para qualquer outra pessoa.

Como lido com redimensionamento de imagem para telas diferentes? A maioria dos provedores de armazenamento ou das CDNs na frente deles suporta transformações sob demanda via parâmetros de URL — peça a mesma imagem em larguras diferentes e deixe a borda cuidar do redimensionamento e do cache, em vez de gerar e guardar cinco versões de cada upload você mesmo.

Encerramento

Upload de arquivos parece um problema resolvido até o momento em que alguém envia um arquivo de verdade — um vídeo, um lote de faturas escaneadas, um arquivo de design que na real tem 80MB. Construa o padrão de URL assinada desde o início e esse dia passa em branco. Pule essa etapa, e vira um incidente de produção com o seu nome no post-mortem.

Comece a construir seu app com a Greta hoje.

Fim do artigo
Voltar ao topo

Construa Algo de Verdade

Se você consegue descrever, você consegue criar.