Adios
BlogNext.js

Next.js

Como criar um site Next.js para produção: App Router, dados, cache e implantação

Crie um site Next.js para produção com App Router, Server Components, cache explícito, alterações seguras, SEO, testes e implantação.

Equipe AdiosAtualizado 17 de julho de 202624 min de leitura

Um site Next.js em produção vai além de um conjunto de componentes React. Roteamento, separação entre servidor e cliente, validade dos dados, metadados, estados de falha e contrato de implantação precisam ser coerentes entre si.

1. Decida o que o site deve fazer

Comece pelas responsabilidades do site, não pela biblioteca de componentes. Liste rotas públicas e privadas, fontes de conteúdo, alterações, requisitos de busca e serviços externos. Isso define quais páginas podem ser estáticas, quais precisam de dados a cada requisição e onde deve ocorrer a autorização.

Em um site típico de produto, as páginas de marketing e a documentação mudam devagar, o blog é gerado a partir de conteúdo e a área da conta depende do usuário conectado. São três ciclos de vida de dados diferentes. Tratar tudo como uma aplicação de página única renderizada no cliente descarta renderização útil no servidor; tratar tudo como HTML estático inviabiliza a área da conta.

Escreva o mapa de rotas antes da implementação. Dê a cada página importante uma função principal e uma URL canônica. Defina quais entidades precisam de segmentos dinâmicos, como /blog/[slug], e qual interface deve persistir na navegação em um layout compartilhado.

  • —Conteúdo público: início, produto, preços, sobre, blog, guias e páginas jurídicas.
  • —Conteúdo da aplicação: painel, configurações, faturamento ou outras rotas dependentes da sessão.
  • —Limites de dados: arquivos locais, um CMS, um banco de dados, APIs externas e entrada do usuário.
  • —Necessidades operacionais: variáveis de ambiente, verificações de saúde, logs, tarefas agendadas e implantação.

2. Criar o projeto com escolhas padrão conscientes

Este guia usa Next.js 16 App Router, TypeScript e um diretório src. As escolhas padrão atuais de create-next-app são adequadas para um novo projeto, mas opções explícitas tornam a configuração reproduzível para a equipe e a CI.

Execute o servidor de desenvolvimento e faça uma compilação de produção imediatamente. Compilar no primeiro dia revela problemas de versão do Node.js, importações incompatíveis e erros de configuração antes que o projeto cresça em torno deles. Mantenha o arquivo de lock do npm no controle de versão e use npm ci em compilações automatizadas para tornar a resolução de dependências reproduzível.

Terminal

npx create-next-app@latest northstar \
  --ts --tailwind --eslint --app --src-dir \
  --import-alias "@/*"

cd northstar
npm run dev
npm run build

3. Organizar as rotas em torno das jornadas dos usuários

O App Router transforma pastas em segmentos de URL. page.tsx torna uma rota pública; layout.tsx envolve o segmento e seus descendentes; loading.tsx fornece conteúdo provisório para streaming; error.tsx captura erros de renderização não tratados; e not-found.tsx lida com recursos ausentes. Mantenha os arquivos perto da rota responsável por eles, em vez de criar uma pasta global de componentes sem limites claros.

Grupos de rotas como (marketing) e (app) organizam pastas sem alterar a URL. Eles são úteis quando o site público e o produto autenticado precisam de layouts diferentes. Segmentos dinâmicos como [slug] recebem parâmetros de rota, enquanto segmentos catch-all como [...parts] capturam vários níveis.

Use rotas paralelas e de interceptação avançadas apenas quando a interação exigir. Uma página de foto compartilhável que abre em um modal durante a navegação interna é um bom caso; uma página comum de configurações, não. A árvore de rotas mais simples que corresponde ao modelo mental do usuário geralmente é a mais fácil de depurar.

A practical App Router structure

src/app/
├── layout.tsx
├── globals.css
├── (marketing)/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── pricing/page.tsx
│   └── blog/
│       ├── page.tsx
│       └── [slug]/page.tsx
├── (app)/
│   ├── dashboard/page.tsx
│   ├── settings/page.tsx
│   └── loading.tsx
├── api/health/route.ts
├── not-found.tsx
└── global-error.tsx

4. Manter o layout raiz pequeno e estável

O layout raiz é obrigatório e contém os elementos html e body. Coloque nele o que realmente é global: idioma do documento, variáveis de fontes do site, CSS global, um provedor de tema quando necessário e metadados padrão compartilhados. Não acumule ali consultas específicas de rotas nem uma grande árvore de provedores cliente.

Layouts persistem durante a navegação no cliente, por isso são adequados para navegação e estruturas estáveis. O arquivo template.tsx funciona de outra forma: recebe uma nova chave e é remontado quando seu segmento muda. Escolha um template apenas quando quiser reiniciar na navegação, por exemplo para repetir uma animação de entrada ou redefinir o estado local.

src/app/layout.tsx

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"], display: "swap" });

export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),
  title: { default: "Northstar", template: "%s | Northstar" },
  description: "Planning software for focused product teams.",
};

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

5. Usar Server Components por padrão

Páginas e layouts são Server Components, a menos que você marque um módulo com use client. Eles podem consultar o banco de dados, ler variáveis de ambiente exclusivas do servidor, chamar serviços internos e enviar conteúdo renderizado sem adicionar o código do componente ao bundle do navegador.

Adicione um Client Component no menor escopo interativo útil. Um filtro pode precisar de estado, manipuladores de eventos e APIs de URL; a grade de produtos abaixo dele pode continuar como Server Component. Marcar a página inteira como Client Component transfere ao navegador mais JavaScript e mais responsabilidade pela busca de dados do que a interação exige.

As props que passam do servidor ao cliente precisam ser serializáveis. Envie um modelo de visualização restrito, não um registro do banco com campos privados. Importe server-only nos módulos de dados que nunca devem entrar no bundle do cliente; isso transforma uma violação acidental desse limite em erro de compilação.

A small client island inside a server-rendered page

// src/app/products/page.tsx — Server Component
import { getProducts } from "@/lib/data";
import ProductFilters from "./product-filters";

export default async function ProductsPage() {
  const products = await getProducts();
  return <ProductFilters products={products} />;
}

// src/app/products/product-filters.tsx — Client Component
"use client";
import { useState } from "react";

export default function ProductFilters({ products }) {
  const [query, setQuery] = useState("");
  const visible = products.filter((product) =>
    product.name.toLowerCase().includes(query.toLowerCase()),
  );

  return <>{/* input and product list */}</>;
}

6. Obter os dados onde são renderizados

Um Server Component assíncrono pode chamar fetch, um ORM ou um cliente de banco de dados diretamente. Isso evita criar um endpoint HTTP interno apenas para que o código renderizado no servidor chame a si mesmo. Mantenha a consulta em um módulo de acesso a dados exclusivo do servidor quando várias rotas a usarem ou quando autorização e definição da saída precisarem de um ponto central auditado.

Evite sequências desnecessárias de requisições. Se duas consultas são independentes, inicie-as juntas com Promise.all. Se apenas um componente filho precisa de dados mais demorados, deixe que ele os busque e coloque um limite Suspense ao seu redor. Assim, a página envia HTML útil enquanto a região mais lenta termina.

Defina quão atual cada resultado precisa estar antes de adicionar cache. Dados de conta específicos do usuário geralmente devem ser consultados a cada requisição. Uma tabela pública de preços pode ficar em cache. Um catálogo pode usar tags para invalidar o cache após um editor publicar uma alteração.

Parallel server-side data fetching

import "server-only";

export default async function DashboardPage() {
  const [account, activity] = await Promise.all([
    getAccount(),
    getRecentActivity(),
  ]);

  return <Dashboard account={account} activity={activity} />;
}

7. Tratar o cache como uma decisão de produto

No Next.js 16, Cache Components é opcional. Com cacheComponents habilitado, a diretiva use cache pode armazenar em cache uma função ou componente assíncrono. cacheLife define sua duração; cacheTag associa entradas relacionadas a uma chave compartilhada de invalidação; e um limite Suspense transmite o trabalho sem cache executado a cada requisição junto da estrutura em cache.

O cache não está automaticamente correto só porque o conteúdo é público. Verifique o que acontece quando o resultado fica obsoleto, como ele é invalidado, se a plataforma de implantação compartilha o cache e se algum valor da sessão pode entrar na chave. Nunca armazene o resultado privado de um usuário com uma chave cujo conteúdo outro usuário possa receber.

Use updateTag em uma Server Action quando o mesmo usuário precisar ver uma alteração imediatamente. Use revalidateTag com um perfil adequado quando stale-while-revalidate for aceitável, ou revalidatePath quando o escopo natural da invalidação for uma página ou layout. Mantenha a invalidação junto da alteração que torna os dados em cache obsoletos.

next.config.ts and src/lib/products.ts

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = { cacheComponents: true };
export default nextConfig;

// src/lib/products.ts
import "server-only";
import { cacheLife, cacheTag } from "next/cache";

export async function getProducts() {
  "use cache";
  cacheLife("hours");
  cacheTag("products");
  return db.product.findMany();
}

8. Projetar estados de carregamento, vazio, erro e recurso ausente

O caminho de sucesso é apenas um estado. loading.tsx cria um limite Suspense na rota e permite mostrar conteúdo provisório imediatamente durante a navegação. Adicione limites Suspense menores quando regiões independentes devam aparecer separadamente. Um esqueleto útil preserva o layout final, em vez de substituir a página por um indicador de carregamento que cause um grande deslocamento.

Falhas esperadas fazem parte do fluxo normal de controle. Um erro de validação deve retornar um resultado tipado ao formulário. Um registro ausente deve chamar notFound. Uma falha de permissão deve gerar a resposta ou o redirecionamento apropriado. Reserve error.tsx para exceções não tratadas, registre o erro com contexto suficiente da requisição e da versão para investigá-lo e ofereça ao usuário uma ação segura de recuperação.

Resultados vazios não são erros. Uma conta nova sem projetos deve explicar o que aparece naquela página e como criar o primeiro projeto. Essa distinção facilita a compreensão da interface e mantém o monitoramento focado em falhas reais.

src/app/blog/[slug]/page.tsx

import { notFound } from "next/navigation";

export default async function PostPage({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);

  if (!post) notFound();

  return <article>{/* post content */}</article>;
}

9. Tratar alterações e endpoints HTTP explicitamente

Server Actions são adequadas para alterações iniciadas pela interface React. Marque a função com use server, valide FormData no servidor, verifique sessão e permissão do usuário, grave a alteração e invalide o cache afetado. Trate cada Server Action exportada como um endpoint público: esconder o botão não é autorização.

Use Route Handlers para interfaces HTTP que vão além de um formulário React, como webhooks, controles de saúde, respostas de arquivos e endpoints usados por aplicações móveis ou terceiros. Exporte handlers para os verbos HTTP necessários e retorne objetos Web Response padrão. Valide assinaturas antes de interpretar campos confiáveis do webhook e torne idempotentes as operações sujeitas a novas tentativas.

Mantenha efeitos colaterais fora da renderização. Enviar e-mails, cobrar um cartão ou gravar eventos de analytics no corpo de um componente pode acontecer mais de uma vez. Execute essas ações no fluxo de alteração, registre estado suficiente para permitir novas tentativas seguras e envie tarefas demoradas para uma fila quando a requisição não deve aguardá-las.

src/app/projects/actions.ts

"use server";

import { updateTag } from "next/cache";

export async function createProject(formData: FormData) {
  const session = await verifySession();
  if (!session) throw new Error("Unauthorized");

  const input = projectSchema.parse({
    name: formData.get("name"),
  });

  await db.project.create({ data: { ...input, ownerId: session.userId } });
  updateTag("projects");
}

10. Incorporar metadados de busca e compartilhamento a cada rota

A otimização para busca começa com uma página útil e acessível aos robôs e uma URL estável. Dê a cada rota indexável um título específico, uma descrição clara, um H1 visível, subtítulos relevantes, links descritivos e conteúdo que responda plenamente à consulta. Metadados não compensam uma página pobre em conteúdo nem várias URLs publicando o mesmo conteúdo.

Exporte um objeto estático de metadados quando os valores forem fixos. Use generateMetadata quando título, descrição, URL canônica ou imagem dependerem dos dados da rota. Defina metadataBase uma vez no layout raiz para resolver corretamente URLs relativas de imagens sociais e URLs canônicas. Gere sitemap e robots a partir da mesma fonte que define as rotas públicas e exclua URLs privadas ou duplicadas da indexação.

Adicione Article, Product, BreadcrumbList ou outro tipo JSON-LD apropriado somente quando o conteúdo visível da página o justificar. Os dados estruturados devem descrever o que o leitor realmente vê. Valide-os após a renderização e atualize dateModified quando houver mudanças substanciais no conteúdo.

  • —Use app/robots.ts e app/sitemap.ts para gerar os arquivos destinados aos robôs de busca.
  • —Adicione arquivos opengraph-image e twitter-image quando uma rota precisar gerar imagens para compartilhamento social.
  • —Redirecione URLs desativadas e escolha um host canônico, um protocolo e uma política de barra final.
  • —Conecte páginas relacionadas com links de texto descritivo para que usuários e robôs de busca possam descobri-las.

Metadata for a dynamic article

import type { Metadata } from "next";

export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);

  if (!post) return { title: "Article not found" };

  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: "/blog/" + post.slug },
    openGraph: {
      type: "article",
      title: post.title,
      description: post.excerpt,
      images: [post.ogImage],
    },
  };
}

11. Proteger o desempenho no limite do componente

Next.js oferece divisão de código por rota, Server Components, pré-carregamento, otimização de imagens e ferramentas de fontes, mas as escolhas da aplicação ainda determinam o resultado. Observe o limite do cliente: a diretiva use client inclui aquele módulo e suas importações cliente no grafo do navegador. Bibliotecas grandes de gráficos, editores, mapas e pacotes de analytics exigem estratégias conscientes de carregamento.

Use next/image com dimensões reais ou um contêiner fill controlado para que o navegador reserve espaço. Use next/font para hospedar e pré-carregar as fontes de que o design realmente precisa. Carregue scripts de terceiros com next/script e a estratégia menos agressiva que ainda atenda à necessidade do negócio.

Meça o comportamento em produção, além do servidor de desenvolvimento. Use Lighthouse em testes de laboratório, colete Core Web Vitals de visitas reais, inspecione consultas lentas do servidor e analise o bundle do cliente quando uma rota perder desempenho. Metas de desempenho são mais úteis quando especificam uma rota e uma métrica, em vez de uma pontuação única para todo o site.

A responsive image with reserved space

import Image from "next/image";

<Image
  src="/product-dashboard.png"
  alt="Northstar dashboard showing the weekly plan"
  width={1600}
  height={900}
  sizes="(max-width: 768px) 100vw, 800px"
  priority
/>

12. Colocar a autenticação ao lado do acesso aos dados

A autenticação comprova a identidade; a autorização determina o que ela pode fazer. Use uma biblioteca de autenticação com manutenção ativa, a menos que o produto tenha uma forte razão para cuidar de senhas, rotação de sessões, recuperação de contas e integração com provedores. Guarde os dados da sessão em cookies seguros HTTP-only e mantenha as leituras sensíveis no servidor.

Centralize a autorização segura em uma camada de acesso a dados e verifique-a novamente em cada Server Action e Route Handler. O Proxy pode fazer redirecionamentos otimistas perto do limite da rota, mas não é a única proteção: usuários ainda podem chamar diretamente os endpoints públicos de alteração, e o código servidor aninhado precisa de suas próprias verificações.

Somente variáveis de ambiente com o prefixo NEXT_PUBLIC_ devem entrar no código do navegador. Considere esses valores públicos desde a compilação. Marque módulos de dados privados com server-only, retorne DTOs restritos para Client Components, valide todas as entradas e escape ou sanitize conteúdo rico não confiável antes de renderizá-lo.

  • —Use cookies secure, HTTP-only e same-site para as sessões quando o desenho da autenticação permitir.
  • —Verificar a propriedade ou o papel no ponto de cada leitura e escrita sensível.
  • —Verifique as assinaturas dos webhooks sobre o corpo bruto da requisição quando o provedor exigir.
  • —Adicione uma Política de Segurança de Conteúdo que corresponda aos scripts e recursos que o site realmente carrega.
  • —Nunca coloque segredos, registros privados do banco de dados ou detalhes irrestritos de erros nas props de Client Components.

13. Testar o comportamento no nível certo

Teste regras de negócio puras com testes unitários sem envolver Next.js. Use testes de integração para a camada de dados, Server Actions e Route Handlers com limites realistas. Reserve testes de navegador para as poucas jornadas cuja falha impediria o usuário de prosseguir: entrar, criar o recurso principal, concluir o checkout ou publicar conteúdo.

A acessibilidade deve fazer parte da implementação e da revisão, não apenas de uma verificação final. Use elementos semânticos, foco de teclado visível, rótulos associados aos formulários, mensagens de erro úteis, contraste suficiente e respeito à preferência por movimento reduzido. Testes automatizados detectam parte dos problemas; verificações com teclado e leitor de tela revelam o que a árvore de componentes sozinha não mostra.

Inclua a compilação de produção na integração contínua. No Next.js 16, next build deixou de executar o linter, então execute lint, verificação de tipos, testes e compilação de produção como comandos explícitos. Inicie a aplicação compilada e faça testes básicos de saúde e rotas críticas antes da promoção.

A straightforward CI verification sequence

npm ci
npm run lint
npx tsc --noEmit
npm test
npm run build
npm start

14. Implantar o processo que você testou

Uma aplicação Next.js renderizada no servidor precisa de um ambiente Node.js compatível, da saída de compilação, de variáveis de ambiente, de um comando de inicialização e de uma verificação de saúde. Compile a partir de uma cópia limpa do repositório com o arquivo de lock registrado. Guarde valores privados no armazenamento de segredos da plataforma de implantação, não no repositório ou na imagem.

Uma rota de saúde deve informar se esta versão pode receber tráfego. Mantenha a verificação pouco custosa e não retorne segredos ou detalhes internos. Inspecione os logs de compilação e execução separadamente, verifique a rota gerada e conecte o domínio personalizado e o TLS gerenciado. Se a aplicação usa disco local, tarefas em segundo plano, otimização de imagens ou Cache Components, confirme que a hospedagem suporta o comportamento escolhido.

A Adios executa o servidor de produção normal do Next.js como um processo Node.js persistente. O manifesto de implantação mantém compilação, inicialização, porta, ambiente de execução e contrato de saúde junto do código-fonte, permitindo revisar as mesmas premissas antes da publicação de uma versão.

adios.yaml

name: northstar
region: de
replicas: 1

build_cmd: npm ci && npm run build
start_cmd: npm start

runtime:
  name: node@24
  port: 3000
  health_path: /api/health
  memory_mb: 1024

env:
  DATABASE_URL: secret://DATABASE_URL
  AUTH_SECRET: secret://AUTH_SECRET

15. Usar uma lista de verificação de versões

A revisão final deve conectar o comportamento do produto ao da execução. Teste uma compilação limpa, acesso direto a cada rota crítica, navegação cliente entre layouts, dependência lenta, erro de validação esperado, erro não tratado, registro ausente e requisição não autorizada. Examine o código-fonte HTML das páginas públicas para confirmar que conteúdo importante e metadados estão presentes sem esperar pelo JavaScript do cliente.

Depois teste a recuperação. Pare uma dependência obrigatória, envie a mesma alteração duas vezes, rotacione um segredo e implante uma versão que falhe na verificação de saúde. Um site está pronto quando a equipe consegue explicar como ele inicia, como falha, como os usuários ficam protegidos durante a falha e como a última versão saudável permanece disponível.

  • —Rotas: URLs canônicas, redirecionamentos, comportamento 404, sitemap e regras de robôs estão corretas.
  • —Renderização: os limites entre servidor e cliente são intencionais, e as seções lentas transmitem conteúdo provisório útil.
  • —Dados: cache, invalidação, autorização, estados vazios e estados de erro correspondem ao produto.
  • —Qualidade: lint, tipos, testes, acessibilidade, compilação de produção e testes básicos passam.
  • —Operações: segredos, saúde, logs, domínio, TLS, rollback e responsáveis estão documentados.
Todos os artigos