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

Vite+React+Express SSR疑问:客户端路由切换页面源未更新

问题解答

现象是否正常?

这是正常现象。你当前的实现是「传统SSR + 客户端SPA路由」的组合:

  • 初始页面加载时,服务器完成SSR渲染,返回包含完整页面内容的HTML,所以源码显示正常;
  • 一旦客户端通过hydrateRoot完成应用水合,后续的视图切换是通过React客户端路由实现的——这种切换仅在浏览器端动态更新DOM,不会向服务器发起新的HTML请求,因此页面源码不会被更新。

如何实现客户端切换视图时同步更新页面源码?

如果需要切换视图时页面源码同步更新,本质是让每次视图切换都触发服务器的SSR渲染,有两种常见方案:

方案1:使用服务端导航(放弃SPA无刷新体验)

直接用原生<a>标签代替客户端路由组件,每次点击导航都会向服务器发起新请求,服务器重新SSR渲染对应页面并返回新HTML。这种方式简单直接,但会失去SPA的无刷新跳转体验。

方案2:同构路由 + 按需服务端渲染(兼顾体验和源码更新)

保留客户端路由的无刷新体验,同时在需要SEO/源码更新的场景下主动触发服务器渲染,核心步骤如下:

1. 修复服务端路由渲染逻辑

当前entry-server.tsx未根据请求URL渲染对应路由,需配置React Router的StaticRouter处理服务端路由,同时解决Store单例共享问题:

// entry-server.tsx
import React from "react";
import ReactDOMServer from "react-dom/server";
import { Provider } from "react-redux";
import { createStore } from "@store/index"; // 改为创建新Store的函数,不要用单例
import { StaticRouter } from "react-router-dom/server";
import App from "./App";

export function render(url: string) {
  // 每个请求创建独立Store实例,避免多用户状态共享
  const store = createStore();
  
  const html = ReactDOMServer.renderToString(
    <Provider store={store}>
      <StaticRouter location={url}>
        <App />
      </StaticRouter>
    </Provider>
  );

  // 可在此收集页面meta/title等信息,填充到模板head
  const head = /* 收集的页面头部内容 */;
  
  return { html, head };
}

2. 确保客户端与服务端路由规则一致

客户端使用BrowserRouter,保证路由规则与服务端完全匹配:

// entry-client.tsx
import React from "react";
import { Provider } from "react-redux";
import { createStore } from "@store/index";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";

const store = createStore();

ReactDOM.hydrateRoot(
  document.getElementById("root") as HTMLElement,
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
);

3. 按需触发服务端渲染

对需要源码更新的路由(如商品详情、文章页),在导航时主动发起服务器请求:

// 示例导航组件
import { useNavigate } from "react-router-dom";

function NavLink({ to, needSSR }) {
  const navigate = useNavigate();
  
  const handleClick = (e) => {
    if (needSSR) {
      // 触发服务器请求,刷新页面
      window.location.href = to;
    } else {
      // 客户端路由无刷新跳转
      e.preventDefault();
      navigate(to);
    }
  };
  
  return <a href={to} onClick={handleClick}>跳转</a>;
}

额外代码问题修复

当前实现还有两个关键问题需修正:

  • 服务端Store单例问题:原代码直接导入单例Store,会导致多用户请求共享状态,必须改为每个请求创建新实例;
  • styled-components SSR配置:vite.config中babel-plugin-styled-components的ssr设为false会导致SSR样式失效,应改为ssr: true。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 15:47:09