eduardweb.
Animații (Framer Motion)Intermediar#nextjs#react#framer-motion#animatii#app-router

Cum faci AnimatePresence să meargă curat în Next.js App Router

De Gabriela Neagu, 6 aug. 2026 · 9 vizualizări · 2 like-uri

Postat 6 aug. 2026
typescript
'use client';

import { useContext, useRef } from 'react';
import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime';
import { usePathname } from 'next/navigation';
import { AnimatePresence, motion } from 'framer-motion';

function FrozenRoute({ children }: { children: React.ReactNode }) {
  const context = useContext(LayoutRouterContext);
  const frozen = useRef(context).current;

  return (
    <LayoutRouterContext.Provider value={frozen}>
      {children}
    </LayoutRouterContext.Provider>
  );
}

export function PageAnimatePresence({ children }: { children: React.ReactNode }) {
  const pathname = usePathname();

  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        initial={{ opacity: 0, y: 12 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: -12 }}
        transition={{ duration: 0.18, ease: 'easeInOut' }}
      >
        <FrozenRoute>{children}</FrozenRoute>
      </motion.div>
    </AnimatePresence>
  );
}

Dacă ai încercat vreodată să pui exit animations în Next.js App Router cu Framer Motion, știi deja că AnimatePresence e un mic coșmar. Next 13 și 14 taie DOM-ul instant la schimbarile de rută, așa că animația de exit pur și simplu nu se mai declanșează. În postarea asta îți arăt pattern-ul pe care îl folosesc în producție ca să rezolv problema asta fără hack-uri urâte.

La un proiect recent cu 14k utilizatori lunari (un SaaS de e-learning), clientul insista pe niște tranziții fluide între lecții. Am pierdut aproape două zile încercând să pun AnimatePresence direct în template.tsx sau layout.tsx. Rezultatul? Pagina veche dispărea instant, iar noua pagină făcea doar animația de intrare. Zero exit transition.

De ce se rupe AnimatePresence în App Router

Pagelayout-ul din Pages Router (_app.tsx) avea un lifecycle destul de predictibil. În App Router însă, React Server Components și noul Router de la Next.js distrug arborele de componente înainte ca Framer Motion să apuce să-și ruleze callback-ul de onExit.

AnimatePresence are nevoie ca copiii lui să rămână renderizați în React DOM în timp ce se execută tranziția de exit. Când schimbi ruta, Next.js schimbă contextul de navigație instantaneu. Componenta veche dispare fără să mai aștepte duration-ul stabilit de tine în transition.

Soluția: Freeze pe LayoutRouterContext

Ca să păstrăm vechea pagină în DOM fix atât timp cât durează animația de exit, trebuie să «înghețăm» contextul de router oferit de Next.js. Creăm o componentă FrozenRoute care salvează valoarea contextului LayoutRouterContext într-un useRef la momentul montării.

Astfel, când Next.js încearcă să înlocuiască arborele cu noua pagină, FrozenRoute continuă să furnizeze vechiul context către copiii săi până când AnimatePresence termină animația și demontează efectiv elementul.

După ce ai creat wrapper-ul de mai jos, îl poți folosi direct în template.tsx sau într-un component wrapper de nivel înalt în layout.tsx.

Trade-off-uri reale și la ce să fii atent

Pattern-ul ăsta rezolvă problema vizuală, dar vine cu niște costuri pe care e bine să le știi de la început:

  • Latență perceptibilă cu mode="wait": Daca pui o animație de exit de 0.4s, utilizatorul va aștepta 400ms înainte să vadă măcar că noua pagină începe să se încarce. Eu folosesc un duration de maxim 0.15s - 0.2s pentru exit.
  • Scroll position: Câteodată Next.js încearcă să facă scroll to top înainte să se termine animația de exit. Dacă pățești asta, trebuie să dezactivezi scroll={false} pe <Link> și să controlezi scroll-ul manual din useEffect după ce animația e gata.
  • Private Next internals: Soluția se bazează pe LayoutRouterContext importat din next/dist/shared/lib/app-router-context.shared-runtime. Deși funcționează perfect în Next 13, 14 și 15, e tehnic un API intern. Există un risc mic să se schimbe la un major update de Next.

Merge brici pentru site-uri de prezentare, dashboard-uri cu tranziții fine sau aplicații unde UX-ul are nevoie de acel «polish» de aplicație nativă.

Voi cum gestionați tranzițiile de pagină în App Router? Ați rămas pe Framer Motion sau ați început să experimentați cu native View Transitions API?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.