Disponível para freelancerEntre em contato para que eu possa ajudar seu negócio a crescer ou tirar sua ideia do papel!

Estou interessado
O Poder Oculto do App Router do Next.js

O Poder Oculto do App Router do Next.js

A maioria dos desenvolvedores que migra para o App Router trata isso como uma forma diferente de organizar arquivos. Você move suas páginas para app/, renomeia algumas coisas para page.tsx e acha que terminou.

Tem muito mais coisa sendo deixada de lado.

O App Router não é só uma nova convenção de arquivos — é um modelo fundamentalmente diferente de como sua UI é construída, carregada e renderizada. Aqui está o que a maioria dos tutoriais ignora.


Layouts Aninhados Não São Só Wrappers

No Pages Router, layouts eram componentes que você envolvia manualmente ao redor do conteúdo da página. No App Router, layouts fazem parte do próprio sistema de roteamento.

app/
  layout.tsx           ← layout raiz (sempre renderizado)
  dashboard/
    layout.tsx         ← layout do dashboard (envolve todas as páginas do dashboard)
    page.tsx
    settings/
      page.tsx

O comportamento chave: layouts não re-renderizam ao navegar entre rotas que os compartilham.

Quando um usuário vai de /dashboard para /dashboard/settings, o dashboard/layout.tsx continua montado. Sem re-montagem, sem re-fetch, sem reset de scroll.

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex h-screen">
      <Sidebar />
      <main className="flex-1 overflow-y-auto p-6">{children}</main>
    </div>
  )
}

Uma sidebar com seu próprio data fetching, estado aberto ou posição de scroll sobrevive à navegação completamente intacta. No Pages Router, isso exigia hoisting cuidadoso de estado ou gambiarras complicadas.


Server Components São o Padrão — Use de Verdade

Todo componente dentro de app/ é um Server Component por padrão. A maioria dos desenvolvedores já vai buscar o 'use client' no momento em que fica em dúvida.

Não faça isso.

Server Components permitem que você busque dados diretamente no componente — sem useEffect, sem estado de loading, sem API route necessária.

// app/dashboard/page.tsx — Server Component, sem 'use client'
import { db } from '@/lib/db'

export default async function DashboardPage() {
  const projects = await db.project.findMany({ where: { userId: getCurrentUser() } })

  return (
    <div>
      <h1>Your Projects</h1>
      {projects.map((p) => (
        <ProjectCard key={p.id} project={p} />
      ))}
    </div>
  )
}

O fetch acontece no servidor. Nada dessa lógica vai para o browser. O componente renderiza direto para HTML.

A regra que sigo: comece todo componente como Server Component. Só adicione 'use client' quando precisar de useState, useEffect, APIs do browser ou event handlers.


Rotas Paralelas: Múltiplos Slots Independentes

Rotas Paralelas permitem renderizar múltiplas páginas independentes no mesmo layout ao mesmo tempo. Elas usam a convenção de nome @pasta.

app/
  layout.tsx
  @modal/
    page.tsx
  @feed/
    page.tsx
// app/layout.tsx
export default function Layout({
  children,
  modal,
  feed,
}: {
  children: React.ReactNode
  modal: React.ReactNode
  feed: React.ReactNode
}) {
  return (
    <div>
      {feed}
      {modal}
      {children}
    </div>
  )
}

Cada slot carrega de forma independente, com seu próprio estado de loading, error boundary e data fetching. Se @feed for lento, @modal ainda renderiza normalmente.

O padrão mais útil aqui: um modal que é uma rota de verdade. A URL muda, pode ser compartilhada, favoritada e funciona no refresh. Navegação estilo Instagram (clica numa foto → modal abre com mudança de URL → refresh carrega a página completa da foto) fica simples com esse padrão.


Rotas de Interceptação: UI Diferente, Mesma URL

Rotas de Interceptação permitem mostrar uma UI diferente para uma rota dependendo de como o usuário chegou nela.

app/
  posts/
    [id]/
      page.tsx             ← página completa do post (URL direta ou refresh)
    (.)posts/[id]/
      page.tsx             ← interceptada: renderiza como modal ao navegar do feed

O prefixo (.) significa "interceptar essa rota do mesmo nível." Use (..) para um nível acima, (...) para a raiz.

Quando o usuário clica num post no feed: a rota interceptada exibe um modal. Quando o usuário dá refresh ou compartilha a URL: a página completa do post renderiza.

Mesma URL. Renderização diferente. Sem truques de JavaScript.


Streaming com Suspense

O App Router suporta streaming sem nenhuma configuração.

// app/dashboard/page.tsx
import { Suspense } from 'react'

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />
      </Suspense>
      <Suspense fallback={<ActivitySkeleton />}>
        <RecentActivity />
      </Suspense>
    </div>
  )
}
// Stats é um Server Component que busca seus próprios dados
async function Stats() {
  const data = await getStats() // query lenta, não bloqueia nada mais
  return <StatsCard data={data} />
}

O HTML da página começa a ser enviado imediatamente. Os slots <Stats /> e <RecentActivity /> aparecem conforme os dados resolvem. O usuário vê conteúdo rápido — não um spinner cobrindo a página inteira.

Cada boundary de Suspense é independente. Uma getStats() lenta não impede RecentActivity de renderizar.


Route Groups: Organize Sem Afetar as URLs

Use nomes (pasta) para agrupar rotas sem adicionar segmentos à URL.

app/
  (marketing)/
    layout.tsx          ← layout de marketing (público, sem auth)
    page.tsx/
    about/
      page.tsx/about
  (app)/
    layout.tsx          ← layout da aplicação (autenticado)
    dashboard/
      page.tsx/dashboard

Os dois grupos ficam dentro de app/, mas (marketing) e (app) são invisíveis na URL. Você pode aplicar layouts completamente diferentes — nav diferente, verificações de auth diferentes, providers diferentes — sem afetar a estrutura das URLs.


Erros Comuns

1. Colocar 'use client' em tudo

// ❌ Errado — perde todos os benefícios de renderização no servidor
'use client'

export default async function ProductList() {
  const products = await db.product.findMany()
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}
// ✅ Correto — Server Component, sem diretiva necessária
export default async function ProductList() {
  const products = await db.product.findMany()
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}

2. Ignorar o loading.tsx

Um arquivo loading.tsx em qualquer pasta de rota envolve a página automaticamente em um boundary de Suspense. Você não precisa adicionar isso manualmente.

// app/dashboard/loading.tsx
export default function DashboardLoading() {
  return <DashboardSkeleton />
}

O skeleton aparece instantaneamente enquanto a página carrega. Sem configuração extra.


3. Buscar dados em Client Components desnecessariamente

// ❌ Errado — buscando no client o que poderia buscar no servidor
'use client'
export default function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((r) => r.json())
      .then(setUser)
  }, [userId])
  return user ? <ProfileCard user={user} /> : <Spinner />
}
// ✅ Correto — Server Component busca direto, passa os dados adiante
export default async function UserProfilePage({ params }: { params: { userId: string } }) {
  const user = await db.user.findUnique({ where: { id: params.userId } })
  return <ProfileCard user={user} />
}

4. Um layout raiz gigante fazendo tudo

// ❌ Errado — busca tudo de uma vez, bloqueia a página inteira
export default async function RootLayout({ children }) {
  const user = await getUser()
  const notifications = await getNotifications()
  const settings = await getUserSettings()
  return (
    <Shell user={user} notifications={notifications} settings={settings}>
      {children}
    </Shell>
  )
}

Use layouts aninhados. Coloque o data fetching o mais próximo possível de onde ele é consumido. Se as notificações só aparecem no dashboard, busque-as em app/dashboard/layout.tsx, não na raiz.


O Que Fazer Agora

  • Audite a estrutura dos seus layouts — você está puxando o data fetching para cima sendo que ele poderia ficar mais perto do componente?
  • Encontre todo 'use client' no seu código e pergunte se realmente é necessário
  • Adicione loading.tsx nas suas rotas mais lentas
  • Experimente Rotas Paralelas para um fluxo de modal que você implementa hoje com estado

O App Router te recompensa quando você alinha a hierarquia dos seus componentes com os dados que eles precisam. Quando esse modelo mental encaixa, boa parte da complexidade do data fetching no lado do cliente simplesmente desaparece — e você vai se perguntar por que não fazia isso antes.