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
    • sandbox.strictStyleIsolation 根据需要启用,避免样式污染
    • 生命周期钩子有统一 loader/错误兜底展示
  • Next 微应用
    • basePath: '/app-next'activeRule: '/app-next' 一致
      • next-app/next.config.js(file:///d:/otherProject/qiankun-app-example/next-app/next.config.js)
    • window 上导出 lifecycles(示例脚本):
      • 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) 注入

十一、跨域与凭据导致 Failed to fetch 排错

  • 现象
    • 进入 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)
    • main-app/index.html(file:///d:/otherProject/qiankun-app-example/main-app/index.html#L19-L22)
  • 入口地址: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)
    • entry: { scripts: ['http://localhost:3004/app-next/qiankun-lifecycle.js'] }
  • 独立生命周期静态服务(端口 3004)
    • next-app/scripts/serve-lifecycle.js(file:///d:/otherProject/qiankun-app-example/next-app/scripts/serve-lifecycle.js)
    • 启动:在 next-app 目录执行 npm run serve:lifecycle
    • 地址: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)
    • bootstrap/mount/unmount 均返回 Promise.resolve(),消除 BOOTSTRAPPING 报错(single-spa #15)
  • 可选: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、路径与生命周期导出条件满足

    效果图

相关推荐
fanly113 天前
Surging AI Agent 完整产品介绍
微服务·microservice
垚森6 天前
我用 GLM-5.2 造了个炸裂主题后台:16 套主题随心切,可在线体验
ai·react
蝎子莱莱爱打怪9 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking10 天前
Java微服务练习方式
java·后端·微服务
米丘13 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质16 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质16 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
地瓜伯伯16 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y16 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰17 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理