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

如何降低NextJS+Mantine站点的CLS以优化性能?

问题

使用NextJS搭配Mantine框架开发站点,采用createStyles添加样式,尽可能复用Mantine组件。部署到Vercel后,除CLS(累积布局偏移)外其余性能指标表现良好,但CLS数值极差,首次加载时能明显感受到内容偏移。

Header组件示例代码

import { useState } from 'react';
import {
  createStyles, Container, Anchor, Group, ActionIcon, useMantineColorScheme
} from '@mantine/core';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import Sun from '@/public/svg/sun.svg';
import MoonStars from '@/public/svg/moonStars.svg';

const useStyles = createStyles((theme) => ({
  inner: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
  },

  burger: {
    [theme.fn.largerThan('sm')]: {
      display: 'none',
    }
  },

  links: {
    fontWeight: 700,
    textTransform: 'uppercase',
    textDecoration: 'none',
    fontSize: theme.fontSizes.md,
  },

  mainLink: {
    color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[6],
    borderBottom: `2px solid transparent`,
    transition: 'border-color 100ms ease, color 100ms ease',

    '&:hover': {
      color: theme.colorScheme === 'dark' ? theme.white : theme.black,
      textDecoration: 'none',
    },
  },

  mainLinkActive: {
    color: theme.colorScheme === 'dark' ? theme.white : theme.black,
    borderBottomColor: theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 5 : 6],
  },

  sideText: {
    color: theme.colorScheme === 'dark' ? theme.white : theme.black,
  }
}));

export default function Component() {
  const { colorScheme, toggleColorScheme } = useMantineColorScheme();
  const { classes, cx } = useStyles();
  let pathname = usePathname();
  const [active, setActive] = useState(() => {
    if (pathname.split('/')[1] === '') return 0;
    if (pathname.split('/')[1] === 'blog') return 1;
  });

  let mainLinks = [
    { "link": "/", "label": "About" },
    { "link": "/blog", "label": "Blog" },
  ]

  const mainItems = mainLinks.map((item, index) => (
    <Anchor component={Link} href={item.link} key={item.label} py='xl' px='md'
      className={cx(classes.mainLink, { [classes.mainLinkActive]: index === active }, classes.links)}
      onClick={() => setActive(index)} >
      {item.label}
    </Anchor>
  ));

  const changeColorScheme = () => {
    toggleColorScheme();
    localStorage.setItem('color-scheme', colorScheme === 'dark' ? 'light' : 'dark');
  }

  return (
    <Container pos='relative' mb='2rem'>
      <Container pos='sticky' className={classes.inner}>
        <div className={classes.links}>
          <Group spacing={0} >
            {mainItems}
          </Group>
        </div>
        <Group position="center" my="xl">
          <ActionIcon
            onClick={() => changeColorScheme()}
            size="lg"
            sx={(theme) => ({
              backgroundColor:
                theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
              color: theme.colorScheme === 'dark' ? theme.colors.yellow[4] : theme.colors.blue[6],
            })}>
            {colorScheme === 'dark' ? <Sun /> : <MoonStars />}
          </ActionIcon>
        </Group>
        {/* <Burger opened={opened} onClick={toggle} className={classes.burger} size="sm" /> */}
      </Container>
    </Container>
  );
}

猜测原因:Mantine将组件转换为JS导致过多代码加载,引发动态内容偏移,考虑改用原生HTML组件,但需要确认具体原因。


原因分析

  1. 客户端样式注入延迟:Mantine的createStyles和组件样式默认在客户端JS执行后才注入,首屏加载时浏览器先渲染无样式DOM,待JS加载完成后才应用样式,直接导致布局偏移。
  2. Hydration内容不匹配:用useState初始化导航激活状态时,客户端hydration阶段的计算结果可能与服务端初始渲染内容不一致,导致导航链接的活跃样式(如下边框)动态变化,触发布局偏移。
  3. 容器尺寸未预定义:Header中的Container组件未设置固定/最小高度,样式加载后元素的padding、margin生效,导致容器高度变化,带动下方内容偏移。
  4. SVG图标无固定尺寸:主题切换的SVG图标未声明宽高,首次渲染时图标加载完成后尺寸变化,引发局部布局偏移。

优化解决方法

1. 启用Mantine服务端样式注入

在Next.js App Router的根布局中配置Mantine SSR,确保样式在服务端就注入到HTML,避免客户端样式延迟:

import { MantineProvider, ColorSchemeScript } from '@mantine/core';
import './globals.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <ColorSchemeScript defaultColorScheme="light" />
        {/* 其他meta标签 */}
      </head>
      <body>
        <MantineProvider withGlobalStyles withNormalizeCSS>
          {children}
        </MantineProvider>
      </body>
    </html>
  );
}

2. 修复导航激活状态的Hydration差异

将激活状态的计算逻辑改为在客户端挂载后执行,避免服务端与客户端渲染内容不一致:

import { useEffect, useState } from 'react';

// ... 其他代码
const [active, setActive] = useState(0);

useEffect(() => {
  const currentActive = pathname.split('/')[1] === '' ? 0 : pathname.split('/')[1] === 'blog' ? 1 : 0;
  setActive(currentActive);
}, [pathname]);

3. 为Header容器设置固定高度

在useStyles的inner类中添加最小高度,避免样式加载后容器高度变化:

inner: {
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'space-between',
  minHeight: '64px', // 根据实际内容调整
},

4. 为SVG图标指定固定尺寸

直接在SVG组件上声明宽高:

{colorScheme === 'dark' ? 
  <Sun width={24} height={24} /> : 
  <MoonStars width={24} height={24} />
}

5. 拆分客户端与服务端逻辑

将Header中不需要交互的部分改为服务端组件,减少客户端JS加载量,避免不必要的动态渲染:

  • 将导航链接的渲染逻辑提取为服务端组件,仅保留主题切换的客户端逻辑。

6. 使用Next.js Image优化SVG加载

改用next/image组件渲染SVG,提前声明尺寸:

import Image from 'next/image';

// ... 其他代码
{colorScheme === 'dark' ? 
  <Image src={Sun} width={24} height={24} alt="Sun icon" /> : 
  <Image src={MoonStars} width={24} height={24} alt="Moon icon" />
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:04:53