警告:道具“className”不匹配,并且未在 Next 13 App Router 中定义窗口

Warning: Prop `className` did not match and window is not defined in Next 13 App Router

提问人:Takeshi 251 提问时间:10/20/2023 更新时间:10/23/2023 访问量:72

问:

我在解决Next.js 13应用程序路由器中的错误时感到头疼。

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

我当然做了很多谷歌搜索,以获取有关如何解决这个问题的信息。错误最令人不快的事情是它们很难被发现。

  • 因此,为了解决这个问题,我遵循了在互联网上找到的建议。

我的 .babelrc

{
  "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

reactStrictMode: false,
  compiler: {
    styledComponents: true,
  },

我在代码中使用了窗口检查。

export const isClient = () => typeof window !== 'undefined';

我也尝试使用next/dynamic。

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

我的自定义钩子

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

有趣的是,我的自定义钩子帮助我解决了应用程序一页上的问题,但是在使用此 Swiper 的第二页上,出现了问题。

当然,有一个解决方案可以消除“警告:Prop className 不匹配”错误。您需要将自定义钩子的执行替换为 innerWidth < Breakpoints.tablet。这样可以消除控制台错误,但它会出现在服务器上,指示未定义窗口

我愿意接受任何批评,并愿意接受帮助来解决这个问题。

祝大家有美好的一天!

JavaScript Next.js 窗口 警告

评论


答:

0赞 Takeshi 251 10/23/2023 #1

问题: 这个问题源于这样一个事实,即我的全局样式是用 CSS 编写的,而其余的样式是使用 styled-components 实现的。

答: 为了解决这个问题,我选择过渡到使用样式化组件来满足所有样式需求。这统一了样式方法,并确保了服务器端和客户端渲染期间样式的一致性。此外,我删除了在阶段中使用 React.Fragment。<></>StyleSheetManagerreturn styles

'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export default function StyledComponentsRegistry({ children }: { children: React.ReactNode }) {
  // Only create stylesheet once with lazy initial state
  // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return styles;
  });

  if (typeof window !== 'undefined') return <>{children}</>;

  return <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>{children}</StyleSheetManager>;
}