Capítulo 119 de 456

layout.js

Core Idea

layout.js define UI compartilhada entre múltiplas rotas, sendo o componente mais externo de um segmento na hierarquia (envolve template.js, error.js, loading.js, not-found.js e page.js). Layouts não re-renderizam em navegação, o que traz restrições importantes de acesso a dado runtime.

Key Concepts

  • children (obrigatório): populado com o segmento filho (layout, page, ou special file).
  • params (opcional): Promise com os dynamic route params do root até o layout; requer await/use().
  • LayoutProps<'/route'>: helper de tipagem global (gerado por next dev/next build/next typegen) que infere params e named slots (@analytics) tipados a partir da estrutura de diretório.
  • Root layout: obrigatório em app/layout.js; único lugar que define <html>/<body>; nunca adicione <head> manual, use a Metadata API.
  • Múltiplos root layouts: qualquer layout sem outro layout.js acima dele é um root layout; criados via route groups (app/(shop)/layout.js) ou omitindo app/layout.js em subdiretórios; navegar entre root layouts diferentes causa full page load, não client-side navigation.
  • Root parameters: dynamic segment antes do root layout (ex. app/[lang]/layout.js), lido de qualquer Server Component via next/root-params.
  • Layouts não re-renderizam: ficam em cache no client durante navegação; por isso não têm acesso a request object cru, search params atualizados ou pathname atualizado.

Code Examples

export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params
}
  • O que demonstra: acesso a params (Promise) em um layout de segmento dinâmico.
import { Suspense } from 'react'
import { NavSkeleton } from './nav-skeleton'
import { DashboardNav } from './dashboard-nav'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Suspense fallback={<NavSkeleton />}>
        <DashboardNav />
      </Suspense>
      <main>{children}</main>
    </>
  )
}
  • O que demonstra: envolver dado runtime do layout em <Suspense> próprio, já que loading.js (abaixo do layout na hierarquia) não cobre acesso a dado uncached/runtime dentro do próprio layout.
'use client'
import { useSearchParams } from 'next/navigation'

export default function Search() {
  const searchParams = useSearchParams()
  const search = searchParams.get('search')
  return '...'
}
  • O que demonstra: contornar a limitação de layout não re-renderizar, lendo search params atualizados via useSearchParams() num Client Component filho.

Reference Tables

Example RouteURLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>

Anti-patterns

  • Adicionar <head> manual (<title>, <meta>) no root layout: use a Metadata API, que já lida com streaming e deduplicação.
  • Esperar acesso a cookies()/headers() cru no layout sem await cookies()/await headers() explícito: layout não tem request object direto; use as APIs cookies/headers em Server Components/Functions.
  • Esperar search params atualizados no layout: layout não re-renderiza em navegação, search params ficam stale; use searchParams da Page ou useSearchParams() num Client Component.
  • Esperar pathname atualizado no layout: mesmo problema; use usePathname() num Client Component.
  • Acessar dado uncached/runtime no layout sem <Suspense> próprio: sem Cache Components, bloqueia a navegação inteira até o layout terminar; com Cache Components, gera erro de build orientando a envolver em <Suspense>.
  • Tentar passar dado do layout para children via prop: não é possível; refetch (deduped por fetch/React.cache) é o padrão aceito.

Key Takeaways

  1. Layout é cacheado e não re-renderiza; qualquer dado que precisa ficar fresco em cada navegação (search params, pathname, cookies dinâmicos) precisa migrar para um Client Component filho ou para a Page.
  2. Um app pode ter múltiplos root layouts (route groups ou omissão de app/layout.js), mas cruzar entre eles força full page load.
  3. loading.js não cobre dado uncached acessado dentro do próprio layout.js (ele fica abaixo na hierarquia); envolva esse acesso em <Suspense> no layout ou mova para page.js.
  4. LayoutProps<'/route'> dá tipagem automática de params e slots nomeados, gerada por next dev/build/typegen.

Connects To

  • File-system conventions (ch111): índice das convenções.
  • Dynamic Segments (ch113): mecânica de params compartilhada.
  • loading.js (ch120): interação específica com o layout e Cache Components.
  • default.js (ch112): fallback usado em slots de Parallel Routes, também filho do layout.