Next.js SaaS
Como projetar um SaaS multitenant no Next.js: isolamento, roteamento e propriedade dos dados
Projete resolução de tenant, propriedade no banco, autorização, chaves de cache, tarefas, domínios personalizados e implantação para um SaaS Next.js multitenant.
Multitenancy é uma invariante: cada leitura, gravação, entrada de cache, tarefa, log e host deve identificar o tenant correto antes de executar trabalho privilegiado.
Definir o que significa tenant
Um tenant pode ser organização, espaço de trabalho, loja ou conta de cliente. Defina quem o cria, quem participa, se usuários podem participar de vários e quais registros pertencem a ele. Use internamente um ID estável de tenant mesmo que o identificador público seja um slug ou host personalizado.
Documente a garantia de isolamento. Infraestrutura compartilhada com propriedade por linha difere de um banco por tenant. O modelo mais forte custa mais para provisionar e operar, mas pode ser justificado por regulação, escala ou exigências dos clientes. Escolha conforme o limite real do produto, não um ideal abstrato de pureza.
Resolver o contexto de tenant a partir de entradas confiáveis
Aplicações baseadas em caminhos resolvem /acme/projects pelo slug da rota. Aplicações com subdomínios ou domínios personalizados associam o host recebido a um registro de tenant. Normalize o host, rejeite valores desconhecidos e não confie no ID enviado por um formulário quando a rota autenticada já estabelece o contexto.
Proxy pode fazer reescritas iniciais de host ou roteamento otimista, mas a resolução segura também deve ocorrer no servidor. Passe um contexto verificado de tenant à camada de dados; não deixe cada componente interpretar cabeçalhos e adivinhar a propriedade independentemente.
export async function resolveTenant(host: string) {
const normalized = host.toLowerCase().split(":")[0];
const tenant = await db.tenant.findUnique({
where: { hostname: normalized },
});
if (!tenant) notFound();
return tenant;
}Aplicar a propriedade em cada consulta
Inclua tenantId nos registros de tabelas compartilhadas e em cada consulta, atualização, exclusão e restrição de unicidade. Um ID globalmente único não autoriza acesso. Consulte pelo ID solicitado e pelo ID verificado do tenant para impedir que um identificador vazado atravesse o limite.
Segurança de linha no banco pode acrescentar defesa em profundidade quando a stack permite, mas a autorização da aplicação continua necessária. Teste as negações criando dois tenants e tentando cada operação protegida com os IDs do outro.
const project = await db.project.findFirst({
where: {
id: projectId,
tenantId: context.tenantId,
},
});Definir papéis por tenant
Um usuário pode ser owner em um tenant e viewer em outro. Armazene os papéis nos vínculos, não em um campo global do usuário. Resolva o tenant, depois o vínculo e verifique a permissão exata da operação.
Evite espalhar strings de funções de acesso pelos componentes. Centralize as regras de permissão em funções que retornem decisões do domínio e repita a verificação dentro de Server Actions e Route Handlers. A interface pode ocultar controles indisponíveis para facilitar o uso, mas é a autorização no servidor que protege a operação.
Separar caches, tarefas e armazenamento por tenant
Toda chave compartilhada de cache precisa da identidade do tenant. Uma chave projects pode expor dados de um tenant a outro; projects:tenant-id estabelece o limite. Aplique a mesma regra a tags de cache, limites de taxa, caminhos de armazenamento objeto, índices de busca, payloads de tarefas e chaves de idempotência.
Workers em segundo plano devem verificar novamente a autorização ou usar um contexto imutável e confiável de tenant registrado na criação da tarefa. Inclua nos logs IDs seguros de tenant, mas não registre conteúdo privado dos clientes só para facilitar a depuração.
"use cache";
cacheTag("projects:" + tenantId);
return db.project.findMany({ where: { tenantId } });Tratar domínios personalizados como configuração verificada
Um tenant não deve obter um host só por digitá-lo em um formulário. Exija verificação antes de rotear tráfego, impeça que um host pertença a dois tenants e defina o que acontece na troca de proprietário. Separe os domínios da plataforma dos gerenciados pelos clientes.
Gere URLs canônicas a partir do host verificado do tenant quando suas páginas públicas forem indexáveis. Painéis autenticados normalmente não devem ser indexados. Compatibilize redirecionamentos e cookies com o modelo de hosts, principalmente ao alternar entre um domínio central de login e um domínio do tenant.
Testar o isolamento como propriedade do sistema
Crie dados automatizados de teste para pelo menos dois tenants e dois papéis. Teste acesso direto às páginas, Server Actions, Route Handlers, arquivos exportados, caches, tarefas, busca e resolução de hosts. Esconder a navegação de outro tenant não testa o isolamento.
Revise consultas e logs em busca de filtros de tenant ausentes. Inclua tenantId nas restrições de unicidade quando nomes precisarem ser únicos apenas dentro do tenant. Teste exclusão e exportação para que dados em segundo plano não permaneçam sem proprietário por acidente.
Implantar roteamento de tenants com versões observáveis
A Adios mantém roteamento Next.js, ambiente persistente, banco obrigatório, referências a segredos, logs e domínios personalizados vinculados à versão. Na prévia, teste um host da plataforma, um host verificado de tenant, um host desconhecido, duas contas de tenants e a rota de saúde antes da promoção.
Logs de compilação e execução distinguem falhas de versão de erros de dados específicos do tenant. TLS gerenciado e promoção mantêm as rotas verificadas na versão saudável, enquanto fontes e manifesto continuam revisáveis. O isolamento ainda depende do projeto da aplicação e dos dados; a hospedagem torna esse projeto implantável e inspecionável.
name: multi-tenant-app
build_cmd: npm ci && npm run build
start_cmd: npm start
runtime:
name: node@24
port: 3000
health_path: /api/health
env:
DATABASE_URL: secret://DATABASE_URL