Capítulo 160 de 456

forbidden

Core Idea

forbidden() lança um erro que renderiza uma página 403 do Next.js, útil para tratar erros de autorização (usuário autenticado mas sem permissão). Feature experimental, requer flag explícita.

Key Concepts

  • Status experimental: não recomendado para produção; requer experimental.authInterrupts: true em next.config.js.
  • Mecanismo: forbidden() lança NEXT_HTTP_ERROR_FALLBACK;403 e interrompe a renderização do segmento de rota; injeta <meta name="robots" content="noindex" /> automaticamente.
  • Deve rodar no render path: um componente, ou função que o componente dá await; deixado em promise não aguardada, o throw não é capturado e a UI de forbidden não renderiza.
  • Escopos suportados: Server Components, Server Functions, Route Handlers.
  • Restrição: não pode ser chamado no root layout.
  • Tipo de retorno never: não precisa de return forbidden(); um try/catch ao redor suprime o interrupt (use unstable_rethrow para deixar passar).
  • UI customizável: via arquivo forbidden.js colocado ao lado da rota.

Code Examples

import { verifySession } from '@/app/lib/dal'
import { forbidden } from 'next/navigation'

export default async function AdminPage() {
  const session = await verifySession()
  if (session.role !== 'admin') {
    forbidden()
  }
  return <></>
}
  • O que demonstra: proteção de rota baseada em role, chamando forbidden() diretamente no componente.
async function getProjects() {
  const session = await verifySession()
  if (session?.role !== 'admin') {
    forbidden()
  }
  return db.projects.findMany()
}

// dentro de <Suspense fallback={...}><Projects /></Suspense>
  • O que demonstra: chamar forbidden() dentro de uma Data Access Layer function, envolta em <Suspense>, para manter o shell da página e o loading UI visíveis enquanto a sessão é checada; trade-off é que o status HTTP já começou como 200 antes do throw (streaming já iniciado).

Anti-patterns

  • Deixar forbidden() numa promise sem await: o throw ocorre onde nada captura; em dev aparece como unhandledRejection: NEXT_HTTP_ERROR_FALLBACK;403 no log, e a página forbidden não é exibida.
  • Envolver a chamada em try/catch sem unstable_rethrow: suprime silenciosamente o interrupt e nenhuma UI de erro aparece.
  • Chamar forbidden() no root layout: não suportado.
  • Esperar status HTTP 403 real quando o check roda dentro de <Suspense>: como o streaming já começou como 200, o código de status não pode mudar; para 403 real, o check precisa rodar antes do streaming (ex. em proxy).

Key Takeaways

  1. Use forbidden() na Data Access Layer, dentro de <Suspense>, quando quiser manter o shell da página visível durante o check de sessão.
  2. Para retornar status 403 real (não 200 com UI de erro), rode o check em proxy em vez de dentro do componente.
  3. authInterrupts também habilita unauthorized(), a contraparte para usuário não autenticado.

Connects To

  • forbidden.js: arquivo de convenção para customizar a UI 403.
  • unauthorized: função irmã para erro 401 (não autenticado), sob a mesma flag authInterrupts.
  • notFound: padrão similar de "throw especial" para 404.
  • unstable_rethrow: necessário para deixar o erro de forbidden passar através de um try/catch.