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

如何从远程微前端应用导航至宿主应用路由?

微前端路由导航问题:子应用跳转宿主登录页失败

环境与背景

  • 基于Cam Jackson方案实现React微前端,包含3个应用:宿主应用、child1子应用、child2子应用
  • 当前问题:子应用1的路由守卫在token过期时调用navigate('/'),无法跳转到宿主的登录页,而是在子应用路由范围内查找匹配项

核心代码片段

宿主应用路由

const Child1 = ({ history }) => (
  <MicroFrontend history={history} host={childapp1} name="child1" />
);

const Child2 = ({ history }) => (
  <MicroFrontend history={history} host={childapp2} name="child2" />
);

const App = () => (
  <BrowserRouter>
  <Account>
    <React.Fragment>
    <ReactNotifications />
      <AppHeader />
      <Routes>
      <Route exact path="/" element={<Signin/>} />
        <Route exact path="/about" element={<About/>} />
        <Route exact path="/child1/*" element={<Child1/>} />
        <Route exact path="/child2/*" element={<Child2/>} />
      </Routes>
    </React.Fragment>
    </Account>
  </BrowserRouter>
);

子应用1路由

<BrowserRouter>
  <Account>
    <React.Fragment>
    <ReactNotifications />
      <Routes>
      <Route path='child1' element={<ProtectedRoute><Home/></ProtectedRoute>}/>
        <Route
          path="child1/dashboard"
          element={
            <ProtectedRoute>
              <Dashboard/>
            </ProtectedRoute>
          }
        />
        <Route path="child1/forbidden" element={<ForbiddenPage/>}
        />
      </Routes>
    </React.Fragment>
    </Account>
  </BrowserRouter>

子应用1的ProtectedRoute(问题代码)

const ProtectedRoute = ({ children }) => {
    const { authData } = useAuth()
    const navigate = useNavigate()
    
    console.log(authData, 'authData');

    if (authData.isAuthenticated && authData.expiryTime > Date.now()) {
        return children
    }
    else{
        navigate('/')
    }
};

export default ProtectedRoute;

MicroFrontend组件代码

import React from 'react';

class MicroFrontend extends React.Component {
  componentDidMount() {
    const { name, host, document } = this.props;
    const scriptId = `micro-frontend-script-${name}`;

    if (document.getElementById(scriptId)) {
      this.renderMicroFrontend();
      return;
    }
    var parentUrl = `${window.location.protocol}//${window.location.href.split('/')[2]}`
    if (name === 'child1'){
      fetch(`${parentUrl}/child1/asset-manifest.json`,{
        method: "GET",
      })
        .then(res =>res.json())
        .then(manifest => {
          console.log(manifest, 'manifest');
          const script = document.createElement('script');
          const linkforCSSfile = document.createElement("link")
  
          linkforCSSfile.href = `${parentUrl}/child1${manifest['files']['main.css']}`
          linkforCSSfile.type = 'text/css'
          linkforCSSfile.rel = 'stylesheet'
  
          script.id = scriptId;
          script.crossOrigin = '';
          script.src = `${parentUrl}/child1${manifest['files']['main.js']}`;
          script.onload = this.renderMicroFrontend;
  
          document.head.appendChild(script);
          document.head.appendChild(linkforCSSfile)
        })
    }
    else{

      fetch(`${parentUrl}/child2/asset-manifest.json`,{
        method: "GET",
      })
        .then(res =>res.json())
        .then(manifest => {
          console.log(manifest, 'manifest');
          const script = document.createElement('script');
          const linkforCSSfile = document.createElement("link")
  
          linkforCSSfile.href = `${parentUrl}/child2${manifest['files']['main.css']}`
          linkforCSSfile.type = 'text/css'
          linkforCSSfile.rel = 'stylesheet'
  
          script.id = scriptId;
          script.crossOrigin = '';
          script.src = `${parentUrl}/child2${manifest['files']['main.js']}`;
          script.onload = this.renderMicroFrontend;
  
          document.head.appendChild(script);
          document.head.appendChild(linkforCSSfile)
    })
  }
    
      
  }

  componentWillUnmount() {
    const { name, window } = this.props;

    window[`unmount${name}`]?.(`${name}-container`);
  }

  renderMicroFrontend = () => {
    const { name, window, history } = this.props;
    console.log(history);

    window[`render${name}`]?.(`${name}-container`, history);
  };

  render() {
    return <main id={`${this.props.name}-container`} />;
  }
}

MicroFrontend.defaultProps = {
  document,
  window,
};

export default MicroFrontend;

问题分析

子应用内部使用独立的<BrowserRouter>,导致useNavigate()仅在子应用自身的路由上下文生效,无法触发宿主路由的跳转。同时,宿主已将history对象传递给MicroFrontend组件,但子应用未复用该对象,而是创建了新的路由上下文。

解决方案

步骤1:子应用移除独立BrowserRouter,复用宿主传递的history

修改子应用入口文件,接收宿主传递的history,使用Router组件复用该历史对象:

// 子应用1入口文件
import { Router } from 'react-router-dom';

window.renderchild1 = (containerId, history) => {
  ReactDOM.render(
    <Router history={history}>
      <Account>
        <React.Fragment>
          <ReactNotifications />
          <Routes>
            <Route path='/' element={<ProtectedRoute><Home/></ProtectedRoute>}/>
            <Route path="/dashboard" element={<ProtectedRoute><Dashboard/></ProtectedRoute>}/>
            <Route path="/forbidden" element={<ForbiddenPage/>}/>
          </Routes>
        </React.Fragment>
      </Account>
    </Router>,
    document.getElementById(containerId)
  );
};

同时移除子应用路由中的child1前缀,因为宿主已通过/child1/*匹配到子应用,子应用仅需处理相对路径。

步骤2:修改ProtectedRoute,确保导航生效

复用宿主history后,useNavigate()会自动使用宿主路由上下文,navigate('/')即可正确跳转到宿主登录页。若仍有问题,可使用强制跳转作为备用方案:

const ProtectedRoute = ({ children }) => {
    const { authData } = useAuth()
    const navigate = useNavigate()
    
    if (authData.isAuthenticated && authData.expiryTime > Date.now()) {
        return children
    } else {
        // 方式1:复用宿主路由上下文导航
        navigate('/');
        // 方式2:强制跳转(备用)
        // window.location.href = '/';
    }
};

步骤3:确保子应用unmount函数正确清理

修改子应用unmount函数,确保移除Router相关渲染:

window.unmountchild1 = (containerId) => {
  ReactDOM.unmountComponentAtNode(document.getElementById(containerId));
};

预期流程验证

宿主登录页 -> child1首页 -> token过期 -> 自动跳转到宿主登录页


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:17:03