SolidJS路由场景下组件重复渲染问题排查与解决
SolidJS路由渲染两次导致DOM重复的问题分析与解决
问题描述
作为SolidJS新手,在使用路由时发现应用会渲染两次,页面DOM中的<main>元素出现重复。复现代码及渲染后的DOM片段如下:
复现代码
app.tsx
import { Suspense } from "solid-js"; import { useAssets } from "solid-js/web"; import { css, renderSheets, type StyleData } from "solid-styled"; import { Router, Route } from "@solidjs/router"; import Home from './routes/index'; import NotFound from "~/routes/[...404]"; function GlobalStyles() { css` @global { * { transition: all .2s linear; } body { margin: 0; font-family: 'Montserrat', -apple-system, -system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #292929FF; } a { margin-right: 1rem; } main { margin: 0 auto; width: 100%; height: 100vh; display: flex; flex-direction: row; } .content-wrapper { width: calc(100vw - 350px); height: 100%; display: flex; flex-direction: column; } input { border: none; outline: none; box-shadow: none; background-color: transparent; } } `; return null; } export default function App() { return ( <Router> <Suspense> <Route path="/" component={Home} /> </Suspense> </Router> ); }
entry-client.tsx
// @refresh reload import { render } from "solid-js/web"; import App from "./app"; // @ts-ignore render(() => <App />, document.getElementById("app"));
entry-server.tsx
// @refresh reload import { createHandler, StartServer } from "@solidjs/start/server"; export default createHandler(() => ( <StartServer document={({ assets, children, scripts }) => ( <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> {assets} </head> <body> <div id="app">{children}</div> {scripts} </body> </html> )} /> ));
index.tsx(Home组件)
import { Title } from "@solidjs/meta"; import Sidebar from "~/components/Sidebar"; import Navbar from "~/components/Navbar"; export default function Home() { return ( <main> TEST </main> ); }
渲染后的DOM片段
... <div id="app"><!--!$e0-0-0-0-0-0-0--><main data-hk="0-0-0-0-0-0-0-0-2-0-0-0-0-0-0-0">TEST</main><!--!$/e0-0-0-0-0-0-0--><main>TEST</main></div>
成因分析
问题核心是服务端渲染(SSR)与客户端水合(hydration)过程中的结构不匹配:
- 服务端渲染时,
Router内部嵌套的Suspense会生成带SolidJS水合标记的DOM节点; - 客户端水合阶段,由于
Suspense的位置不符合SolidJS Router的SSR兼容规范,导致客户端无法正确复用服务端渲染的节点,而是重新渲染了一次组件,最终出现重复的<main>元素; - 额外说明:你的
Home组件没有异步加载逻辑,此时使用Suspense属于冗余操作,反而会破坏SSR与客户端的结构一致性。
解决办法
方法1:移除冗余的Suspense
由于Home组件没有异步内容,直接去掉Suspense包裹,调整App.tsx代码如下:
export default function App() { return ( <Router> <Route path="/" component={Home} /> <Route path="*" component={NotFound} /> </Router> ); }
方法2:若需使用Suspense(针对异步组件场景)
如果后续Home组件需要异步加载(比如用lazy导入),需将Suspense放在Route内部,作为组件的包裹层,而非直接嵌套在Router下:
// 先改为lazy导入Home import { lazy } from "solid-js"; const Home = lazy(() => import('./routes/index')); export default function App() { return ( <Router> <Route path="/" element={ <Suspense fallback={<div>Loading...</div>}> <Home /> </Suspense> } /> <Route path="*" component={NotFound} /> </Router> ); }
方法3:确保全局样式正确加载(可选)
你的GlobalStyles组件目前未在App中渲染,若需要全局样式,建议在App组件内添加:
export default function App() { return ( <> <GlobalStyles /> <Router> <Route path="/" component={Home} /> <Route path="*" component={NotFound} /> </Router> </> ); }
内容的提问来源于stack exchange,提问作者Xbel
相关产品推荐
相关产品推荐

