View Transitions no Next.js 16: as três camadas
Uma é Baseline, outra não roda no Firefox e a terceira é experimental no React. Saber qual você está usando decide o que vai para produção.
- next.js
- react

Sumário
- Camada 1: same-document, e por que ela é a única sem ressalva
- Camada 2: cross-document, e o que o Firefox faz com ela
- Camada 3: o <ViewTransition> do React
- O aviso que muda a decisão
- As seis limitações do componente React
- As quatro receitas, e o que cada uma comunica
- Quatro armadilhas que a doc avisa
- O que eu usaria hoje em produção
- Perguntas frequentes
- Fontes
"View Transitions" virou nome de três coisas diferentes, com três níveis de maturidade. Confundi-las é o que faz alguém colocar em produção uma API que a documentação do React chama de não pronta para produção.
| Camada | O que é | Status hoje |
|---|---|---|
document.startViewTransition() | Transição dentro da mesma página | Baseline — Chrome 111+, Safari 18+, Firefox 144+ |
At-rule @view-transition | Transição entre páginas (MPA) | Não é Baseline — o Firefox ignora |
Componente React <ViewTransition> | Transição orquestrada pelo React | Experimental — só Canary |
O Next.js 16 dá acesso às três, porque o App Router roda a última versão Canary do React. Isso é conveniente e perigoso na mesma medida: você consegue usar o componente sem perceber que ele não está estável.
Camada 1: same-document, e por que ela é a única sem ressalva
É a única das três que passou de Baseline neste ano.
A API é uma função só. Você entrega uma callback que muda o DOM, e o browser captura o antes, aplica a mudança, captura o depois e anima entre os dois:
document.startViewTransition(() => {
// qualquer mudança de DOM aqui
lista.replaceChildren(...novosItens)
})O suporte hoje cobre os três motores: Chrome 111+, Safari 18+ e Firefox 144+. Sem flag, sem prefixo.
O controle fino vem por CSS, nos pseudo-elementos que o browser cria durante a transição:
::view-transition-old(root) { animation: 0.4s ease-in both sair; }
::view-transition-new(root) { animation: 0.4s ease-in both entrar; }Para transição de elemento compartilhado (a miniatura que vira a imagem grande), o mecanismo é view-transition-name. Dois elementos em estados diferentes com o mesmo nome, e o browser interpola entre eles.
Esta camada é onde eu colocaria qualquer coisa que precise funcionar em produção hoje.
Camada 2: cross-document, e o que o Firefox faz com ela
Aqui a transição acontece entre páginas, no fluxo tradicional de navegação.
O opt-in é uma at-rule, e ela precisa existir nos dois documentos:
@view-transition {
navigation: auto;
}O descritor navigation aceita auto ou none. Com auto, o documento passa por transição quando quatro condições são atendidas ao mesmo tempo:
- A navegação é same-origin
- Não há redirect cross-origin no caminho
- O
navigationTypeétraverse,pushoureplace - Para
pushereplace, a navegação foi iniciada pelo usuário — não pela interface do browser
Essa lista explica a maior parte dos "não funcionou". Redirect que passa por outro domínio derruba a transição inteira, e navegação disparada por botão do browser não conta como iniciada pelo usuário.
E há o Firefox. O MDN classifica a at-rule como "Limited availability — not Baseline". O Firefox 144 suporta a API same-document, mas ignora a at-rule @view-transition. Fluxo de múltiplas páginas ali simplesmente não anima: a navegação acontece com o corte seco de sempre.
Isso não quebra nada — degrada. É a diferença entre esta camada e a próxima.
O mesmo raciocínio de degradação vale para as scroll-driven animations do CSS: o que degrada sozinho é seguro, o que esconde conteúdo precisa de fallback.
Camada 3: o <ViewTransition> do React
Esta é a que o Next.js 16 entrega e a que exige mais cuidado.
O componente envolve uma subárvore e anima quando ela entra, sai ou muda:
import { ViewTransition } from 'react'
<ViewTransition enter="auto" exit="auto" default="none">
<Video />
</ViewTransition>O React ativa a animação sozinho, conforme o tipo de mudança:
| Prop | Dispara quando |
|---|---|
enter | O <ViewTransition> é inserido dentro de uma Transition |
exit | Ele é removido dentro de uma Transition |
update | Há mutação de DOM ou mudança de layout dentro dele |
share | Existe um <ViewTransition> de mesmo name na árvore removida e na inserida |
default | Fallback para todos os gatilhos acima |
Os valores são "auto", "none", um nome de classe CSS, ou um objeto com lógica condicional — o que permite animação diferente para navegação para frente e para trás.
Transição de elemento compartilhado fica declarativa. Mesmo name, árvores diferentes:
<ViewTransition name="capa"><div className="miniatura" /></ViewTransition>
// ...em outra tela
<ViewTransition name="capa"><div className="miniatura tela-cheia" /></ViewTransition>E há a via imperativa, para quem quer Web Animations API em vez de CSS. As callbacks onEnter, onExit, onUpdate e onShare recebem uma instância com os pseudo-elementos .old e .new:
<ViewTransition
onEnter={(instance) => {
const anim = instance.new.animate(
[{ opacity: 0 }, { opacity: 1 }],
{ duration: 500 }
)
return () => anim.cancel()
}}
/>O aviso que muda a decisão
A documentação do React é explícita sobre o estágio:
O Next.js 16 usa a última Canary do React no App Router. É por isso que o componente está disponível para você sem instalar nada — e é por isso que o aviso é fácil de não ver. A disponibilidade vem do framework; a estabilidade, não.
O que isso significa na prática: usar o <ViewTransition> hoje é aceitar que a API pode mudar num release menor. Para site de cliente com contrato de manutenção, isso é uma dívida que você está assumindo sem avisar. Para projeto próprio ou experimento, é aceitável.
Vale registrar o outro lado, porque ele é real: a Vercel documenta o componente como padrão suportado, com guia oficial de quatro receitas, e afirma que funciona no App Router sem configuração nenhuma — você não precisa instalar react@canary. Existe até uma skill oficial para ensinar as receitas a um agente:
npx skills add vercel-labs/agent-skills --skill vercel-react-view-transitionsAs duas coisas são verdade ao mesmo tempo. O React chama de experimental; o Next.js entrega, documenta e suporta. A decisão de risco é sua, e ela deveria ser explícita no orçamento em vez de implícita no código.
Se a transição precisa existir e não pode quebrar, as camadas 1 e 2 fazem o mesmo trabalho com CSS e a API nativa.
As seis limitações do componente React
Todas documentadas, e cinco delas geram sintoma sem erro.
1. Precisa ser o elemento mais externo. O componente só funciona se estiver antes de qualquer nó do DOM. Envolver um <div> em volta dele quebra as animações de entrada e saída.
2. Um por nome, por vez. Só um <ViewTransition name="X"> pode estar montado simultaneamente.
3. Exige uma Transition. Nada acontece fora de startTransition(), <Suspense> ou useDeferredValue. Mudança de estado comum não ativa a animação. Em Next.js isso é menos restritivo do que parece: navegação de rota já é uma transition, então as animações ativam sozinhas na navegação.
4. Só no DOM. Não funciona em React Native.
5. flushSync pula a animação. Atualização síncrona ignora a transição inteira.
6. Acessibilidade é manual. O React não checa prefers-reduced-motion por você.
A sexta merece atenção. Diferente do CSS, onde você escreve a media query uma vez e ela vale, aqui a checagem é responsabilidade sua:
const reduzido = useMediaQuery('(prefers-reduced-motion: reduce)')
<ViewTransition default={reduzido ? 'none' : 'auto'}>
<Conteudo />
</ViewTransition>Sem isso, você está animando para quem pediu explicitamente ao sistema para não animar.
As quatro receitas, e o que cada uma comunica
O guia oficial organiza por significado, não por técnica. É a melhor decisão editorial que a Vercel tomou nessa documentação, e vale copiar o critério.
| Padrão | O que comunica ao usuário |
|---|---|
| Morph de elemento compartilhado | "Mesma coisa, indo mais fundo" |
| Reveal do Suspense | "O dado chegou" |
| Slide direcional | "Avançando / voltando" |
| Crossfade na mesma rota | "Mesmo lugar, conteúdo diferente" |
O morph é o mais simples e o mais valioso: mesmo name nos dois lados, e o browser interpola tamanho e posição.
// na grade
<ViewTransition name={`foto-${foto.id}`}>
<Image src={foto.src} alt={foto.titulo} />
</ViewTransition>
// na página de detalhe
<ViewTransition name={`foto-${foto.id}`}>
<Image src={foto.src} alt={foto.titulo} fill />
</ViewTransition>Nenhuma prop extra é necessária. Mas há uma condição de timing: o morph só toca quando o destino renderiza no mesmo commit da navegação — o que acontece com página prefetchada. Se o destino cair num fallback de Suspense primeiro, o par não se forma e o conteúdo entra com a animação de enter.
O slide direcional é a única receita que depende de API específica do Next.js. A prop transitionTypes no <Link> marca a navegação, e o <ViewTransition> mapeia o tipo para a animação:
<Link href={`/foto/${foto.id}`} transitionTypes={['nav-forward']}>
<ViewTransition
enter={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
default="none"
>O useRouter aceita transitionTypes em push() e replace() também. E o tipo não é automático: você decide quais links são "para frente" e quais são "para trás", conforme a hierarquia do seu app.
Quatro armadilhas que a doc avisa
Todas geram sintoma sem erro.
1. O wrapper no layout nunca dispara. Layouts persistem entre navegações, então enter e exit não acontecem lá. O <ViewTransition> de página precisa estar em cada page.tsx.
2. default="none" sem share mata o morph em silêncio. O default="none" existe para impedir que um <ViewTransition> nomeado anime em toda transição não relacionada. Mas se você adicionar default="none" num par nomeado sem manter o share explícito, o par simplesmente para de fazer morph — sem aviso.
3. Cliques durante a transição se perdem. A camada ::view-transition captura eventos de ponteiro enquanto anima. A correção é uma linha:
::view-transition { pointer-events: none; }Mesmo assim o hit-testing continua pulando os participantes nomeados durante a transição — então mantenha as animações curtas e evite nomear elemento que o usuário clica em sequência rápida.
4. Voltar pelo browser não carrega o tipo. Navegação iniciada pelo botão de voltar ou por gesto de swipe não carrega transition type, então o slide direcional não toca. O morph de elemento compartilhado continua valendo, porque ele depende só do name.
Vale também ancorar o header, senão ele desliza junto e o usuário perde a referência espacial:
::view-transition-group(site-header) { animation: none; z-index: 100; }
::view-transition-old(site-header) { display: none; }O display: none no snapshot antigo evita o flash de dois headers visíveis ao mesmo tempo.
O que eu usaria hoje em produção
A regra que sigo tem uma linha: use a camada mais madura que resolve o problema.
| Você quer | Use |
|---|---|
| Animar troca de conteúdo na mesma página | Camada 1 — startViewTransition() |
| Transição entre páginas num site multipágina | Camada 2 — @view-transition, aceitando que o Firefox não anima |
| Elemento compartilhado entre duas telas do App Router | Camada 3, se o projeto tolerar API instável |
| Reveal, parallax, barra de progresso | Nada disso — scroll-driven animations |
E o critério de fallback, que vale para as três: como fica se a transição não acontecer?
Se a resposta é "a navegação fica com corte seco", está tudo bem — é o comportamento que a web teve por trinta anos. Se a resposta é "o conteúdo fica invisível" ou "o elemento fica no lugar errado", o problema não é a transição; é que você construiu o estado final assumindo que a animação sempre roda.
Esse teste é o mesmo que aplico às scroll-driven animations, e ele reprova o mesmo tipo de código: o que parte de um estado escondido.
Se você ainda vai atualizar, o que quebra na migração para o Next.js 16 cobre as outras vinte mudanças que vêm junto.
Perguntas frequentes
Posso usar View Transitions em produção hoje?
A camada same-document, sim, sem ressalva — é Baseline nos três motores. A cross-document, sim, aceitando que o Firefox não anima e degrada para corte seco. O componente do React, só se o projeto tolerar uma API que a própria documentação chama de não pronta para produção.
Por que a transição entre páginas não funciona no meu site?
Cheque as quatro condições da camada 2 na ordem: same-origin, sem redirect cross-origin, navigationType compatível, e navegação iniciada pelo usuário. Redirect que passa por outro domínio é a causa mais comum. Depois disso, confirme que a at-rule existe nos dois documentos.
<ViewTransition> funciona sem startTransition?
Não. O componente exige que a atualização aconteça dentro de uma Transition, de um <Suspense> ou de um useDeferredValue. Mudança de estado comum não ativa a animação, e não há erro indicando isso.
Isso substitui o GSAP para transição de página?
Para transição simples entre estados, sim — e sem biblioteca. Para sequência coordenada com várias etapas, controle de easing por trecho e sincronização com scroll, não. A licença e o escopo do GSAP cobrem quando ele continua sendo a ferramenta certa.
Fontes
- MDN — View Transition API. Acesso em 20/08/2026.
- MDN —
@view-transition. Classificada como Limited availability. Acesso em 20/08/2026. - React —
<ViewTransition>. Canal Canary/Experimental. Acesso em 20/08/2026. - Next.js — Designing view transitions. Doc atualizada em 07/08/2026. Acesso em 20/08/2026.
- Next.js — How to upgrade to version 16. App Router na Canary do React 19.2. Acesso em 20/08/2026.
Verificado em 20 de agosto de 2026.
Gatilho de reavaliação: revisar quando (a) o <ViewTransition> do React sair do canal Canary e estabilizar, (b) o Firefox passar a honrar a at-rule @view-transition, movendo cross-document para Baseline, ou (c) as props do componente mudarem antes da estabilização, o que a própria documentação avisa que pode acontecer.
Leia também
Motion •
Motion Design para Web: Guia Completo
Scroll, texto, imagens e vídeo: o catálogo completo de técnicas de motion para web, com implementação em Next.js e os casos em que cada uma compensa.
- motion
- scroll
Motions premium em Next.js: os 18 padrões do site da Plantica, passo a passo
O scroll vira CSS custom property e o CSS faz o resto: os 18 padrões de motion do site da Plantica em Next.js, com o prompt de implementação pronto de cada um.
- motion
- gsap
Motions premium em Next.js: os 18 padrões do site da ANAI, passo a passo
A arquitetura híbrida do site da ANAI — CSS vars, tweens por seção e canvas — destrinchada em 18 padrões de motion com prompts prontos.
- motion
- gsap


