eduardweb.
Animații (Framer Motion)Intermediar#css#nextjs#react#framer-motion#frontend

Tranziții de pagină line în Next.js App Router cu Framer Motion (fără hack-uri urâte)

De Elisabeta Stan, 29 iul. 2026 · 6 vizualizări · 3 like-uri

Postat 29 iul. 2026
typescript
'use me 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 PageTransition({ children }: { children: React.ReactNode }) {
  const pathname = usePathname();

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

Am trecut anul trecut un SaaS intern cu vreo 12k utilizatori activi de pe Pages Router pe Next.js App Router. Totul a mers brici la refactoring, mai puțin un lucru care ne-a mâncat jumătate de zi: tranzițiile dintre pagini. În vechiul _app.tsx puneam un AnimatePresence cu mode="wait", treceam Component.key și aia era.

În App Router, surpriză. Când schimbi ruta, Next.js dă unmount instantaneu la Server Component-ul paginii vechi. AnimatePresence nici nu apucă să vadă că nodul a fost eliminat ca să-și ruleze animația de exit. Rezultatul? Pagina nouă apare brusc sau animația de ieșire e ignorată complet.

De ce nu merge abordarea clasică?

Problema e arhitecturală. App Router separă layout-ul de pagină și gestionează navigația prin suspendarea arborelui de React Server Components. Când dai click pe un <Link>, Next.js aduce payload-ul noii rute și înlocuiește direct copiii layout-ului. AnimatePresence are nevoie ca vechiul DOM node să rămână în React pentru durata animației, dar Next.js l-a șters deja din starea routerului.

Ca să facem animațiile de exit să funcționeze, trebuie să păstrăm manual vechiul arbore de componente înghețat (frozen) pentru câteva sute de milisecunde până când Framer Motion își termină treaba.

Pattern-ul care chiar funcționează: Freeze Context

Am încercat mai multe librării terțe, dar multe aveau bug-uri cu memoria sau rupeau streaming-ul. Până la urmă am rămas pe o soluție curată: un wrapper de client component care folosește usePathname() și salvează copiii anteriori într-un context înghețat până se termină tranziția.

Ideea e simplă. Creăm o componentă FrozenRoute care memorează contextul de layout din Next.js. Astfel, când ruta se schimbă, AnimatePresence randază versiunea „înghețată” a paginii vechi exact pe durata animației de exit.

Trade-off-uri reale

Nimic nu e gratuit în web dev, iar pattern-ul ăsta vine cu niște minusuri clare pe care e bine să le știi înainte să-l bagi în producție:

  • Perceput vs. Real performance: Adaugi un delay artificial de 200-300ms la navigație doar ca să arate bine. La noi pe dashboard a fost OK, dar pe un e-commerce ar putea scădea conversia.
  • Layout shifts pe RSC: Dacă pagina nouă aduce date prin streaming (Suspense), animația de intrare s-ar putea să se declanșeze înainte ca datele să fie gata, rezultând într-un flash ciudat.
  • Memory leaks la navigație rapidă: Dacă userul dă spam pe link-uri mai repede decât viteza animației, contextul înghețat poate rămâne desincronizat.

Concluzia mea? Folosește animații pe rute doar dacă aduc valoare UX-ului (de exemplu în modaluri, wizard-uri multi-step sau tab-uri). Pentru pagini mari, o tranziție simplă de opacity (fade-in) pe client e mai mult decât suficientă și mult mai ieftină.

Voi cum ați rezolvat problema asta în App Router? Ați rămas pe animații native din CSS/View Transitions API sau tot Framer Motion folosiți?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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