Estágio 02 · 02-05
LockedNext.js é o framework de fato pra apps React em produção. Mas o que ele faz vai além de "React com SSR", é um sistema de roteamento, render híbrido (server/client/edge), múltiplas camadas de cache, otimização automática de assets, primitivas de RSC. Cada uma dessas peças tem regras sutis, e times perdem dias debugando comportamento esperado de cache, hydration mismatches, ou diferenças entre runtimes.
Este módulo não é "tutorial Next". É o modelo mental do que acontece em cada request, como o cache decide servir HTML estático ou re-renderizar, e quando vale o trade-off de Edge runtime vs Node.
Pages Router (/pages) foi o padrão até 2022. Modelo: cada arquivo em pages/ é uma rota; getStaticProps/getServerSideProps/getStaticPaths controlam fetch.
App Router (/app, default desde Next 13.4 stable) é uma reescrita baseada em RSC. Mudanças principais:
'use client'.loading.tsx, error.tsx).Pages Router ainda funciona, ainda é mantido, e ainda tem casos onde é a escolha certa (apps simples, projetos antigos sem urgência de migrar). App Router é o caminho pra projetos novos.
Em App Router, estrutura define rotas:
app/
layout.tsx → root layout, abrange tudo
page.tsx → /
about/
page.tsx → /about
orders/
layout.tsx → layout pra /orders/*
page.tsx → /orders
[id]/
page.tsx → /orders/:id
edit/
page.tsx → /orders/:id/edit
(dashboard)/ → group, sem afetar URL
settings/
page.tsx → /settings
profile/
page.tsx → /profile
api/
orders/
route.ts → API endpoint
Convenções de arquivos:
page.tsx, UI da rota.layout.tsx, wrapper. children é a rota.loading.tsx, Suspense fallback automático.error.tsx, Error boundary.not-found.tsx, 404.template.tsx, como layout mas re-cria a cada navegação (perde state).route.ts, API endpoint (GET, POST, etc. exportados).middleware.ts (na raiz, não em app/), roda antes de todo request.Parênteses ((group)) agrupam sem afetar URL. Colchetes duplos ([[...slug]]) são catch-all opcional.
Cada componente em app/ é Server Component por default. Comportamento:
async e usar await.useState, useEffect) nem listeners (onClick).Pra ser Client Component, a primeira linha do arquivo deve ter:
'use client';
Marca o componente e tudo o que ele importa como client. RSCs podem importar Client Components; o inverso só via children ou props serializáveis.
Regra: empurre Client Components o mais pra fora possível. Se uma página é só Client porque tem um botão interativo no canto, você está mandando código demais ao cliente.
Next 14/15 tem várias camadas de cache. Em Next 15, Turbopack/refactor mudou defaults, sempre confirme version. Os layers são:
Request Memoization: dentro de uma única request (server-side), fetch com mesma URL retorna cache. Mecanismo do React, não Next.
Data Cache (server): fetch é wrapped por Next pra cachear baseado em opções (cache, next.revalidate, next.tags). Default em Next 14 era cached ("static"); Next 15 mudou pra "no cache by default". Sempre cheque versão.
Full Route Cache (build): rotas estáticas são pré-renderizadas em build e servidas como HTML. Rotas dinâmicas não.
Router Cache (client): segmentos de rota cacheados no cliente em memória. Acelera back/forward e revisits.
CDN/edge cache (deployment): Vercel/Cloudflare/etc. cacheiam respostas conforme headers.
Como controlar:
fetch(url, { cache: 'force-cache' | 'no-store' }), Data Cache.fetch(url, { next: { revalidate: 60, tags: ['orders'] } }), TTL e tag-based invalidation.export const dynamic = 'force-dynamic' | 'force-static' | 'auto' em layout/page, força comportamento da rota.export const revalidate = 60, TTL pra rota inteira.revalidatePath('/orders') ou revalidateTag('orders') em Server Action, invalida.cookies(), headers() em RSC, torna a rota dinâmica.Mental model: toda decisão de cache é "qual camada serve essa response, e quando ela invalida". Se você não consegue responder isso pra cada rota, vai apanhar.
Em App Router, o servidor manda HTML em pedaços. Suspense boundaries definem onde a stream pode "esperar" sem bloquear o resto:
<>
<Header />
<Suspense fallback={<Loading />}>
<Posts /> {/* fetch demorado */}
</Suspense>
<Footer />
</>
User vê Header, Footer e Loading imediatamente; Posts aparece quando o fetch resolve. Tempo até primeiro byte (TTFB) muito melhor que SSR clássico.
loading.tsx em App Router é shorthand: arquivo é automaticamente envolvido em <Suspense> pelo Next.
Server Actions são funções server-side chamadas direto do client (sem você escrever endpoint). Sintaxe:
async function createOrder(formData: FormData) {
'use server';
const order = await db.order.create({ ... });
revalidatePath('/orders');
return { id: order.id };
}
// no JSX:
<form action={createOrder}>
<input name="customer" />
<button>Create</button>
</form>
Por baixo: Next gera endpoint, RPC client, deserialization. Tipagem se mantém porque você importa a função.
Use cases: forms, mutations simples, "fire and forget" actions. Pra fluxos complexos (com validação rica, optimistic updates), libs como TanStack Form, React Hook Form + next-safe-action ajudam.
middleware.ts na raiz roda em Edge runtime antes de cada request matched.
Use cases típicos:
Limitações: Edge runtime é restrito (não tem todas APIs de Node, deve ser leve, sem fs, sem child_process). Rode lógica pesada em RSC, não em middleware.
Cada rota pode escolher runtime via export const runtime = 'nodejs' | 'edge'.
Node runtime:
Edge runtime:
Limitações Edge:
pg), use HTTP-based (@vercel/postgres, @neondatabase/serverless, Supabase REST, etc.).fs, crypto clássico, etc.).Decisão: comece em Node. Mude pra Edge quando o gargalo for cold start ou latência geográfica E a rota é compatível.
Route handler (route.ts):
export async function GET(req: Request) { ... }
export async function POST(req: Request) { ... }
APIs RESTful tradicionais. Quando você quer endpoint público, integração com webhook, etc.
Server Action: chamada interna de UI Next pra Next. Sintaxe mais ergonômica, sem precisar de fetch manual.
Não são mutuamente exclusivos. Use route handlers pra integrações externas; Server Actions pra mutations da própria UI.
Next otimiza assets automaticamente:
next/image: lazy load, srcset responsive, format conversion (AVIF/WebP). Reduz LCP drasticamente. Sempre use sobre <img>.next/font: download de Google Fonts ou local. Self-hosted, com font-display: swap correto, sem layout shift.next/script: load async, defer, depois de hydration, etc. Pra third-party scripts.Otimização que mais impacta Core Web Vitals em projetos típicos.
next.config.js (ou .ts) configura tudo. Pontos importantes:
images.domains ou remotePatterns, quais hosts são permitidos pra next/image.experimental flags, features pré-stable.headers(), redirects(), rewrites(), configuração de routing fora do file system.output, 'standalone' pra Docker images mínimas.Build:
next build gera .next/.○ Static, λ Dynamic, ƒ ISR, Middleware, etc.). Sempre revise build output: entender o que ficou estático vs dinâmico evita surpresas em produção.fetch com next: { revalidate, tags } em RSC pra cache controlado.useActionState (React 19) pra status pendente.cookies() pra ler session.@modal) pra modais que persistem em URL.generateStaticParams + revalidate pra híbrido SSG + ISR.A separação entre Server e Client Components é o conceito central do App Router:
await direto, acessar DB, fs, secrets. Não podem usar hooks, event handlers, browser APIs."use client"): rodam server (initial render) + client (hydration + subsequent). São JS shipped. Podem usar hooks, eventos, browser APIs.Boundary é importação: Server Component que importa Client → ok. Client que importa Server → erro. Mas você pode passar Server Component como children pra Client Component:
// ServerWrapper.tsx (server)
export default function Page() {
return <ClientShell><ServerData /></ClientShell>;
}
// ClientShell.tsx
'use client';
export default function ClientShell({ children }) {
const [open, setOpen] = useState(false);
return open ? children : <button onClick={() => setOpen(true)}>open</button>;
}
children é serialized e shipped. ServerData foi rendered no servidor; ClientShell é interactive no client.
Regras:
RSC produz árvore serializada especial (não HTML, não JSON puro):
Browser recebe HTML inicial + RSC payload. Hidratação combina ambos. Em navigation subsequent (link click), só RSC payload é fetched + diff aplicado.
Implicações:
cache() (React) e revalidateTag (Next) controlam cache de fetch interno.loading.tsx em uma rota = wrapper Suspense automático. Substitui durante load.
app/
layout.tsx
loading.tsx # ← UI fallback enquanto rota carrega
page.tsx
@modal/
default.tsx
(..)photos/[id]/page.tsx # intercepting
Parallel routes (@slot): múltiplos slots renderizando em paralelo. Use case clássico: dashboard com sidebar + main + analytics independentes.
Intercepting routes ((.), (..), (...)): override de rota baseado em from-where-came. Modal sobre photo grid em vez de full page.
Streaming patterns:
Promise.all.Server Action é função async marcada com 'use server'. Pode ser invocada de:
action={fn}.formAction={fn}.Após execução, invalidate caches:
'use server';
export async function createOrder(data) {
const order = await db.orders.create(data);
revalidatePath('/orders');
revalidateTag('orders');
return order;
}
Optimistic UI com useOptimistic (React 19):
'use client';
function Likes({ postId, count }) {
const [optimisticCount, addOptimistic] = useOptimistic(count);
return (
<form action={async () => {
addOptimistic(optimisticCount + 1);
await likePost(postId);
}}>
<button>{optimisticCount}</button>
</form>
);
}
Transitions com useTransition impedem UI lock durante action. isPending mostra spinner sem block input.
Validation: Zod no server side é padrão. Erro retornado, exibido via useActionState.
Rate limiting: Server Actions são endpoints públicos sob disfarce. Apply rate limit + auth check sempre.
Edge runtime usa Vercel Edge Functions (V8 isolates), não Node.js. Restrições:
fs, child_process, crypto.randomBytes (use crypto.subtle).bcrypt no, argon2 no. Use Web Crypto API.pg no (TCP raw), use @neondatabase/serverless ou @vercel/postgres (HTTP-based).Quando usar Edge: SEO-critical pages, geo-distributed APIs, low-latency redirects. Quando NÃO usar: heavy compute, libs nativas, long-running.
ISR = Incremental Static Regeneration. Build estático + revalidação background.
export const revalidate = 60; // segundos
Comportamento: primeira request após 60s dispara regen background; user vê stale; após regen, próximo user vê fresh.
On-demand revalidation mais responsivo:
revalidatePath('/products/[slug]');
revalidateTag('products');
Patterns:
revalidateTag('orders') invalida todas pages com fetch tagged 'orders')./api/revalidate?secret=... pra rebuild scheduled.Cache layer interaction (recall §2.4):
revalidate/tags.Mismatches comuns: dynamic = 'force-dynamic' desliga Data Cache, mas Router Cache ainda existe, user pode ver dado antigo até router.refresh().
Cold start típico Next 14:
Bun + Next: alternativa de runtime; vale testar mas suporte oficial parcial.
error.tsx em rota = error boundary. not-found.tsx = 404.
'use client';
export default function Error({ error, reset }) {
return (
<div>
<p>Erro: {error.message}</p>
<button onClick={reset}>Tentar novamente</button>
</div>
);
}
Instrumentation (instrumentation.ts):
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./otel');
}
}
Setup OpenTelemetry, Sentry, etc.
onRequestError (Next 15+) captura errors no servidor com request context.
Você precisa, sem consultar:
cookies() torna rota dinâmica e como intencionalmente forçar isso ou evitar.revalidatePath ou revalidateTag e justificar a escolha entre os dois.route.ts vs Server Action.Date.now(), Math.random(), localStorage em RSC, conditional client-only).Migrar Logística (versão 02-01/02-02 vanilla) pra Next.js App Router, com cache controlado, Server Actions, e streaming.
/, landing./dashboard, server-side aggregations (mockadas com sleep pra forçar streaming)./orders, lista server-side com Suspense progressivo./orders/[id], detalhe via await em RSC./orders/new, form com Server Action./settings/(account|preferences), parallel/intercepting opcional./dashboard: rota com revalidate: 60, tag dashboard./orders: força dynamic (lista pode mudar a qualquer momento)./orders/[id]: cached com tag order:{id}, invalidada pela Server Action de update.session. Sem session → redirect a /login./login faz Server Action que set cookie.next/image em qualquer foto.next/font carregando fonte só uma vez.getServerSideProps (App Router only)./dashboard < 200 KB JS.revalidatePath ou revalidateTag.generateStaticParams pra detalhes de pedidos populares.output: 'standalone' simplifica imagem Docker.Destrava
02-05 é prereq dos seguintes módulos: