Next.js使用react-bootstrap Navbar刷新后CSS异常报错如何解决
最近我在Next.js项目中使用react-bootstrap的Navbar组件时遇到异常,不清楚问题根源:
页面首次加载时Navbar展示正常:
但刷新页面后就会出现CSS样式错乱问题:
同时控制台会抛出对应的报错信息:
我的Navbar组件代码如下:
<Navbar collapseOnSelect expand="lg" bg="danger" variant="dark"> <Container> <Link href="/" passHref> <Navbar.Brand>Tecnologia Única</Navbar.Brand> </Link> <Navbar.Toggle aria-controls="responsive-navbar-nav" /> <Navbar.Collapse id="responsive-navbar-nav"> {auth.isLoggedIn && ( <> <Nav className="me-auto"> <NavDropdown title="Usuários" id="collasible-nav-dropdown"> <Link href="/users/" passHref> <NavDropdown.Item>Listagem</NavDropdown.Item> </Link> <Link href="/users/create" passHref> <NavDropdown.Item>Novo</NavDropdown.Item> </Link> </NavDropdown> </Nav> <Nav className={styles.center}> <Navbar.Text>Bem-vindo, {auth.user.name}</Navbar.Text> <Nav.Link onClick={logoutHandler}> <Button variant="outline-light">Sair</Button> </Nav.Link> </Nav> </> )} {!auth.isLoggedIn && ( <Nav> <Link href="/login" passHref> <Nav.Link> <Button variant="outline-light">Entrar</Button> </Nav.Link> </Link> </Nav> )} </Navbar.Collapse> </Container> </Navbar>
补充:auth.isLoggedIn的实现代码如下:
import { createContext, useState, ReactNode } from "react"; import { useRouter } from "next/router"; interface IAuthContext { isLoggedIn: boolean; token: string | null; user: IUserData; onLogout: () => void; onLogin: (data: LoginData) => void; } interface IUserData { name: string; email: string; document: string; perfil: number; } const AuthContext = createContext<IAuthContext>({ isLoggedIn: false, token: "", user: { document: "", email: "", name: "", perfil: 0 }, onLogout: () => {}, onLogin: (data: LoginData) => {}, }); type AuthContextProviderProps = { children: ReactNode; }; type LoginData = { Id: number | null; IsUsuarioAtivo: boolean; TipoPerfil: number; Token: string; Nome: string; CPF: string; Email: string; Login: string | null; Perfis: string | null; UserIdFB: string | null; AccessTokenFB: string | null; }; export const AuthContextProvider = (props: AuthContextProviderProps) => { const router = useRouter(); let initialToken: string | null = ""; if (typeof window !== "undefined") { initialToken = localStorage.getItem("token"); } const [token, setToken]: any = useState(initialToken); const isUserLoggedIn = !!token; const [user, setUser]: any = useState<IUserData>({ document: "", email: "", name: "", perfil: 0, }); const logoutHandler = () => { setToken(null); localStorage.removeItem("token"); router.push("/login"); }; const loginHandler = (data: LoginData) => { console.log(data); setToken(data.Token); localStorage.setItem("token", data.Token); setUser({ name: data.Nome, email: data.Email, document: data.CPF, perfil: data.TipoPerfil, }); router.push("/"); }; return ( <AuthContext.Provider value={{ user: user, token: token, isLoggedIn: isUserLoggedIn, onLogout: logoutHandler, onLogin: loginHandler, }} > {props.children} </AuthContext.Provider> ); }; export default AuthContext;
问题原因
该问题是Next.js服务端渲染(SSR)与客户端hydration结构不匹配导致的:
- 服务端渲染阶段没有
window对象,你代码里typeof window !== "undefined"判断为false,initialToken为空字符串,isUserLoggedIn值为false,所以服务端返回的HTML是未登录状态的Navbar结构,仅显示「Entrar」按钮。 - 客户端hydration阶段会读取localStorage里存储的token,此时
isUserLoggedIn变为true,客户端渲染的是登录后带用户菜单的Navbar结构,和服务端返回的HTML结构不一致,触发hydration mismatch错误,react-bootstrap的样式依赖组件结构生成,结构不匹配就会出现样式错乱。
解决方案
方案1:延迟登录状态判断,保证hydration一致性
在AuthContext里添加挂载状态判断,等客户端组件挂载完成后再读取localStorage的token,确保服务端和客户端首次渲染结构完全一致:
// 修改AuthContextProvider代码 export const AuthContextProvider = (props: AuthContextProviderProps) => { const router = useRouter(); // 初始token设为null,服务端和客户端首次渲染保持一致 const [token, setToken] = useState<string | null>(null); const [isHydrated, setIsHydrated] = useState(false); // 仅在客户端挂载后读取本地存储的token useEffect(() => { const storedToken = localStorage.getItem("token"); setToken(storedToken); setIsHydrated(true); }, []); const isUserLoggedIn = !!token; // 其余代码保持不变... // 未完成hydration时返回占位内容,避免结构差异 if (!isHydrated) return <div style={{ height: '56px' }} />; // 高度和Navbar保持一致即可 return ( <AuthContext.Provider value={{ user: user, token: token, isLoggedIn: isUserLoggedIn, onLogout: logoutHandler, onLogin: loginHandler, }} > {props.children} </AuthContext.Provider> ); };
方案2:禁用Navbar组件的SSR
使用Next.js的dynamic导入,指定Navbar组件仅在客户端渲染,直接规避服务端和客户端结构不一致的问题:
// 在引入Navbar的页面/组件中修改导入方式 import dynamic from 'next/dynamic'; const CustomNavbar = dynamic(() => import('../components/Navbar'), { ssr: false }); // 直接使用<CustomNavbar />即可
额外检查
确保你在项目的_app.tsx/_app.jsx中全局引入了bootstrap样式,避免样式加载顺序问题:
import 'bootstrap/dist/css/bootstrap.min.css';
内容的提问来源于stack exchange,提问作者Matheus Foresto Moselli
相关产品推荐
相关产品推荐

