Navegação
Navegue entre rotas usando o componente <Link>, useRouter programático, hooks de leitura de URL e redirect no lado do servidor.
Busque em todas as páginas da documentação
Navegue entre rotas usando o componente <Link>, useRouter programático, hooks de leitura de URL e redirect no lado do servidor.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
// Navegação declarativa
import Link from "next/link";
<Link href="/dashboard">Dashboard</Link>
// Navegação programática (Componente Cliente)
"use client";
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/dashboard");
// Ler URL atual (Componente Cliente)
import { usePathname, useSearchParams } from "next/navigation";
const pathname = usePathname(); // "/dashboard"
const searchParams = useSearchParams(); // Instância URLSearchParams
// Redirect no lado do servidor (Componente Servidor ou Ação de Servidor)
import { redirect } from "next/navigation";
redirect("/login");Quando usar isso: Sempre que precisar se mover entre páginas, ler a URL atual ou redirecionar usuários com base em condições.
// components/nav-bar.tsx - Navegação com destaque de link ativo
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
const links = [
{ href: "/", label: "Home" },
{ href: "/dashboard", label: "Dashboard" },
{ href: "/settings", label: "Settings" },
];
export function NavBar() {
const pathname = usePathname();
return (
<nav className="flex gap-4 border-b px-6 py-3">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
className={
pathname === link.href
? "font-bold text-blue-600"
: "text-gray-600 hover:text-gray-900"
}
>
{link.label}
</Link>
))}
</nav>
);
}// components/search-filter.tsx - Parâmetros de busca para filtragem
"use client";
import { useSearchParams, usePathname, useRouter } from "next/navigation";
import { useCallback } from "react";
export function SearchFilter() {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const currentQuery = searchParams.get("q") ?? "";
const currentSort = searchParams.get("sort") ?? "newest";
const updateParams = useCallback(
(key: string, value: string) => {
const params = new URLSearchParams(searchParams.toString());
if (value) {
params.set(key, value);
} else {
params.delete(key);
}
router.push(`${pathname}?${params.toString()}`);
},
[searchParams, pathname, router]
);
return (
<div className="flex gap-4">
<input
type="text"
placeholder="Search..."
defaultValue={currentQuery}
onChange={(e) => updateParams("q", e.target.value)}
className="rounded border px-3 py-2"
/>
<select
value={currentSort}
onChange={(e) => updateParams("sort", e.target.value)}
className="rounded border px-3 py-2"
>
<option value="newest">Newest</option>
<option value="oldest">Oldest</option>
<option value="popular">Popular</option>
</select>
</div>
);
}// app/login/page.tsx - Redirect no lado do servidor após verificação de autenticação
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
export default async function LoginPage() {
const session = await auth();
if (session) redirect("/dashboard");
return (
<form action="/api/auth/login" method="POST">
<input name="email" type="email" placeholder="Email" />
<input name="password" type="password" placeholder="Password" />
<button type="submit">Log In</button>
</form>
);
}// Navegação programática após envio de formulário
"use client";
import { useRouter } from "next/navigation";
import { useState, useTransition } from "react";
export function CreatePostForm() {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [title, setTitle] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("/api/posts", {
method: "POST",
body: JSON.stringify({ title }),
headers: { "Content-Type": "application/json" },
});
const post = await res.json();
startTransition(() => {
router.push(`/blog/${post.slug}`);
router.refresh(); // Revalidar dados do servidor
});
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Post title"
className="rounded border px-3 py-2"
/>
<button
type="submit"
disabled={isPending}
className="ml-2 rounded bg-blue-600 px-4 py-2 text-white"
>
{isPending ? "Creating..." : "Create"}
</button>
</form>
);
}<Link> faz pré-busca por padrão. Quando um <Link> entra na viewport, o Next.js faz a pré-busca da rota em segundo plano. Rotas estáticas são totalmente pré-buscadas; rotas dinâmicas pré-buscam até o limite mais próximo de loading.tsx.<Link> realiza navegação no lado do cliente. Ele intercepta o clique, atualiza a URL e renderiza a nova rota sem recarregar a página inteira. Apenas os segmentos alterados são re-renderizados.useRouter fornece navegação imperativa. push(), replace(), back(), forward() e refresh() permitem navegar programaticamente.router.refresh() re-busca dados do servidor. Ele revalida os Server Components da rota atual sem perder o estado do lado do cliente (valores de entrada, posição de rolagem).usePathname() retorna o caminho atual. É reativo - o componente re-renderiza quando a URL muda. Não inclui parâmetros de busca ou hash.useSearchParams() retorna um URLSearchParams somente leitura. Para atualizar parâmetros de busca, construa uma nova string de URL e use router.push().redirect() lança internamente. Deve ser chamado fora de blocos try/catch (ou usar unstable_rethrow no catch). O código após redirect() nunca é executado.redirect() usa 307 (temporário) por padrão. Use redirect(url, RedirectType.replace) para 308 (permanente) ou use permanentRedirect().useRouter, usePathname e useSearchParams requerem a diretiva "use client".// Link com href dinâmico e controle de pré-busca
import Link from "next/link";
// Desativar pré-busca para links raramente visitados
<Link href="/terms" prefetch={false}>Terms</Link>
// href dinâmico com template literal
<Link href={`/blog/${slug}`}>Read More</Link>
// Link com replace (sem nova entrada no histórico)
<Link href="/dashboard" replace>Dashboard</Link>
// Link com scroll={false} para preservar a posição de rolagem
<Link href="/dashboard?tab=settings" scroll={false}>Settings Tab</Link>// permanentRedirect - redirect 308
import { permanentRedirect } from "next/navigation";
export default async function OldPage() {
permanentRedirect("/new-page");
}// redirect em Ações de Servidor
"use server";
import { redirect } from "next/navigation";
export async function createPost(formData: FormData) {
const post = await db.post.create({
data: { title: formData.get("title") as string },
});
redirect(`/blog/${post.slug}`);
}// useSelectedLayoutSegment - ler segmento filho ativo
"use client";
import { useSelectedLayoutSegment } from "next/navigation";
export function TabNav() {
const segment = useSelectedLayoutSegment();
// Em /dashboard/analytics → segment = "analytics"
// Em /dashboard → segment = null
return (
<nav>
<Link
href="/dashboard"
className={segment === null ? "font-bold" : ""}
>
Overview
</Link>
<Link
href="/dashboard/analytics"
className={segment === "analytics" ? "font-bold" : ""}
>
Analytics
</Link>
</nav>
);
}// props do componente Link
import type { LinkProps } from "next/link";
// Props principais: href (string ou UrlObject), replace, scroll, prefetch
// tipo de retorno do useRouter
import { useRouter } from "next/navigation";
// router.push(href: string, options?: { scroll?: boolean }): void
// router.replace(href: string, options?: { scroll?: boolean }): void
// router.refresh(): void
// router.back(): void
// router.forward(): void
// router.prefetch(href: string): void
// assinatura da função redirect
import { redirect, permanentRedirect, RedirectType } from "next/navigation";
// redirect(url: string, type?: RedirectType): never
// permanentRedirect(url: string, type?: RedirectType): never
// Objeto URL para hrefs complexos
<Link
href={{
pathname: "/blog/[slug]",
query: { slug: "hello-world" },
}}
>
Read Post
</Link>useRouter é de next/navigation, não next/router. A versão next/router é para o Pages Router e não funcionará no App Router.redirect() lança um erro internamente. Se chamado dentro de um try/catch, ele será capturado. Use unstable_rethrow(error) em blocos catch que possam interceptá-lo.useSearchParams() deve ser envolvido em <Suspense>. No Next.js 15+, usar useSearchParams() sem um limite Suspense faz com que toda a rota opte pela renderização no lado do cliente. Envolva o componente que o utiliza em <Suspense>.router.push() não aguarda. A navegação é assíncrona, mas o método retorna void. Use useTransition para rastrear o estado pendente.<Link> faz pré-busca ao entrar na viewport. Isso pode causar requisições de rede inesperadas. Use prefetch={false} para links que os usuários raramente clicam.router.refresh() não limpa o cache do roteador. Ele apenas re-busca os dados do servidor da página atual. Outras rotas em cache permanecem desatualizadas.searchParams não acionam loading.tsx. Apenas mudanças de segmento (caminho diferente) acionam o limite de carregamento. Mudanças em parâmetros de busca re-renderizam sem a UI de carregamento.redirect() em um layout se aplica a todas as páginas filhas. Tenha cuidado ao colocar redirects condicionais em layouts - eles rodam para cada rota filha.scroll em <Link> é true por padrão. A navegação rola para o topo por padrão. Defina scroll={false} para navegação tipo aba ou listas filtradas.| Abordagem | Quando Usar |
|---|---|
Tag <a> | Links externos ou quando você precisa de um recarregamento completo da página |
window.location | Saindo do SPA - recarregamento completo para uma URL diferente |
| Middleware redirect | Redirecionando antes que qualquer renderização ocorra |
next.config.js redirects | Regras de redirect estáticas sem lógica de tempo de execução |
Server Action com redirect() | Redirecionando após uma mutação |
revalidatePath ou revalidateTag | Atualizando dados sem navegação |
De uma aplicação SaaS Next.js 15 / React 19 em produção (SystemsArchitect.io).
// Exemplo de produção: Construtor de URL centralizado
// Arquivo: src/lib/url-builder.ts
import { generateSlug } from './slug-utils';
export function buildServiceUrlFromSlug(slug: string): string {
return `/services/${slug}`;
}
export function buildSectionUrl(serviceSlug: string, sectionId: string): string {
const cleanSectionId = stripServicePrefix(sectionId, serviceSlug);
return `/services/${serviceSlug}/${cleanSectionId}`;
}
export function buildPointUrl(
serviceSlug: string,
sectionId: string,
pointSlug: string
): string {
const cleanSectionId = stripServicePrefix(sectionId, serviceSlug);
const cleanPointSlug = stripSectionPrefix(pointSlug, sectionId);
return `/services/${serviceSlug}/${cleanSectionId}/pt/${cleanPointSlug}`;
}
export function buildPointTabUrl(
serviceSlug: string,
sectionId: string,
pointSlug: string,
tabSlug: string | null
): string {
const baseUrl = buildPointUrl(serviceSlug, sectionId, pointSlug);
if (tabSlug === null) return baseUrl;
return `${baseUrl}/${tabSlug}`;
}
export function parseServiceUrl(url: string): string | null {
const match = url.match(/^\/services\/([^\/]+)\/?$/);
return match ? match[1] : null;
}O que isso demonstra em produção:
/services/... são definidos. Alterar o padrão aqui atualiza todo o aplicativostripServicePrefix e stripSectionPrefix limpam IDs de banco de dados que podem conter prefixos redundantes (normalização de dados legados)parseServiceUrl é o inverso, extraindo um slug de uma URL usando regexpt/ em URLs de pontos distingue pontos de seções na hierarquia de roteamento<Link> é declarativo, faz pré-busca por padrão e é a abordagem padrão para a maioria das navegaçõesuseRouter é imperativo, usado para navegação programática após eventos como envio de formuláriosImporte de next/navigation, não de next/router. A versão next/router é para o Pages Router e não funcionará em componentes do App Router.
Quando um <Link> entra na viewport, o Next.js faz a pré-busca da rota em segundo plano. Rotas estáticas são totalmente pré-buscadas. Rotas dinâmicas pré-buscam até o limite mais próximo de loading.tsx. Use prefetch={false} para links que os usuários raramente clicam.
Ele re-busca os Server Components da rota atual sem perder o estado do lado do cliente (valores de entrada, posição de rolagem). Ele não limpa o cache do roteador para outras rotas.
redirect() lança um erro internamente, então o bloco catch o intercepta. Use unstable_rethrow(error) em blocos catch que possam acidentalmente capturar redirect() ou notFound().
No Next.js 15+, usar useSearchParams() sem um limite <Suspense> faz com que toda a rota opte pela renderização no lado do cliente. Envolva o componente que o utiliza em <Suspense>.
"use client";
import { useSearchParams, usePathname, useRouter } from "next/navigation";
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const params = new URLSearchParams(searchParams.toString());
params.set("sort", "newest");
router.push(`${pathname}?${params.toString()}`);Não. Apenas mudanças de segmento (caminho diferente) acionam o limite de carregamento. Mudanças de parâmetros de busca re-renderizam a página sem mostrar a UI de carregamento.
redirect() envia um redirect 307 (temporário) por padrãopermanentRedirect() envia um redirect 308 (permanente)import { useRouter } from "next/navigation";
const router = useRouter();
// router.push(href: string, options?: { scroll?: boolean }): void
// router.replace(href: string, options?: { scroll?: boolean }): void
// router.refresh(): void
// router.back(): void
// router.forward(): void
// router.prefetch(href: string): voidUse useTransition já que router.push() retorna void e não aguarda.
const [isPending, startTransition] = useTransition();
startTransition(() => {
router.push("/dashboard");
});
// isPending é true durante a navegaçãoEla é true por padrão, rolando para o topo da página na navegação. Defina scroll={false} para navegação tipo aba ou listas filtradas onde você deseja preservar a posição de rolagem.
"use client";
import { useSelectedLayoutSegment } from "next/navigation";
export function TabNav() {
const segment = useSelectedLayoutSegment();
// /dashboard/analytics -> "analytics"
// /dashboard -> null
return (
<Link
href="/dashboard/analytics"
className={segment === "analytics" ? "font-bold" : ""}
>
Analytics
</Link>
);
}Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥