04Frozen Router

Frozen Router

Framer MotionAdvancedAnimatePresence#internal-api

The FrozenRouter pattern solves the 'flickering' content problem during exit animations in Next.js App Router. Context freezing keeps old content visible until the animation completes.

Quick Start

  • 📦npm install framer-motion
  • 🧊Use FrozenRouter to preserve content during exit
  • 🎭AnimatePresence mode="wait" sequences enter/exit
  • ⚠️Uses internal Next.js API — may break in future versions

Installation

Terminal
1npm install framer-motion

Page Transition Component

app/page-transition.tsx
1'use client'
2
3import { AnimatePresence, motion } from 'framer-motion'
4import { usePathname } from 'next/navigation'
5import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'
6import { useContext, useRef, ReactNode } from 'react'
7
8function FrozenRouter({ children }: { children: ReactNode }) {
9 const context = useContext(LayoutRouterContext)
10 const frozen = useRef(context).current
11
12 return (
13 <LayoutRouterContext.Provider value={frozen}>
14 {children}
15 </LayoutRouterContext.Provider>
16 )
17}
18
19export function PageTransition({ children }: { children: ReactNode }) {
20 const pathname = usePathname()
21
22 return (
23 <AnimatePresence mode="wait">
24 <motion.div
25 key={pathname}
26 initial={{ opacity: 0, y: 20, scale: 0.98 }}
27 animate={{ opacity: 1, y: 0, scale: 1 }}
28 exit={{ opacity: 0, y: -20, scale: 0.98 }}
29 transition={{
30 duration: 0.3,
31 ease: [0.25, 0.46, 0.45, 0.94],
32 }}
33 >
34 <FrozenRouter>{children}</FrozenRouter>
35 </motion.div>
36 </AnimatePresence>
37 )
38}

Layout

app/layout.tsx
1import { PageTransition } from './page-transition'
2
3export default function Layout({ children }: { children: React.ReactNode }) {
4 return <PageTransition>{children}</PageTransition>
5}