You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13.x.x集成Framer-motion遇hydration不匹配错误求助

Next.js 13 SSR + Framer Motion 消除 data-projection-id Hydration 警告

问题原因

控制台出现的Warning: Prop 'data-projection-id' did not match警告,是因为Framer Motion在客户端渲染时会自动生成data-projection-id属性用于动画状态跟踪,但服务端渲染时该属性值为null,导致React在hydrate过程中检测到服务端与客户端的DOM属性不匹配。

解决方案

方案1:添加suppressHydrationWarning属性

直接给所有motion元素添加suppressHydrationWarning属性,告诉React忽略该内部属性的hydrate不匹配(该ID仅用于Framer Motion内部逻辑,不影响页面功能)。

修改后的组件代码:

'use client';

import { motion } from 'framer-motion';
import Vitamin from '@/components/vitamin';

const Fruit = ({ fruit }) => {
  const vitCardContainer = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        delayChildren: 0.125,
        staggerChildren: 0.05,
      },
    },
  };
  const vitCardItems = {
    hidden: { opacity: 0, scale: 0.8 },
    visible: { opacity: 1, scale: 1, transition: { duration: 0.25 } },
  };

  return (
    <section>
      <h1 className='text-2xl font-bold text-red-600'>{fruit!.name}</h1>
      <div className='flex flex-col my-4'>
        <h3 className='text-xl font-bold text-red-400'>Overview</h3>
        <p className='my-2 text-base font-semibold text-red-400'>
          {fruit!.overview}
        </p>
      </div>
      <div className='flex flex-col my-4'>
        <h3 className='text-xl font-bold text-red-400'>Vitamins Present</h3>
        <motion.ul
          suppressHydrationWarning
          className={
            'flex flex-row flex-wrap my-4 ' +
            (fruit!.vitamins?.length > 3 ? 'justify-between' : 'justify-normal')
          }
          variants={vitCardContainer}
          initial='hidden'
          animate='visible'
        >
          {fruit!.vitamins?.slice(0, 6).map((c) => (
            <motion.li
              suppressHydrationWarning
              key={'fruit-vitamins-' + c.id}
              variants={vitCardItems}
              className={fruit!.vitamins?.length > 3 ? '' : 'pr-4'}
            >
              <Vitamin
                id={c.id}
                name={c.name}
                character={c.character}
                profile_path={c.profile_path}
              />
            </motion.li>
          ))}
        </motion.ul>
      </div>
    </section>
  );
};

export default Fruit;

方案2:延迟动画初始化到客户端挂载后

通过useAnimation控制器结合useEffect,确保动画仅在客户端hydrate完成后启动,从根源避免服务端与客户端的DOM状态差异。

修改后的组件代码:

'use client';

import { motion, useAnimation } from 'framer-motion';
import { useEffect } from 'react';
import Vitamin from '@/components/vitamin';

const Fruit = ({ fruit }) => {
  const animationControls = useAnimation();
  const vitCardContainer = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        delayChildren: 0.125,
        staggerChildren: 0.05,
      },
    },
  };
  const vitCardItems = {
    hidden: { opacity: 0, scale: 0.8 },
    visible: { opacity: 1, scale: 1, transition: { duration: 0.25 } },
  };

  // 客户端挂载完成后启动动画
  useEffect(() => {
    animationControls.start('visible');
  }, [animationControls]);

  return (
    <section>
      <h1 className='text-2xl font-bold text-red-600'>{fruit!.name}</h1>
      <div className='flex flex-col my-4'>
        <h3 className='text-xl font-bold text-red-400'>Overview</h3>
        <p className='my-2 text-base font-semibold text-red-400'>
          {fruit!.overview}
        </p>
      </div>
      <div className='flex flex-col my-4'>
        <h3 className='text-xl font-bold text-red-400'>Vitamins Present</h3>
        <motion.ul
          className={
            'flex flex-row flex-wrap my-4 ' +
            (fruit!.vitamins?.length > 3 ? 'justify-between' : 'justify-normal')
          }
          variants={vitCardContainer}
          initial='hidden'
          animate={animationControls}
        >
          {fruit!.vitamins?.slice(0, 6).map((c) => (
            <motion.li
              key={'fruit-vitamins-' + c.id}
              variants={vitCardItems}
              className={fruit!.vitamins?.length > 3 ? '' : 'pr-4'}
            >
              <Vitamin
                id={c.id}
                name={c.name}
                character={c.character}
                profile_path={c.profile_path}
              />
            </motion.li>
          ))}
        </motion.ul>
      </div>
    </section>
  );
};

export default Fruit;

方案选择

  • 方案1实现简单,适合快速消除警告;
  • 方案2更贴合SSR的hydration机制,避免潜在的DOM不一致问题,同时保留完整动画效果。

内容的提问来源于stack exchange,提问作者Dibyendu Saha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:02:10