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

Next.js 13基于屏幕尺寸设置framer-motion初始动画值时的Hydration错误

Next.js 13 Pages目录下Framer-Motion Hydration错误解决方案

在Next.js 13旧版pages目录中使用framer-motion实现视口滑入动画时,因大屏/移动端不同初始X值设置,出现服务端与客户端渲染样式不匹配的Hydration错误,具体提示:

Warning: Prop style did not match. Server: "transform:translateX(86vw) translateZ(0)" Client: "transform:translateX(93vw) translateZ(0)"
Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.

问题根源

服务端渲染时不存在window对象,你的isMobile()函数会返回false;而客户端如果处于移动端环境,函数返回true,导致初始transform样式在服务端和客户端不一致。另外需要明确:'use client'在pages目录中不会让组件完全跳过服务端渲染,它仅标记组件需要客户端交互能力,服务端仍会先渲染一次静态内容,因此仍会出现样式不匹配。


解决方案

方案1:useEffect+useState延迟客户端初始值设置

通过useState初始化服务端默认值,再在useEffect(仅客户端执行)中根据屏幕尺寸更新初始X值,避免服务端与客户端渲染差异:

'use client';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { isMobile } from '../path/to/your/utils';

export default function AnimatedHeading() {
  const [initialX, setInitialX] = useState('86vw'); // 服务端默认渲染值

  useEffect(() => {
    // 仅在客户端运行,根据屏幕尺寸更新初始位置
    setInitialX(isMobile() ? '93vw' : '86vw');
  }, []);

  return (
    <motion.h1
      initial={{ x: initialX }}
      whileInView={{ x: 0 }}
      transition={{ duration: 0.75, type: 'spring' }}
      viewport={{ once: true }}
      className={classes.element}
    >
      Example content
    </motion.h1>
  );
}

方案2:使用framer-motion内置useMediaQuery钩子

framer-motion的useMediaQuery钩子会安全地在客户端获取媒体查询结果,服务端默认返回false,客户端加载后自动更新,避免Hydration错误:

'use client';
import { motion, useMediaQuery } from 'framer-motion';

export default function AnimatedHeading() {
  const isMobile = useMediaQuery('(min-width: 320px) and (max-width: 767.98px)');

  return (
    <motion.h1
      initial={{ x: isMobile ? '93vw' : '86vw' }}
      whileInView={{ x: 0 }}
      transition={{ duration: 0.75, type: 'spring' }}
      viewport={{ once: true }}
      className={classes.element}
    >
      Example content
    </motion.h1>
  );
}

方案3:动态导入并禁用SSR

通过Next.js的dynamic工具导入组件并禁用服务端渲染,让组件完全在客户端加载,从根源避免渲染差异:

// 页面组件中
import dynamic from 'next/dynamic';

const AnimatedHeading = dynamic(() => import('../components/AnimatedHeading'), {
  ssr: false,
  loading: () => <h1>Loading...</h1> // 可选加载态
});

export default function HomePage() {
  return (
    <div>
      <AnimatedHeading />
    </div>
  );
}

组件文件中正常编写动画逻辑:

// AnimatedHeading.jsx
'use client';
import { motion } from 'framer-motion';
import { isMobile } from '../path/to/your/utils';

export default function AnimatedHeading() {
  return (
    <motion.h1
      initial={{ x: isMobile() ? '93vw' : '86vw' }}
      whileInView={{ x: 0 }}
      transition={{ duration: 0.75, type: 'spring' }}
      viewport={{ once: true }}
      className={classes.element}
    >
      Example content
    </motion.h1>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:07:02