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

NextJS 13 App目录项目用Chakra UI出现FOUC及控制台错误

问题描述

基于NextJS 13 App目录搭建的项目,使用Chakra UI框架,未登录时middleware会将请求重定向到登录页,但存在以下问题:

  • 登录页出现未样式化内容闪烁(FOUC)
  • 控制台报错
  • 登录页目录中的加载组件从未显示

相关代码

package.json

{
  "dependencies": {
    "@chakra-ui/pro-theme": "^0.0.57",
    "@chakra-ui/react": "^2.4.3",
    "@emotion/cache": "^11.10.5",
    "@emotion/react": "^11.10.5",
    "@emotion/styled": "^11.10.5",
    "@types/node": "18.11.10",
    "@types/react": "18.0.25",
    "@types/react-dom": "18.0.9",
    "cookies-next": "^2.1.1",
    "eslint": "8.28.0",
    "eslint-config-next": "13.0.5",
    "framer-motion": "^6.5.1",
    "next": "13.0.5",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-icons": "^4.7.1",
    "typescript": "4.9.3"
  }
}

middleware.ts

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import hasSession from "./helpers/gate/hasSession";

export const config = {
  matcher: [
    "/((?!login|favicon.ico).*)",
  ],
};

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith("/_next/")) {
    return NextResponse.next();
  }

  if (hasSession(request)) {
    return NextResponse.next();
  }

  const loginUrl = new URL("/login", request.url);
  return NextResponse.redirect(loginUrl);
}

app/layout.tsx

import Chakra from "../lib/ChakraProvider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head />
      <body>
        <Chakra>{children}</Chakra>
      </body>
    </html>
  );
}

lib/ChakraProvider.tsx

"use client";

import { ChakraProvider, ColorModeScript } from "@chakra-ui/react";
import { theme as proTheme } from "@chakra-ui/pro-theme";

export default function Chakra({ children }: { children: React.ReactNode }) {
  return (
    <ChakraProvider theme={proTheme}>
      <ColorModeScript initialColorMode={"dark"} />
      {children}
    </ChakraProvider>
  );
}

问题根源

  1. FOUC与控制台报错:ChakraProvider是客户端组件,根布局为服务端组件,导致Chakra样式在客户端hydration前未加载;ColorModeScript放在ChakraProvider内部,执行时机过晚,加剧样式闪烁。
  2. 加载组件不显示:middleware是服务端层面的重定向,登录页的loading.tsx仅在客户端导航或组件异步加载时触发,服务端跳转不会激活它。

解决方案

1. 修复FOUC与控制台报错

调整ChakraProvider结构与样式加载

  • 将ColorModeScript移至根布局的<head>中,确保页面渲染前执行
  • 创建服务端兼容的Emotion缓存,避免客户端与服务端样式不匹配

修改lib/ChakraProvider.tsx:

"use client";

import { ChakraProvider } from "@chakra-ui/react";
import { theme as proTheme } from "@chakra-ui/pro-theme";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";

// 创建服务端兼容的Emotion缓存
const clientEmotionCache = createCache({
  key: "chakra-ui-emotion-cache",
});

export default function Chakra({ children }: { children: React.ReactNode }) {
  return (
    <CacheProvider value={clientEmotionCache}>
      <ChakraProvider theme={proTheme}>{children}</ChakraProvider>
    </CacheProvider>
  );
}

修改app/layout.tsx:

import Chakra from "../lib/ChakraProvider";
import { ColorModeScript } from "@chakra-ui/react";
import { theme as proTheme } from "@chakra-ui/pro-theme";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        {/* 提前注入ColorModeScript,避免主题样式闪烁 */}
        <ColorModeScript
          initialColorMode={proTheme.config.initialColorMode}
        />
      </head>
      <body>
        <Chakra>{children}</Chakra>
      </body>
    </html>
  );
}

2. 让登录页显示加载状态

由于服务端重定向不会触发loading.tsx,可在登录页组件中添加客户端加载逻辑:

修改app/login/page.tsx(示例):

"use client";

import { useState, useEffect } from "react";
import { Box, Spinner, Heading, Button, Input } from "@chakra-ui/react";

export default function LoginPage() {
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // 模拟初始化逻辑(如检查缓存、加载配置)
    setTimeout(() => {
      setIsLoading(false);
    }, 400);
  }, []);

  if (isLoading) {
    return (
      <Box display="flex" justifyContent="center" alignItems="center" minH="100vh">
        <Spinner size="xl" color="blue.500" />
      </Box>
    );
  }

  return (
    <Box
      display="flex"
      flexDir="column"
      gap={4}
      justifyContent="center"
      alignItems="center"
      minH="100vh"
      p={4}
    >
      <Heading size="lg">登录</Heading>
      <Input placeholder="用户名" w="sm" />
      <Input placeholder="密码" type="password" w="sm" />
      <Button colorScheme="blue">登录</Button>
    </Box>
  );
}

3. 优化Middleware规则

简化静态资源拦截逻辑,通过matcher直接排除无需校验的路径:

修改middleware.ts:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import hasSession from "./helpers/gate/hasSession";

export const config = {
  matcher: [
    "/((?!login|favicon.ico|_next/static|_next/image|assets|.*\\.(png|jpg|svg)$).*)",
  ],
};

export function middleware(request: NextRequest) {
  if (hasSession(request)) {
    return NextResponse.next();
  }

  const loginUrl = new URL("/login", request.url);
  return NextResponse.redirect(loginUrl);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 06:45:36