Next.js App Router根布局为何出现重渲染?
问题背景
在 Next.js(App Router)中使用根布局渲染 Navbar 和子内容,官方文档明确说明:
布局是多页面间共享的UI,导航时布局会保留状态、保持交互且不会重渲染,布局也可嵌套。
但实际切换页面时,Navbar 控制台打印出"navbar rendering...",同时 Google 字体权重会短暂重置后恢复,与文档描述不符。
相关代码
根布局(app/layout.tsx)
import "./globals.css"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { Russo_One } from "next/font/google"; import Navbar from "./_components/Navbar"; export const russo = Russo_One({ weight: "400", subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Sportly", description: "Generated by create next app", }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body className={inter.className}> <Navbar /> {children} </body> </html> ); }
Navbar 组件(app/_components/Navbar.tsx)
import { Link, Typography, Box } from "@mui/material"; import { russo } from "../layout"; export default function Navbar() { console.log("navbar rendering..."); return ( <nav className="/*STYLES*/"> <h1 style={{fontWeight: "400" /* STYLES*/}} className={russo.className}> SPORTLY </h1> </nav> ); }
原因分析
Navbar 重渲染的根源
根布局本身不会重渲染,但 Navbar 作为根布局的子组件,直接从根布局模块导入了russo字体对象。Next.js 的next/font/google在开发模式下返回的字体对象可能存在不稳定的引用关系,导致 React 认为 Navbar 的依赖项发生变化,从而触发不必要的重渲染。字体权重闪烁的原因
开发模式下字体加载存在延迟,加上组件重渲染时字体类名的应用有短暂间隙,会导致页面短暂显示系统 fallback 字体,看起来像是字体权重被重置。
解决方案
1. 将字体定义移至单独文件
创建独立的字体管理文件,避免从根布局模块导出字体,确保字体对象引用稳定:
// app/fonts.ts import { Inter, Russo_One } from "next/font/google"; export const inter = Inter({ subsets: ["latin"] }); export const russo = Russo_One({ weight: "400", subsets: ["latin"] });
修改根布局导入:
import "./globals.css"; import type { Metadata } from "next"; import { inter } from "./fonts"; import Navbar from "./_components/Navbar"; export const metadata: Metadata = { title: "Sportly", description: "Generated by create next app", }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body className={inter.className}> <Navbar /> {children} </body> </html> ); }
修改 Navbar 导入:
import { Link, Typography, Box } from "@mui/material"; import { russo } from "../fonts"; export default function Navbar() { console.log("navbar rendering..."); return ( <nav className="/*STYLES*/"> <h1 style={{fontWeight: "400" /* STYLES*/}} className={russo.className}> SPORTLY </h1> </nav> ); }
2. 用 React.memo 包裹 Navbar
如果字体引用稳定后仍存在重渲染,使用 React.memo 阻止组件不必要的重渲染:
import { Link, Typography, Box } from "@mui/material"; import { russo } from "../fonts"; import { memo } from "react"; const Navbar = () => { console.log("navbar rendering..."); return ( <nav className="/*STYLES*/"> <h1 style={{fontWeight: "400" /* STYLES*/}} className={russo.className}> SPORTLY </h1> </nav> ); }; export default memo(Navbar);
3. 消除字体闪烁
在全局样式中为目标元素设置 fallback 字体的一致权重,减少视觉闪烁:
/* app/globals.css */ h1 { font-weight: 400; font-family: 'Russo One', sans-serif; }
补充说明
开发模式下 Next.js 的热更新机制可能会导致一些额外渲染,生产模式下这类问题会大幅缓解,建议在生产环境测试验证最终效果。
内容的提问来源于stack exchange,提问作者ZachSal

