qiankun微服务搭建之【react+nextJs】

qiankun + React + Next.js 微服务搭建实战记录

本文基于《qiankun-react项目实战》中的思路,从 0 搭建一个:

  • 主应用(纯 HTML + qiankun,端口 7000)
  • React 微应用(基于 Vite + React 18 + react-router-dom 6,端口 5173)

并完整记录从初始化项目、接入 qiankun,到启动、联调和验证的全部过程。

一、整体架构设计

  • 主应用:负责导航和微应用的注册、加载、卸载
    • 使用 qiankunregisterMicroAppsstart API
    • 使用简单的 http-server 静态服务
    • 提供一个容器 DOM:<main id="container"></main>
  • React 微应用:业务实现
    • 使用 React 18 + React Router v6
    • 使用 Vite 作为构建工具
    • 通过 vite-plugin-qiankun 适配为 qiankun 微应用
    • 路由 basename 与微应用 activeRule 一致:/app-react
  • Next 微应用:业务实现
    • 使用 Next.js 13
    • 通过 basePath: '/app-next' 与主应用 activeRule 保持一致
    • window 上导出 bootstrap/mount/unmount 生命周期,通过 JS Entry 或 HTML Entry 接入

二、主应用搭建过程

1. 创建主应用目录

在仓库根目录下创建主应用文件夹:

  • 目录:main-app/
  • 目的:单独存放主应用的静态资源和脚本

2. 初始化主应用依赖

main-app 目录下准备 package.json

json 复制代码
{
  "name": "main-app",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "start": "http-server -p 7000 -c-1"
  },
  "dependencies": {
    "qiankun": "^2.14.0"
  },
  "devDependencies": {
    "http-server": "^14.1.1"
  }
}

说明:

  • 使用 http-server 在 7000 端口提供静态服务
  • 使用最新的 qiankun 2.x 版本
  • -c-1 关闭缓存,便于开发调试

3. 编写主应用 HTML

文件:main-app/index.html

html 复制代码
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>主应用 - qiankun</title>
    <style>
      body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, 'Segoe UI Emoji'; margin: 0; }
      header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: #222; color: #fff; }
      nav a, nav button { margin-right: 12px; color: #fff; background: transparent; border: 1px solid #fff; padding: 6px 10px; cursor: pointer; }
      #container { padding: 16px; min-height: 60vh; }
      #loading { padding: 16px; color: #666; }
    </style>
  </head>
  <body>
    <header>
      <div>主应用</div>
      <nav>
        <button id="btn-home">主应用首页</button>
        <button id="btn-react">进入 React 微应用</button>
        <button id="btn-next">进入 Next 微应用</button>
      </nav>
    </header>
    <div id="loading"></div>
    <main id="container"></main>
    <script type="module" src="./src/index.js"></script>
  </body>
</html>

关键点:

  • #container:qiankun 微应用挂载容器
  • 两个按钮:分别切换到主应用首页和 React 微应用

4. 注册 React 微应用并启动 qiankun

文件:main-app/src/index.js

javascript 复制代码
import { registerMicroApps, start, initGlobalState } from 'qiankun';

const loadingEl = document.querySelector('#loading');
function setLoading(loading) {
  loadingEl.textContent = loading ? '加载中...' : '';
}

const actions = initGlobalState({
  theme: 'light',
  user: { name: 'demo' },
});
actions.onGlobalStateChange((state) => {
  console.log('global state changed', state);
});

registerMicroApps(
  [
    {
      name: 'reactApp',
      entry: 'http://localhost:5173/app-react/',
      container: '#container',
      activeRule: '/app-react',
      props: {
        token: 'demo-token',
        onGlobalStateChange: actions.onGlobalStateChange,
        setGlobalState: actions.setGlobalState,
      },
    },
    {
      name: 'nextApp',
      entry: { scripts: ['http://localhost:3002/app-next/qiankun-lifecycle.js'] },
      container: '#container',
      activeRule: '/app-next',
      props: {
        token: 'next-token',
        onGlobalStateChange: actions.onGlobalStateChange,
        setGlobalState: actions.setGlobalState,
      },
    },
  ],
  {
    beforeLoad: [
      app => {
        setLoading(true);
        console.log('before load', app.name);
      },
    ],
    afterMount: [app => {
      setLoading(false);
      console.log('after mount', app.name);
    }],
    afterUnmount: [app => console.log('after unmount', app.name)],
  }
);

start({
  prefetch: 'all',
  sandbox: { experimentalStyleIsolation: true },
  singular: true,
});

document.querySelector('#btn-home').addEventListener('click', () => {
  history.pushState(null, '', '/');
});

document.querySelector('#btn-react').addEventListener('click', () => {
  history.pushState(null, '', '/app-react/');
});

document.querySelector('#btn-next').addEventListener('click', () => {
  history.pushState(null, '', '/app-next/');
});

要点:

  • entry 指向微应用开发服务器地址:http://localhost:5173
  • activeRule/app-react,后续微应用的路由 basename 也使用同样的前缀
  • 通过生命周期钩子显示加载状态、打印日志,方便调试
  • 使用 history.pushState 切换 URL,触发 qiankun 的路由匹配
  • 已注册 Next 微应用,使用 JS Entry 加载生命周期脚本,导航按钮 #btn-next 进入 /app-next/

至此,主应用部分搭建完成。

三、React 微应用搭建过程

本次实现选择 Vite + React 18 的形态,并结合 vite-plugin-qiankun 接入 qiankun。

1. 创建微应用目录

在仓库根目录下创建:

  • 目录:react-app/

用于承载 React 微应用的所有代码。

2. 初始化 React 微应用依赖

文件:react-app/package.json

json 复制代码
{
  "name": "react-app",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --port 5173"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.0"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "@vitejs/plugin-react": "^4.2.0",
    "vite-plugin-qiankun": "^1.0.15"
  }
}

选择说明:

  • 使用 React 18,方便与当前文档中的 React 18 示例对齐
  • 使用 React Router v6,配合 RoutesRoute API
  • Vite 5 + @vitejs/plugin-react 提供开发体验
  • vite-plugin-qiankun 将 Vite 项目改造为 qiankun 微应用

3. 配置 Vite 适配 qiankun

文件:react-app/vite.config.js

javascript 复制代码
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import qiankun from 'vite-plugin-qiankun';

export default defineConfig({
  base: '/app-react/',
  plugins: [
    react(),
    qiankun('reactApp', { useDevMode: true }),
  ],
  server: {
    port: 5173,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
});

关键配置:

  • base: '/app-react/':确保构建产物中的静态资源路径与微应用部署前缀一致
  • qiankun('reactApp', { useDevMode: true })
    • reactApp 对应主应用中注册时的 name
    • useDevMode: true 允许在开发环境直接通过 Vite dev server 提供微应用
  • server.headers:允许主应用跨域加载资源(开发阶段使用 * 即可)

4. 编写微应用入口 HTML

文件:react-app/index.html

html 复制代码
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React 微应用</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
  </html>

与普通 Vite + React 项目基本一致,唯一需要注意的是:

  • 根节点 id 为 root,在微应用生命周期中需要根据 qiankun 提供的 container 精确找到这个节点

5. 编写 React 入口和生命周期

文件:react-app/src/main.jsx

javascript 复制代码
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import App from './App.jsx';

let root = null;

function render(props = {}) {
  const container = props.container || document;
  const dom = container.querySelector('#root');
  root = createRoot(dom);
  root.render(
    <BrowserRouter basename={qiankunWindow.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
      <Routes>
        <Route path="/" element={<App />} />
      </Routes>
    </BrowserRouter>
  );
}

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  render();
}

renderWithQiankun({
  bootstrap() {},
  mount(props) {
    render(props);
  },
  unmount() {
    root?.unmount();
    root = null;
  },
});

对应关系:

  • qiankunWindow.__POWERED_BY_QIANKUN__:判断当前是否运行在 qiankun 容器内
    • 子应用独立运行(本地调试):false,正常以 / 为根路由
    • 被主应用加载:true,以 /app-reactbasename
  • renderWithQiankun:包装 bootstrap/mount/unmount 生命周期
  • mount(props):由 qiankun 在激活微应用时调用,将 props.container 作为根节点查找范围
  • unmount:调用 root.unmount() 完整卸载 React 应用,释放资源

6. 编写简单的 React 页面

文件:react-app/src/App.jsx

javascript 复制代码
import React from 'react';

export default function App() {
  return (
    <div style={{ padding: 16 }}>
      <h1>React 微应用</h1>
      <p>这是通过 qiankun 接入的 React 微应用。</p>
    </div>
  );
}

此处先保持页面简单清晰,后续可以继续扩展路由和业务组件。

四、启动与联调步骤

1. 安装依赖

在项目根目录下分别安装主应用和微应用依赖(命令示例):

bash 复制代码
cd main-app
npm install

cd ../react-app
npm install

2. 启动 React 微应用

进入 react-app 目录:

bash 复制代码
npm run dev

Vite 默认会在 http://localhost:5173 启动开发服务器,与主应用中配置的 entry 一致。

此时可以直接访问 http://localhost:5173 验证子应用独立运行是否正常。

3. 启动主应用

进入 main-app 目录:

bash 复制代码
npm run start

http-server 会在 http://localhost:7000 提供静态服务。

在浏览器访问:

  • 打开 http://localhost:7000
  • 点击"进入 React 微应用"按钮,地址栏变为 http://localhost:7000/app-react
  • #container 中会渲染 React 微应用的内容

4. 验证路由与卸载

  • 在微应用页面中刷新浏览器,确认路由 basename 配置正确
  • 点击浏览器"后退",确认微应用卸载,主应用首页恢复正常

可以通过控制台日志进一步确认:

  • before load reactApp
  • after mount reactApp
  • 切换到其他路由后,看到 after unmount reactApp

五、与文档内容的对应关系

1. 主应用部分

文档中的主应用示例:

  • 使用 registerMicroApps 注册微应用数组
  • 调用 start() 启动 qiankun
  • 可以配置 prefetchsandboxsingular 等参数

本实战中:

  • 完整使用了 registerMicroAppsstart,并结合生命周期钩子实现加载状态展示
  • start 中启用了 prefetch: 'all'sandbox.strictStyleIsolationsingular: true,与文档中的最佳实践保持一致

2. React 微应用部分

文档给出的 React 微应用方案主要基于:

  • CRA + webpack + public-path.js
  • ReactDOM.render / ReactDOM.unmountComponentAtNode
  • React Router v5 的 BrowserRouter + basename

本实战做了如下调整:

  • 使用 Vite 替代 CRA + webpack,简化配置
  • 使用 React 18 的 createRoot / root.unmount
  • 使用 React Router v6 的 Routes + Route API
  • 通过 vite-plugin-qiankun 替代手写 public-path.js 和生命周期导出

同时保持了关键的一致性:

  • 路由前缀 basenameactiveRule 保持一致:/app-react
  • 独立运行与被主应用加载两种模式都可兼容

六、踩坑与经验

  1. 路由前缀必须一致

    • 主应用注册微应用时 activeRule: '/app-react'
    • 微应用中 BrowserRouterbasename 必须使用同样的前缀
  2. 开发环境跨域

    • 主应用和微应用端口不同(7000 与 5173)
    • 主应用通过 entry 加载微应用的 HTML 和静态资源,需要设置 CORS 头
    • 本实战在 Vite 的 server.headers 中设置了 Access-Control-Allow-Origin: '*'
  3. React 18 卸载方式

    • React 18 推荐使用 createRoot / root.unmount,不再使用 ReactDOM.unmountComponentAtNode
    • 保证在 unmount 生命周期中调用 root.unmount(),避免内存泄漏
  4. 独立运行与微应用模式兼容

    • 通过判断 qiankunWindow.__POWERED_BY_QIANKUN__ 分支渲染
    • 在独立模式下直接 render(),在微应用模式下由 renderWithQiankun 接管
  5. 严格样式隔离的权衡

    • strictStyleIsolation 会使用 Shadow DOM 包裹微应用根节点
    • 可以有效隔离样式,但某些依赖全局样式的组件库可能需要额外适配

七、总结

通过本次实战,我们完成了:

  • 从 0 搭建一个 qiankun 主应用
  • 接入一个 React 18 + React Router v6 + Vite 的微应用
  • 实现微应用独立运行与被主应用加载两种模式
  • 应用文档中提到的关键概念:activeRulebasename、生命周期钩子、沙箱配置等

在此基础上,可以继续扩展:

  • 增加更多微应用(例如 Vue、Angular)
  • 接入全局状态管理,实现主应用与微应用之间的实时通信
  • 构建更完善的监控与埋点系统,追踪每个微应用的加载与运行情况

这篇记录可作为在团队内推广 qiankun + React + Next.js 微前端实践的参考示例。

八、排错指南:React 微应用生命周期导出失败

  • 现象
    • 进入 React 微应用时报错:You need to export lifecycle functions in reactApp entry
    • 控制台同时出现 [import-html-entry]: error occurs while executing normal script <script type="module">import { injectIntoGlobalHook } from "/app-react/@react-refresh" ...,页面停留在"加载中"
  • 根因
    • Vite 开发模式会注入 React Fast Refresh 的模块脚本(type="module")
    • qiankun 的 HTML entry 将脚本当作普通脚本执行,无法处理 ES Module 的 import,导致生命周期注册代码未执行,从而判定未导出 lifecycles
  • 修复步骤
    • 关闭 Fast Refresh 与 HMR,以避免注入模块脚本
      • 修改 [react-app/vite.config.js](file:///d:/otherProject/qiankun-app-example/react-app/vite.config.js):react({ fastRefresh: false })server.hmr: false
    • 改用 UMD/JS Entry 方式加载子应用,避免 HTML 解析与模块注入问题
      • 在微应用添加 UMD 构建配置:见 [react-app/vite.config.js](file:///d:/otherProject/qiankun-app-example/react-app/vite.config.js) 的 build.lib
      • 构建并启动静态资源服务(含 CORS):
        • 执行 npm run build 生成 dist/index.umd.cjs
        • 执行 npm run serve 启动 5174 端口(已开启 --cors
      • 主应用注册改为 JS Entry:
        • main-app/src/index.js\](file:///d:/otherProject/qiankun-app-example/main-app/src/index.js#L1-L77) 使用 `entry: { scripts: ['http://localhost:5174/index.umd.cjs'] }`

      • 打开 http://localhost:7000/app-react,正常加载子应用;控制台不再出现 @react-refresh 相关错误

九、UMD/JS Entry 与 HTML Entry 的差异说明

  • HTML Entry
    • 入口类型:子应用的 HTML 地址(包含脚本、样式)
    • 执行方式:qiankun 拉取 HTML,解析并按顺序执行脚本与样式
    • 优点:与多数前端构建产物匹配,结构直观
    • 注意:开发模式下若注入 ES Module(如 React Fast Refresh),会因 type="module" 导致执行失败;需关闭相关注入或改用 JS Entry
  • JS Entry(UMD)
    • 入口类型:脚本数组(通常为 UMD/可直接执行的 JS)
    • 执行方式:qiankun 直接加载并执行脚本,期待脚本在全局导出 lifecycles
    • 优点:避免 HTML 解析与模块注入差异,开发联调更稳
    • 注意:需开启 CORS;并保证打包产物以 UMD 形式导出并在 window 下可访问(vite build.libname 对应全局变量名)
  • 推荐实践
    • 开发环境:优先使用 JS Entry(UMD)+ 关闭 Fast Refresh/HMR,保证稳定加载
    • 生产环境:可使用 HTML Entry(构建后的静态 HTML),避免开发注入脚本;确保资源路径与 activeRule/basename 一致
    • 始终保证路由前缀一致:activeRule 与子应用路由 basename 使用同一前缀

十、检查清单(集成前后自测)

  • React 微应用

    • vite base 与主应用 activeRule 前缀一致(/app-react
    • 若使用开发模式:fastRefresh: falseserver.hmr: false
    • UMD 构建配置正确(build.lib),产出 index.umd.cjs
    • 静态服务开启 CORS(--cors
  • 主应用

    • React 微应用使用 JS Entry 引入 UMD 脚本
      • main-app/src/index.js\](file:///d:/otherProject/qiankun-app-example/main-app/src/index.js#L1-L77) 的 `entry.scripts` 指向 `http://localhost:5174/index.umd.cjs`

    • 生命周期钩子有统一 loader/错误兜底展示
  • Next 微应用

    • basePath: '/app-next'activeRule: '/app-next' 一致
      • next-app/next.config.js\](file:///d:/otherProject/qiankun-app-example/next-app/next.config.js)

      • next-app/public/qiankun-lifecycle.js\](file:///d:/otherProject/qiankun-app-example/next-app/public/qiankun-lifecycle.js),并在 \[pages/_document.js\](file:///d:/otherProject/qiankun-app-example/next-app/pages/_document.js) 注入

  • 现象

    • 进入 Next 微应用时,控制台报错:application 'nextApp' died in status LOADING_SOURCE_CODE: Failed to fetch
    • 指向的资源通常是页面中注入的生命周期脚本或其他静态资源
  • 根因

    • qiankun 的 import-html-entry 拉取子应用 HTML 与脚本时会使用 fetch,并可能携带凭据(credentials: include
    • 当请求携带凭据时,CORS 不允许 Access-Control-Allow-Origin: *,必须返回具体来源,并且需要允许凭据
  • 修复

    • 在 Next 中设置明确的跨域响应头(针对主应用的来源):
      • 示例:在 [next-app/next.config.js](file:///d:/otherProject/qiankun-app-example/next-app/next.config.js) 中配置
        • Access-Control-Allow-Origin: http://localhost:7000
        • Access-Control-Allow-Credentials: true
        • Access-Control-Allow-Methods: GET,OPTIONS
        • Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
    • 保证入口 URL 使用尾斜杠(目录语义),避免相对路径解析错误
      • 主应用注册 Next:entry: { scripts: ['http://localhost:3002/app-next/qiankun-lifecycle.js'] },见 [main-app/src/index.js](file:///d:/otherProject/qiankun-app-example/main-app/src/index.js#L1-L77)
    • 保证静态资源路径与 basePath 前缀一致
      • 例如生命周期脚本:在 [next-app/pages/_document.js](file:///d:/otherProject/qiankun-app-example/next-app/pages/_document.js) 注入 /app-next/qiankun-lifecycle.js
  • 验证

    • 重启 Next dev 后,在主应用访问 /app-next,资源加载成功,错误消失
    • 网络面板中可见 Access-Control-Allow-Origin 返回为主应用地址,且响应包含 Access-Control-Allow-Credentials: true

常见问题

qiankun.js?v=562334ee:4903

Uncaught TypeError: application 'nextApp' died in status LOADING_SOURCE_CODE: application 'nextApp' died in status LOADING_SOURCE_CODE: application 'nextApp' died in status LOADING_SOURCE_CODE: application 'nextApp' died in status LOADING_SOURCE_CODE: application 'nextApp' died in status LOADING_SOURCE_CODE: http://localhost:3001/qiankun-lifecycle.js Failed to fetch

Uncaught TypeError: application 'nextApp' died in status LOADING_SOURCE_CODE: application 'nextApp' died in status LOADING_SOURCE_CODE: Failed to fetch

Uncaught TypeError: application 'nextApp' died in status LOADING_SOURCE_CODE: application 'nextApp' died in status LOADING_SOURCE_CODE: Failed to fetch

十二、Next.js 微应用集成(本仓库实作)

本仓库已新增 Next 微应用目录:next-app/,并完成与主应用的集成,点击主应用导航中的"进入 Next 微应用"即可进入。

1. Next 应用结构与关键文件

  • 目录:next-app/
  • 关键文件:
    • 包管理与脚本:[package.json](file:///d:/otherProject/qiankun-app-example/next-app/package.json)
    • 基础配置(路由前缀与 CORS):[next.config.js](file:///d:/otherProject/qiankun-app-example/next-app/next.config.js)
    • 文档注入生命周期脚本:[pages/_document.js](file:///d:/otherProject/qiankun-app-example/next-app/pages/_document.js)
    • 微应用首页:[pages/index.js](file:///d:/otherProject/qiankun-app-example/next-app/pages/index.js)
    • 导出 qiankun 生命周期脚本:[public/qiankun-lifecycle.js](file:///d:/otherProject/qiankun-app-example/next-app/public/qiankun-lifecycle.js)

2. 配置要点

  • 路由前缀:basePath: '/app-next' 与主应用 activeRule: '/app-next' 保持一致
    见 [next-app/next.config.js](file:///d:/otherProject/qiankun-app-example/next-app/next.config.js)
  • 跨域:为主应用来源 http://localhost:7000 设置允许跨域与凭据
    见 [next-app/next.config.js](file:///d:/otherProject/qiankun-app-example/next-app/next.config.js) 的 headers()
  • 生命周期导出:在窗口对象上导出 bootstrap/mount/unmount
    见 [next-app/public/qiankun-lifecycle.js](file:///d:/otherProject/qiankun-app-example/next-app/public/qiankun-lifecycle.js)
  • 注入脚本:在文档中通过 <script src="/app-next/qiankun-lifecycle.js"> 注入
    见 [next-app/pages/_document.js](file:///d:/otherProject/qiankun-app-example/next-app/pages/_document.js)

3. 主应用注册与导航

主应用已注册 Next 微应用,入口为带尾斜杠的目录语义:

  • 注册与按钮事件:
    • main-app/src/index.js\](file:///d:/otherProject/qiankun-app-example/main-app/src/index.js#L30-L72)

  • 入口地址:entry: { scripts: ['http://localhost:3002/app-next/qiankun-lifecycle.js'] }
  • 点击"进入 Next 微应用"按钮后,地址栏变为 http://localhost:7000/app-next/

4. 启动与联调

  • 安装依赖并启动 Next:
    • 进入 next-app/npm install && npm run dev(默认端口 3002)
  • 启动主应用:
    • 进入 main-app/npm install && npm run start(默认端口 7000)
  • 浏览器访问:
    • 打开 http://localhost:7000,点击"进入 Next 微应用"
    • #container 容器中渲染 Next 微应用页面

5. 卸载与返回

  • 点击浏览器"后退"返回主应用首页,微应用触发 unmount,清空 #__next 内容
  • 可在控制台观察生命周期触发日志与 CORS 响应头

十三、开发模式稳定加载方案(JS Entry + 独立生命周期服务)

为避免 Next 开发模式下 HTML Entry/模块脚本导致的加载不稳定,采用以下稳定方案:

核心思路

  • 改为 JS Entry:主应用仅加载生命周期脚本,避免 HTML 解析差异
  • 独立生命周期静态服务:明确返回主应用来源与允许凭据的 CORS 响应头
  • 生命周期函数返回 Promise,满足 single-spa/qiankun 的异步要求

改动点(本仓库)

  • 主应用 entry 指向独立生命周期服务
    • main-app/src/index.js\](file:///d:/otherProject/qiankun-app-example/main-app/src/index.js#L30-L34)

  • 独立生命周期静态服务(端口 3004)
    • next-app/scripts/serve-lifecycle.js\](file:///d:/otherProject/qiankun-app-example/next-app/scripts/serve-lifecycle.js)

    • 地址:http://localhost:3004/app-next/qiankun-lifecycle.js
  • 生命周期脚本返回 Promise
    • next-app/public/qiankun-lifecycle.js\](file:///d:/otherProject/qiankun-app-example/next-app/public/qiankun-lifecycle.js)

  • 可选:Next 中间件统一返还 CORS 头(开发时)
    • next-app/middleware.js\](file:///d:/otherProject/qiankun-app-example/next-app/middleware.js)

  1. 启动 Next dev:npm run dev(端口 3002)
  2. 启动生命周期服务:npm run serve:lifecycle(端口 3004)
  3. 启动主应用:npm run start(端口 7000)
  4. 在主应用页面点击"进入 Next 微应用",应正常显示内容;来回切换 React/Next 页面均稳定

说明

  • 开发联调推荐 JS Entry + 独立脚本服务以确保稳定;生产可回到构建后的 HTML Entry,并确保 basePathactiveRule 一致且服务端返回明确 CORS。

十四、为何 Next 使用 JS Entry(而非 HTML Entry)

  • 背景

    • 主应用在注册 Next 微应用时使用 JS Entry:
      • 参考 [main-app/src/index.js:L31-L32](file:///d:/otherProject/qiankun-app-example/main-app/src/index.js#L31-L32)
      • entry: { scripts: ['http://localhost:3002/app-next/qiankun-lifecycle.js'] }
    • 非 HTML Entry:未直接使用 entry: 'http://localhost:3002/app-next/'
  • 原因

    • 生命周期确定性:JS Entry 的脚本会在 window 上明确导出 bootstrap/mount/unmount,qiankun 能稳定调用;HTML Entry 依赖页面脚本自行导出,Next 默认并不会这么做,容易出现"未导出生命周期"的报错
    • 开发模式更稳:Next dev 会注入 HMR、Overlay 等模块脚本,HTML Entry 的解析与执行更易受这些差异影响;JS Entry 不解析 HTML,仅加载生命周期脚本,避免干扰
    • CORS 与凭据:HTML Entry 通过 import-html-entry 拉取页面与脚本,通常包含 credentials;若服务端未针对主应用来源返回精确的 CORS 响应头,易报 Failed to fetch;JS Entry 只需拉取一个脚本,跨域头配置更简单、可控
    • 路径语义:HTML Entry 要求入口使用尾斜杠(目录语义)且与 basePath 一致,否则相对资源解析错位;JS Entry 只需一个明确的脚本 URL,避免路径陷阱
  • 何时可使用 HTML Entry

    • 满足以下前提即可改回 HTML Entry:
      • Next 配置 basePath: '/app-next' 且在 [next-app/next.config.js](file:///d:/otherProject/qiankun-app-example/next-app/next.config.js) 中为主应用来源 http://localhost:7000 返回明确的 CORS 头(含 Access-Control-Allow-Credentials: true
      • 在文档中注入生命周期脚本:见 [pages/_document.js](file:///d:/otherProject/qiankun-app-example/next-app/pages/_document.js) 使用 <script src="/app-next/qiankun-lifecycle.js" defer></script>
      • 主应用入口地址使用尾斜杠目录语义:entry: 'http://localhost:3002/app-next/'
  • 建议

    • 开发阶段优先 JS Entry,保证加载稳定与生命周期确定性
    • 生产构建后可使用 HTML Entry,但务必确保上述 CORS、路径与生命周期导出条件满足

    效果图

相关推荐
爱内卷的学霸一枚18 小时前
现代微服务架构实践:从设计到部署的深度解析
windows·微服务·架构
小码哥06819 小时前
代驾系统微服务容器化部署与灰度发布流程
微服务·云原生·代驾系统·代驾·代驾服务·同城代驾
Crazy Struggle20 小时前
推荐 .NET 8.0 开源项目伪微服务框架
微服务·.net 8.0·微服务框架
小马爱打代码1 天前
熔断限流从入门到实战:打造高可用微服务架构
微服务·云原生·架构
黑棠会长1 天前
微服务实战.06 |微服务对话时,你选择打电话还是发邮件?
微服务·云原生·架构·c#
程序员泠零澪回家种桔子1 天前
微服务日志治理:ELK 栈实战指南
后端·elk·微服务·云原生·架构
凯子坚持 c1 天前
C++基于微服务脚手架的视频点播系统---客户端(2)
开发语言·c++·微服务
凯子坚持 c1 天前
C++基于微服务脚手架的视频点播系统---客户端(3)
开发语言·c++·微服务