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>
);
}