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

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)过程中的结构不匹配:

  1. 服务端渲染时,Router内部嵌套的Suspense会生成带SolidJS水合标记的DOM节点;
  2. 客户端水合阶段,由于Suspense的位置不符合SolidJS Router的SSR兼容规范,导致客户端无法正确复用服务端渲染的节点,而是重新渲染了一次组件,最终出现重复的<main>元素;
  3. 额外说明:你的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 13:08:12