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

Next.js 13 App Router中className不匹配及window未定义问题求助

Next.js 13 App Router 中 styled-components className 不匹配问题求助

控制台警告:

Warning: Prop `className` did not match. 
Server: "styled__HeadLink-sc-6589f63-0 fIOFTm" 
Client: "styled__HeadLink-sc-b76127ed-0 jzAIeh"

已尝试的解决方案

  • 配置 .babelrc 启用 styled-components SSR 支持:
    {
      "env": {
        "development": {
          "presets": ["next/babel"],
          "plugins": [["babel-plugin-styled-components", { "ssr": true, "displayName": true, "preprocess": false }]]
        },
        "production": {
          "plugins": [["babel-plugin-styled-components", { "ssr": true, "displayName": true, "preprocess": false }]],
          "presets": ["next/babel"]
        },
        "test": {
          "presets": ["next/babel"]
        }
      },
      "plugins": [["babel-plugin-styled-components", { "ssr": true, "displayName": true, "preprocess": false }]]
    }
    
  • 在 next.config.js 中配置 styledComponents 编译选项:
    reactStrictMode: false,
    compiler: {
      styledComponents: true,
    },
    
  • 添加客户端 window 存在性检查工具函数:
    export const isClient = () => typeof window !== 'undefined';
    
  • 使用 next/dynamic 禁用组件 SSR:
    export default dynamic(() => Promise.resolve(VeryTroubleBlock), { ssr: false });
    

问题出现场景

问题仅在 Swiper 组件初始渲染时触发,相关组件代码如下:

const isTablet = useAdaptiveSize(Breakpoints.tablet);
const isMobile = useAdaptiveSize(Breakpoints.mobile);

return (
  <div className="here_is_trouble">
          {isTablet ? (
            <Swiper
              modules={[Pagination, A11y]}
              pagination={{ clickable: true }}
              slidesPerView={isMobile ? 1 : 2}
              spaceBetween={24}
            >
              {managers.map((manager: TroubleBlockProps, index: number) => (
                <SwiperSlide key={`${index}`}>
                  <ManagerItems {...manager} />
                </SwiperSlide>
              ))}
            </Swiper>
          ) : (
            <>
              {managers.map((manager) => (
                <ManagerItems key={manager.name} {...manager} />
              ))}
            </>
          )}
        </div>
)
export default dynamic(() => Promise.resolve(VeryTroubleBlock), { ssr: false });

自定义 useAdaptiveSize Hook 代码:

import { isClient } from '@/utils/client-check';
import { useState, useLayoutEffect } from 'react';
import { useWindowSize } from 'usehooks-ts';

export function useAdaptiveSize(size: number): boolean {
  const { width } = useWindowSize();
  const [isTablet, setIsTablet] = useState(false);

  useLayoutEffect(() => {
    if (isClient()) setIsTablet(width < size);
  }, [width]);

  return isTablet;
}

核心矛盾

  • 该自定义 Hook 解决了应用中某页面的同类问题,但在包含上述 Swiper 的页面仍存在 className 不匹配警告。
  • 若直接用 window.innerWidth < Breakpoints.tablet 替代自定义 Hook,可消除 className 警告,但会触发服务器端 window is not defined 错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 04:06:01