How do I use a loading page with a template page transition with framer-motion and Next 14?

30 Views Asked by At

Using NextJS 14

I am using a template page to animate page transitions, using framer-motion to animate opacity from 0 to 1 on every route change.

I am using a loading page to server side render a loading skeleton for a page.

template.tsx

"use client";

import { motion } from "framer-motion";

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ ease: "easeInOut", duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}

loading.tsx

"use client";

import Skeleton from "@/components/skeleton";

export default function Loading() {
  return <Skeleton />;
}

The issue is, the template is a parent of the loading page, which means the loading skeleton is rendered on the server with an opacity of 0 and is therefore invisible when it is sent to the client.

The template, however, needs to start with an opacity of 0 and animate to 1 in order for the real page to have an animated transition when it replaces the loading page.

How do I give the template an opacity of 1 on the server, when the loading page is rendered, and an opacity of 0 on the client when the actual page is rendered?


I tried setting opacity to 1 if rendering on the server and 0 if rendering on the client. This worked and created the effect I want -> visible loading skeletons, but invisible pages that animate in.

The problem though, is that this creates a warning.
Warning: Prop `style` did not match. Server: "opacity:1" Client: "opacity:0"

template.tsx

"use client";

import { motion } from "framer-motion";

import { isClient } from "@/utils";

export default function Template({ children }: { children: React.ReactNode }) {
  // Ensure loading skeletons are visible when they are rendered on the server
  const initialOpacity = isClient ? 0 : 1;

  return (
    <motion.div
      initial={{ opacity: initialOpacity }}
      animate={{ opacity: 1 }}
      transition={{ ease: "easeInOut", duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}
0

There are 0 best solutions below