next-transition-router

repository·main·Indexed 18 days ago

https://github.com/ismamz/next-transition-router

A lightweight (<8KB) library for adding animated transitions between pages in Next.js App Router applications. It allows integration of animation libraries like GSAP or Framer Motion into the routing lifecycle via the TransitionRouter provider, a custom Link component, and hooks like useTransitionRouter and useTransitionState to track transition stages (leaving, entering, none).

Tokens
4.4K
Snippets
16
Records
16
Agent score
60%

What's inside next-transition-router

  1. How to handle links for transitions

    main

    You can choose between two modes for triggering transitions:

    1. Manual Mode (Default: auto={false})

    You must use the custom Link component provided by next-transition-router instead of the standard Next.js Link. This is the most explicit way to ensure transitions trigger.

    2. Auto Mode (auto={true})

    When auto is enabled, the router automatically intercepts click events on internal links (excluding anchor links). In this mode, you can continue using standard Next.js Link components.

    To prevent a specific link from triggering a transition in auto mode, add the data-transition-ignore attribute to the link element.

    // Manual Mode
    import { Link } from "next-transition-router";
    
    export function Example() {
      return <Link href="/about">About</Link>;
    }
    
    // Auto Mode (in TransitionRouter props)
    <TransitionRouter auto={true}>
      {/* Standard Next.js links will now trigger transitions */}
      <Link href="/about">About</Link>
      
      {/* This link will be ignored */}
      <a href="/ignore-me" data-transition-ignore>Ignore</a>
    </TransitionRouter>
  2. Set up the TransitionRouter provider

    main

    To use animated transitions, you must wrap your application in the TransitionRouter component. This must be a client component because it requires passing DOM-related functions as props.

    Create a provider component (e.g., app/providers.tsx) and then import it into your root layout (e.g., app/layout.tsx).

    The leave and enter callbacks support both synchronous and asynchronous functions. The leave callback also receives from and to parameters (strings representing the previous and next paths) which allow for conditional animations based on the route change.

    Note: When using router.back(), the to parameter will be undefined.

    "use client";
    
    import { TransitionRouter } from "next-transition-router";
    
    export function Providers({ children }: { children: React.ReactNode }) {
      return (
        <TransitionRouter
          leave={async (next, from, to) => {
            await someAsyncAnimation(from, to);
            next();
          }}
          enter={async (next) => {
            await anotherAsyncAnimation();
            next();
          }}
        >
          {children}
        </TransitionRouter>
      );
    }
  3. Optimize animation performance with startTransition

    main

    When overlapping exit animations with page loading, React rendering can cause animation jank. To prioritize animation performance, wrap the next() call inside requestAnimationFrame and React's startTransition. This prevents React updates from interfering with your animation timeline while maintaining visual timing.

    import { startTransition } from "react";
    
    // Inside TransitionRouter props
    enter={(next) => {
      const tl = gsap.timeline()
        .to(".overlay", { y: "-100%", duration: 0.5 })
        .call(() => {
          requestAnimationFrame(() => startTransition(next));
        }, undefined, "<50%"); // Overlap timing preserved
        
      return () => tl.kill();
    }}
  4. Implement cleanup in transition callbacks

    main

    To prevent memory leaks, TransitionRouter supports cleanup functions within the leave and enter callbacks. If you return a function from these callbacks, it will be executed to cancel animations, similar to how a cleanup function works in a React useEffect hook.

    This is particularly useful when using animation libraries like GSAP to kill active tweens if a component unmounts or a transition is interrupted.

    "use client";
    
    import { gsap } from "gsap";
    import { TransitionRouter } from "next-transition-router";
    
    export function Providers({ children }: { children: React.ReactNode }) {
      return (
        <TransitionRouter
          leave={(next) => {
            const tween = gsap.fromTo("main", { autoAlpha: 1 }, { autoAlpha: 0, onComplete: next });
            return () => tween.kill(); // Cleanup function
          }}
          enter={(next) => {
            const tween = gsap.fromTo("main", { autoAlpha: 0 }, { autoAlpha: 1, onComplete: next });
            return () => tween.kill(); // Cleanup function
          }}
        >
          {children}
        </TransitionRouter>
      );
    }
  5. How transition stages work

    main

    The TransitionRouter manages a lifecycle consisting of three stages:

    1. none: The default state where the application is idle and ready for navigation.
    2. leaving: Triggered when a navigation event is initiated. During this stage, the leave callback is executed. The actual navigation is deferred until the next() callback provided to leave is invoked.
    3. entering: Triggered when the component unmounts or the pathname changes while in the leaving stage. During this stage, the enter callback is executed. The stage returns to none once the next() callback provided to enter is invoked.

    When auto={true} is set, the router uses event delegation to intercept clicks on <a> tags. You can opt-out of this behavior for specific links by adding the data-transition-ignore attribute to the anchor tag.

    // This link will be ignored by the automatic transition interceptor
    <a href="/fast-path" data-transition-ignore>Quick Link</a>
  6. Use useTransitionState to track transition progress

    main

    The useTransitionState hook allows you to monitor the current stage of a page transition. This is useful for triggering secondary animations (like a reveal effect) once a page is ready.

    It returns an object with:

    • stage: The current phase, which can be 'entering', 'leaving', or 'none'.
    • isReady: A boolean indicating if the new page is ready to be animated.
    "use client";
    
    import { useTransitionState } from "next-transition-router";
    
    export function Example() {
      const { stage, isReady } = useTransitionState();
    
      return (
        <div>
          <p>Current stage: {stage}</p>
          <p>Page ready: {isReady ? "Yes" : "No"}</p>
        </div>
      );
    }
  7. Use useTransitionRouter for programmatic navigation

    main

    The useTransitionRouter hook provides a way to navigate programmatically while supporting page transitions. It functions similarly to the standard Next.js useRouter hook but includes transition support for its push, replace, and back methods.

    Note: Browser back/forward navigation (e.g., using browser buttons) does not trigger page transitions by design.

    "use client";
    
    import { useTransitionRouter } from "next-transition-router";
    
    export function Programmatic() {
      const router = useTransitionRouter();
    
      return (
        <button onClick={() => router.push("/about")}>
          Go to /about
        </button>
      );
    }
  8. Use the Link component for transition-aware navigation

    main

    The Link component is a drop-in replacement for standard navigation links that automatically triggers transitions when clicked, ensuring the transition state is correctly managed.

    import { Link } from 'next-transition-router';
    
    function Navigation() {
      return (
        <nav>
          <Link href="/dashboard">Dashboard</Link>
        </nav>
      );
    }
  9. Use the custom Link component for transition-aware navigation

    main

    The Link component is a wrapper around next/link that automatically integrates with the TransitionRouter. When a user clicks a link, it checks if the navigation should trigger a transition (using shouldLinkTriggerTransition). If so, it intercepts the default Next.js navigation and uses the TransitionRouter to perform a transition-aware push or replace.

    This component accepts all standard props from next/link, including href, as, replace, and scroll.

    import { Link } from "next-transition-router";
    
    // Usage is identical to next/link, but with transition support enabled
    function MyComponent() {
      return (
        <Link href="/dashboard" replace scroll={false}>
          Go to Dashboard
        </Link>
      );
    }
  10. Use TransitionRouter and useTransitionState

    main

    The TransitionRouter component and useTransitionState hook are exported from the main entrypoint. TransitionRouter is used to provide the transition context to your application, while useTransitionState allows you to access the current state of transitions (e.g., whether a transition is currently in progress) within your components.

    import { TransitionRouter, useTransitionState } from 'next-transition-router';
    
    // Wrap your application or a specific layout
    function App() {
      return (
        <TransitionRouter>
          <YourContent />
        </TransitionRouter>
      );
    }
    
    // Access state in a child component
    function YourContent() {
      const state = useTransitionState();
      // state contains information about the current transition
      return <div>{state.isTransitioning ? 'Loading...' : 'Ready'}</div>;
    }
  11. Use useTransitionRouter for programmatic navigation

    main

    The useTransitionRouter hook provides access to the transition-aware router instance. Use this hook when you need to perform programmatic navigation that integrates with the transition lifecycle.

    import { useTransitionRouter } from 'next-transition-router';
    
    function MyComponent() {
      const router = useTransitionRouter();
    
      const handleClick = () => {
        router.push('/new-path');
      };
    
      return <button onClick={handleClick}>Navigate</button>;
    }