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

如何解决Next.js SSR(ISR)中Material UI断点渲染异常引发的CLS问题

解决Material UI sx断点在Next.js SSR/ISR下的CLS问题

问题根源

Next.js SSR/ISR模式下,服务端无法获取客户端视口尺寸,Material UI的sx断点对象默认会使用最小断点(如xs)的值,导致客户端 hydration 后切换到正确断点样式,引发累积布局偏移(CLS)。

以下是几种可行的解决方案:


1. 直接使用CSS媒体查询替代sx断点对象

这是最简洁的方案,利用CSS原生媒体查询特性,服务端渲染的CSS会包含所有断点样式,客户端加载后自动匹配当前视口,避免样式不匹配导致的布局偏移。

import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';

export default function ResponsiveBox() {
  const theme = useTheme();
  return (
    <Box
      sx={{
        height: 233,
        width: 350,
        // 直接使用Material UI主题的断点媒体查询
        [`@media ${theme.breakpoints.up('md')}`]: {
          maxHeight: 167,
          maxWidth: 250,
        },
      }}
    />
  );
}

若记得具体断点像素值,也可直接写像素媒体查询,无需依赖useTheme:

<Box
  sx={{
    height: 233,
    width: 350,
    '@media (min-width:900px)': { // Material UI默认md断点为900px
      maxHeight: 167,
      maxWidth: 250,
    },
  }}
/>

2. 用useMediaQuery动态匹配客户端视口

通过Material UI的useMediaQuery钩子在客户端挂载后获取真实视口信息,动态设置样式。SSR时会使用默认值(服务端无法识别媒体查询),但客户端 hydration 后会立即更新为正确样式,适合对CLS敏感度较低的场景。

import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Box from '@mui/material/Box';

export default function ResponsiveBox() {
  const theme = useTheme();
  // 检测是否达到md及以上断点
  const isMdOrLarger = useMediaQuery(theme.breakpoints.up('md'));

  return (
    <Box
      sx={{
        height: 233,
        width: 350,
        maxHeight: isMdOrLarger ? 167 : 233,
        maxWidth: isMdOrLarger ? 250 : 350,
      }}
    />
  );
}

3. 禁用组件的SSR渲染

如果组件内容不影响SEO,可通过Next.js的dynamic导入禁用组件的服务端渲染,让组件仅在客户端渲染,确保首次渲染就使用正确的断点值。

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

// 动态导入组件,禁用SSR
const ResponsiveBox = dynamic(() => import('../components/ResponsiveBox'), {
  ssr: false,
});

export default function Page() {
  return <ResponsiveBox />;
}
// ../components/ResponsiveBox.jsx
import Box from '@mui/material/Box';

export default function ResponsiveBox() {
  return (
    <Box
      sx={{
        height: 233,
        width: 350,
        maxHeight: { xs: 233, md: 167 },
        maxWidth: { xs: 350, md: 250 },
      }}
    />
  );
}

4. 服务端获取客户端断点信息(彻底解决CLS)

通过Cookie传递客户端视口断点信息,让服务端渲染时就能使用正确的样式值,彻底消除布局偏移。适合对CLS要求极高的场景,ISR模式下需配合重新验证机制。

步骤1:客户端存储断点到Cookie

在_app.js中添加逻辑,检测视口并将断点信息写入Cookie:

import { useEffect } from 'react';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';

export default function MyApp({ Component, pageProps }) {
  const theme = useTheme();

  useEffect(() => {
    const isMdOrUp = useMediaQuery(theme.breakpoints.up('md'));
    // 将断点信息写入Cookie,有效期1天
    document.cookie = `breakpoint=${isMdOrUp ? 'md' : 'xs'}; path=/; max-age=86400`;
  }, [theme]);

  return <Component {...pageProps} />;
}

步骤2:服务端读取Cookie并传递给组件

在页面的getStaticProps(ISR模式)中读取Cookie,将断点作为props传递给组件:

export async function getStaticProps(context) {
  // 从请求中读取Cookie,默认使用xs断点
  const breakpoint = context.req?.cookies?.breakpoint || 'xs';
  
  return {
    props: { breakpoint },
    revalidate: 60, // ISR重新验证时间,按需调整
  };
}

export default function Page({ breakpoint }) {
  return <ResponsiveBox breakpoint={breakpoint} />;
}

步骤3:组件使用服务端传递的断点

import Box from '@mui/material/Box';

export default function ResponsiveBox({ breakpoint }) {
  return (
    <Box
      sx={{
        height: 233,
        width: 350,
        maxHeight: breakpoint === 'md' ? 167 : 233,
        maxWidth: breakpoint === 'md' ? 250 : 350,
      }}
    />
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 05:50:37