qiankun微前端使用指南-从开发到部署

qiankun 使用指南(基座 Vue2 + 子应用 Vue2 / Vue3 / React / jQuery)

本文为通用实践文档,不依赖任何现有项目代码,可作为你的微前端接入手册与实施清单。

阅读指南与结构

  • 顺序建议:
    1. 快速上手(基座注册与启动)
    2. 子应用通用改造(publicPath 与生命周期)
    3. 本地开发跨域(CORS / 代理)
    4. 跨应用通信(props / 全局状态 / 动态 update)
    5. 路由与激活规则(Hash/History)
    6. 部署与 Nginx 配置(同域/子域)
    7. 进阶能力(loadMicroApp 等)
    8. 常见问题排查
    9. 实施 Checklist(自检)

目录

  • 适用场景与核心概念
  • 快速上手
  • 子应用通用改造
  • 子应用示例(Vue2 / Vue3 / React / jQuery)
  • 跨应用通信(含基座向子应用传递数据)
  • 路由与激活规则
  • 本地开发跨域(Webpack DevServer)
  • 部署与 Nginx 配置
  • 构建与部署要点清单
  • 进阶能力
  • 常见问题排查
  • 实施 Checklist

最佳实践总览

  • 使用同域子路径部署,entry 指向同源 /app-*,省去跨域复杂度
  • 子应用入口首行引入 public-path.js,打包为 UMD,根节点统一 #app
  • 路由优先 Hash;History 必配 try_files 回退
  • 通信优先全局状态(initGlobalState),props 承载初始化与函数引用,频繁更新用 loadMicroApp().update
  • 本地联调优先“基座代理同源”,次选子应用 CORS;携带凭证需精确 Origin+Credentials
  • 缓存策略:HTML no-cache,静态资源长缓存(带 hash)

适用场景与核心概念

  • 适用场景:多团队/多技术栈共存、垂直拆分大型前端、独立部署与灰度、渐进式改造老系统。
  • 核心概念
    • 基座(主应用):承载导航、布局、权限与微应用加载调度。
    • 子应用(微应用):独立构建与部署,通过 UMD 生命周期接入基座。
    • 激活规则 activeRule:根据 URL(建议 Hash 或 History 前缀)决定子应用何时装载/卸载。
    • 运行时 publicPath 注入:解决生产静态资源路径问题。

快速上手

1) 安装

npm i qiankun --save

2) 基座(Vue2)最小可用集成

  • 在你的基座页面中预留容器:
<!-- App.vue 或基座布局中 -->
<div id="micro-app-container">
  <div id="subapp-root"></div>
</div>
  • 基座注册与启动:
// main.js(或专门的 micro-frontend.ts)
import { registerMicroApps, start, initGlobalState } from 'qiankun';

const getActiveRule = (hashOrList) => (location) => {
  const list = Array.isArray(hashOrList) ? hashOrList : [hashOrList];
  return list.some((prefix) => new RegExp(`^${prefix}($|[/?#].*)`).test(location.hash || location.pathname));
};

registerMicroApps([
  {
    name: 'app-vue2',
    entry: process.env.NODE_ENV === 'production' ? '/app-vue2/' : '//localhost:8081',
    container: '#subapp-root',
    activeRule: getActiveRule('#/app-vue2'),
    props: { fromBase: 'hello' },
  },
  {
    name: 'app-vue3',
    entry: process.env.NODE_ENV === 'production' ? '/app-vue3/' : '//localhost:8082',
    container: '#subapp-root',
    activeRule: getActiveRule('#/app-vue3'),
  },
  {
    name: 'app-react',
    entry: process.env.NODE_ENV === 'production' ? '/app-react/' : '//localhost:8083',
    container: '#subapp-root',
    activeRule: getActiveRule('#/app-react'),
  },
  {
    name: 'app-jquery',
    entry: process.env.NODE_ENV === 'production' ? '/app-jquery/' : '//localhost:8084',
    container: '#subapp-root',
    activeRule: getActiveRule('#/app-jquery'),
  },
], {
  beforeLoad: [() => {/* 显示加载中 */}],
  beforeMount: [() => {/* 切换布局/隐藏基座区域 */}],
  afterUnmount: [() => {/* 恢复布局 */}],
});

// 全局状态(可选)
const actions = initGlobalState({ user: null, token: null });
actions.onGlobalStateChange((state, prev) => {
  console.log('global state change:', state, prev);
});

start({
  prefetch: 'all',          // 预加载策略:'all' | true | false | [appName]
  sandbox: { strictStyleIsolation: false },
  singular: false,          // 是否同一时刻只渲染一个微应用
});

3) 子应用通用改造

  • 运行时 publicPath 注入:
// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  /* eslint-disable */
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  • 入口生命周期(通用约定):
    • bootstrap(props):初始化仅需执行一次的逻辑。
    • mount(props):挂载渲染,读取 props.container 与自定义 props
    • unmount(props):解绑事件、卸载实例、清理 DOM。
  • 独立/qiankun 双态运行:在入口末尾检测 window.__POWERED_BY_QIANKUN__,不在 qiankun 环境时直接 render()

子应用示例

A. Vue2 子应用(Webpack)

// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// src/main.js
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);
let instance = null;
let router = null;

function render(props = {}) {
  const { container } = props;
  router = new VueRouter({ mode: 'hash', routes: [] });
  instance = new Vue({ router, render: (h) => h(App) })
    .$mount(container ? container.querySelector('#app') : '#app');
}

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

export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() {
  instance && instance.$destroy();
  instance && (instance.$el.innerHTML = '');
  instance = null;
  router = null;
}
// webpack.config.js 关键配置
module.exports = {
  output: {
    library: 'app-vue2-[name]',
    libraryTarget: 'umd',
    publicPath: 'auto',
  },
  devServer: {
    port: 8081,
    headers: { 'Access-Control-Allow-Origin': '*' },
  },
};

B. Vue3 子应用(Vite)

// src/public-path.js(仍可沿用同样写法)
if ((window as any).__POWERED_BY_QIANKUN__) {
  // @ts-ignore
  __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// src/main.ts
import './public-path';
import { createApp } from 'vue';
import App from './App.vue';
import { createRouter, createWebHashHistory } from 'vue-router';

let app: any = null;
let router: any = null;

function render(props: any = {}) {
  const { container } = props;
  router = createRouter({ history: createWebHashHistory(), routes: [] });
  app = createApp(App);
  app.use(router);
  app.mount(container ? container.querySelector('#app')! : '#app');
}

if (!(window as any).__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {}
export async function mount(props: any) { render(props); }
export async function unmount() {
  app?.unmount();
  app = null;
  router = null;
}
// vite.config.ts(推荐 vite-plugin-qiankun)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';

export default defineConfig({
  plugins: [vue(), qiankun('app-vue3', { useDevMode: true })],
  server: { port: 8082, cors: true },
  base: '/',
});

C. React 18 子应用(Webpack 或 Vite)

// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// src/main.jsx
import './public-path';
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

let root = null;

function render(props = {}) {
  const { container } = props;
  const dom = container ? container.querySelector('#app') : document.getElementById('app');
  if (!dom) return;
  root = createRoot(dom);
  root.render(<App />);
}

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

export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() { root?.unmount(); root = null; }
// webpack.config.js 关键配置
module.exports = {
  output: {
    library: 'app-react-[name]',
    libraryTarget: 'umd',
    publicPath: 'auto',
  },
  devServer: { port: 8083, headers: { 'Access-Control-Allow-Origin': '*' } },
};
// vite.config.ts(可选)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import qiankun from 'vite-plugin-qiankun';

export default defineConfig({
  plugins: [react(), qiankun('app-react', { useDevMode: true })],
  server: { port: 8083, cors: true },
  base: '/',
});

D. jQuery / 原生 子应用(UMD)

// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// src/main.js
import './public-path';
import $ from 'jquery';

let containerEl = null;

function render(props = {}) {
  const { container } = props;
  containerEl = container ? container.querySelector('#app') : document.getElementById('app');
  if (!containerEl) return;

  $(containerEl).html(
    '<div class="jq-app">\n' +
    '  <h3>jQuery Micro App</h3>\n' +
    '  <button id="jq-btn">Click</button>\n' +
    '</div>'
  );
  $(containerEl).on('click', '#jq-btn', () => alert('Hello from jQuery app'));
}

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

export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() {
  if (containerEl) {
    $(containerEl).off('click', '#jq-btn');
    $(containerEl).empty();
    containerEl = null;
  }
}
// webpack.config.js 关键配置
module.exports = {
  output: {
    library: 'app-jquery-[name]',
    libraryTarget: 'umd',
    publicPath: 'auto',
  },
  devServer: { port: 8084, headers: { 'Access-Control-Allow-Origin': '*' } },
};

路由与激活规则

  • 推荐 Hash 前缀:简单可靠,基座 activeRule('#/app-react') 与子应用内部使用 Hash 路由一致。
  • History 模式:需要服务端 rewrite,否则刷新 404。
location /app-react/ {
  root   /your/dist/root;
  try_files $uri $uri/ /app-react/index.html;
}
  • activeRule 编写:支持字符串或数组;可根据 location.pathnamelocation.hash 组合匹配。

跨应用通信

  • props 传参(简单直观):基座在 registerMicroAppsprops 字段传入;子应用在 bootstrap/mount 中读取。
  • 全局状态(推荐)initGlobalState 返回的 actions 可在基座与所有子应用间同步状态。
// 基座
const actions = initGlobalState({ user: null });
actions.setGlobalState({ user: { id: 1 } });

// 子应用
export async function mount(props) {
  props.onGlobalStateChange((state) => console.log(state), true);
  props.setGlobalState && props.setGlobalState({ user: { id: 2 } });
}
  • 其他:事件总线、URL 查询参数、LocalStorage(需注意隔离与一致性)。

构建与部署要点清单

  • 子应用打包必须为 UMD,且入口导出生命周期。
  • 生产环境必须启用 运行时 publicPath 注入,避免静态资源 404。
  • 开发环境建议为每个子应用配置 独立端口 且允许 CORS
  • 基座 container 与子应用根节点选择器(如 #app)需一致。
  • 多子应用并行时,合理设置 singularprefetch,避免性能瓶颈。

常见问题排查

  • 刷新 404(History 模式):服务端缺少 rewrite;改用 Hash 或配置 Nginx try_files
  • 样式污染/丢失:开启 sandbox.strictStyleIsolation 或使用 CSS Modules/BEM;确保子应用 unmount 清理干净。
  • 静态资源路径错误:缺少 public-path.js 注入或构建 publicPath 写死。
  • 白屏:容器选择器不匹配;入口未导出生命周期;网络错误导致 entry 加载失败。
  • 内存泄漏unmount 未解绑事件/未销毁实例。

进阶能力

  • 手动加载微应用
import { loadMicroApp } from 'qiankun';
const app = loadMicroApp({ name: 'x', entry: '//localhost:9000', container: '#subapp-root' });
// app.unmount() 可在任意时机卸载
  • 保活与 KeepAlive:自行在基座维护缓存容器(注意与 qiankun 沙箱冲突)。
  • HTML Entry 子应用:直接使用完整 HTML 作为 entry(默认行为),无需额外配置。

实施 Checklist(落地自检)


基座向子应用传递数据(详解)

方案一:通过 props 直传(简单直观)

  • 基座在注册时通过 props 字段传入任意可序列化对象或函数引用(同上下文,非跨进程,函数可直接传递)。
// 基座
import { registerMicroApps } from 'qiankun';

const api = {
  fetchUser: async (id) => ({ id, name: 'Alice' }),
  openModal: (payload) => console.log('open modal', payload),
};

registerMicroApps([
  {
    name: 'app-react',
    entry: '//localhost:8083',
    container: '#subapp-root',
    activeRule: '#/app-react',
    props: {
      user: { id: 1, name: 'admin' },
      permissions: ['read', 'write'],
      api,
      featureFlags: { newUI: true },
    },
  },
]);
// 子应用(以 React 为例)
export async function mount(props) {
  const { user, permissions, api, featureFlags } = props;
  console.log('from base:', user, permissions, featureFlags);
  api.openModal({ title: 'Hello' });
}
  • 要点:
    • props 仅在 mount 初次传入,之后若需“实时更新”,更推荐使用全局状态;或使用 loadMicroApp + update 机制(见方案三)。
    • 大对象请仅传递标识(如 id),数据在子应用自行拉取,以降低耦合与传输体积。

方案二:全局状态(推荐,支持订阅与推送)

  • 基座初始化全局状态后,子应用可通过 props.onGlobalStateChange 订阅、props.setGlobalState 推送。
// 基座
import { initGlobalState, registerMicroApps, start } from 'qiankun';

const actions = initGlobalState({ user: { id: 1, name: 'admin' }, theme: 'light' });
actions.onGlobalStateChange((state, prev) => {
  console.log('base observe:', state, prev);
});

registerMicroApps([
  { name: 'app-vue2', entry: '//localhost:8081', container: '#subapp-root', activeRule: '#/app-vue2' },
]);

start();

// 在任意时机更新:
actions.setGlobalState({ theme: 'dark' });
// 子应用(任意技术栈通用)
export async function mount(props) {
  // 订阅(fireImmediately=true 首次立即触发一次)
  props.onGlobalStateChange && props.onGlobalStateChange((state, prev) => {
    console.log('sub observe:', state, prev);
    // TODO: 根据 state 更新自身 UI/状态
  }, true);

  // 推送
  props.setGlobalState && props.setGlobalState({ theme: 'dark' });
}
  • 要点:
    • 不需要手动在 props 中传入 actions,初始化后 qiankun 会把 onGlobalStateChangesetGlobalStateoffGlobalStateChange 注入到每个子应用的 props
    • 状态必须为可序列化对象;更新是浅合并(保留未覆盖的字段)。

方案三:动态更新 props(loadMicroApp + update)

  • 如需在运行中多次下发不同 props,可使用手动加载 API 并在子应用导出 update 生命周期。
// 基座(手动加载)
import { loadMicroApp } from 'qiankun';

const app = loadMicroApp({
  name: 'app-react',
  entry: '//localhost:8083',
  container: '#subapp-root',
  props: { filters: { keyword: '' } },
});

// 运行时更新 props
app.update({ filters: { keyword: 'hello' } });
// 子应用(增加 update 生命周期)
export async function update(props) {
  // 从 props 中读取最新数据并应用到 UI
  console.log('updated props', props);
}
  • 要点:
    • registerMicroApps 场景下没有直接的 props 更新 API;若需要频繁更新,请优先用“全局状态”,或切换到 loadMicroApp

类型定义(可选,TS)

// 子应用可约束接收的 props 类型
export interface MicroAppProps {
  container?: HTMLElement;
  name?: string;
  onGlobalStateChange?: (cb: (state: any, prev: any) => void, fireImmediately?: boolean) => void;
  setGlobalState?: (state: Record<string, any>) => void;
  offGlobalStateChange?: () => void;
  // 自定义字段
  user?: { id: number; name: string } | null;
  permissions?: string[];
  featureFlags?: Record<string, boolean>;
  api?: { fetchUser?: (id: number) => Promise<any>; openModal?: (payload: any) => void };
}

最佳实践

  • 使用全局状态广播通用上下文(如 usertheme、语言等),props 仅承载初始化必要参数和函数引用。
  • 避免下发大型对象;以 ID + 子应用内聚的数据获取为主。
  • 明确卸载时机:在 unmount 内清理订阅(offGlobalStateChange)、事件监听与定时器,避免内存泄漏。
  • 如需强约束,使用 TypeScript 定义 MicroAppProps 并在各子应用落地校验。

本地开发跨域(Webpack DevServer)

qiankun 在开发环境通常通过不同端口加载子应用 HTML Entry(跨源)。需为每个子应用的 Webpack DevServer 开启 CORS,或者通过“基座代理”将子应用映射到同源路径以规避 CORS。

子应用开启 CORS(最简单)

// 子应用 webpack.config.js(或 vue-cli 的 vue.config.js -> devServer)
module.exports = {
  // ...
  devServer: {
    port: 8083, // 每个子应用使用独立端口
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
    },
    allowedHosts: 'all', // webpack-dev-server v4
    hot: true,
  },
  output: {
    publicPath: 'auto', // 搭配运行时 publicPath 注入
    library: 'app-react-[name]',
    libraryTarget: 'umd',
  },
};
  • 适用于不携带 Cookie/凭证的加载场景(纯资源加载)。
  • 基座的 entry 可直接指向 //localhost:8083

携带凭证(Cookie/Authorization)场景

若子应用资源或接口需要 Cookie,会触发“凭证跨域”规则:

  • 响应头必须为精确的 Access-Control-Allow-Origin: http://localhost:8080(不能使用 *)。
  • 且需包含 Access-Control-Allow-Credentials: true
  • 前端请求需开启凭证(如 axios withCredentials: true)。
// 子应用 webpack.config.js
const BASE_ORIGIN = 'http://localhost:8080'; // 基座地址
module.exports = {
  devServer: {
    port: 8083,
    headers: {
      'Access-Control-Allow-Origin': BASE_ORIGIN,
      'Access-Control-Allow-Credentials': 'true',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
    },
  },
};
// axios 示例(基座或子应用内都需与后端约定好)
import axios from 'axios';
axios.defaults.withCredentials = true;

注意:若仅是“加载子应用入口 HTML 与脚本”,大多数场景无需凭证;仅当你的静态资源服务依赖 Cookie 鉴权时才需此配置。

通过基座代理规避跨域(同源映射,推荐)

将子应用开发服务代理到基座同源路径(如 http://localhost:8080/app-react/),此时无需子应用开放 CORS:

// 基座(Vue CLI)vue.config.js -> devServer.proxy
module.exports = {
  devServer: {
    port: 8080,
    proxy: {
      '/app-react': {
        target: 'http://localhost:8083',
        changeOrigin: true,
        pathRewrite: { '^/app-react': '/' },
        // ws: false // 如需
      },
      '/app-vue2': {
        target: 'http://localhost:8081',
        changeOrigin: true,
        pathRewrite: { '^/app-vue2': '/' },
      },
    },
  },
};
// 基座注册时将 entry 改为同源路径
registerMicroApps([
  {
    name: 'app-react',
    entry: '/app-react/', // 由代理转发到 8083
    container: '#subapp-root',
    activeRule: '#/app-react',
  },
]);
  • 优点:彻底避免跨域与 CORS 复杂度;模拟生产同路径部署结构。
  • 缺点:需要配置多个代理规则;若子应用也访问其后端接口,可能还需额外代理。

其他注意点

  • 协议一致:基座与子应用尽量同为 http 或同为 https,避免混合内容被浏览器拦截。
  • Host 校验:webpack-dev-server v4 默认限制外部访问,开发时可设置 allowedHosts: 'all'
  • CSP 与跨源脚本:如启用严格 CSP,请确保允许加载相应源的脚本与样式。
  • Script crossorigin:通常不需要手动设置;若有 SourceMap 收集等需求,可设置 output.crossOriginLoading = 'anonymous'

常见错误与排查

  • 报错 “No 'Access-Control-Allow-Origin' header is present”:
    • 子应用未设置 devServer.headers;或使用了凭证但仍设置了 *
  • 报错 “The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'”:
    • 开启了 Cookie/凭证但响应头仍为 *;改为精确 Origin 并添加 Access-Control-Allow-Credentials: true
  • net::ERR_FAILED/加载入口 HTML 失败:
    • 端口未启动、端口冲突、被系统代理/防火墙拦截;或 entry 地址错误。

部署与 Nginx 配置

微前端生产部署的关键点:

  • 子应用必须以 UMD 产物对外提供入口(HTML Entry 方式加载),并保证运行时 publicPath 正确;
  • 根据路由模式配置回退(Hash 模式无需回退,History 模式需 try_files 到 index.html);
  • 统一同源(同域 + 子路径)最省心,也可采用子域名跨域 + CORS;
  • 区分 HTML 与静态资源的缓存策略,避免发布后页面不更新。

方案一:同域 + 子路径(推荐)

  • 目录结构(示例):
    • 基座:/var/www/base/(包含 index.html 与静态资源)
    • Vue2 子应用:/var/www/app-vue2/
    • Vue3 子应用:/var/www/app-vue3/
    • React 子应用:/var/www/app-react/
    • jQuery 子应用:/var/www/app-jquery/
  • 基座在 registerMicroApps 中的 entry 指向同域路径:/app-vue2//app-react/ 等。
server {
  listen 80;
  server_name example.com;

  # 基座(History 模式)
  root /var/www/base;
  index index.html;

  # 静态资源强缓存
  location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
    expires 7d;
    add_header Cache-Control "public, max-age=604800, immutable";
  }

  # HTML 不缓存(便于发布后立即生效)
  location = /index.html {
    add_header Cache-Control "no-cache";
  }

  # 基座 History 路由回退
  location / {
    try_files $uri $uri/ /index.html;
  }

  # 子应用:Vue2(Hash 或 History 均可,History 需要回退)
  location /app-vue2/ {
    alias /var/www/app-vue2/;
    index index.html;
    try_files $uri $uri/ /app-vue2/index.html;
  }

  # 子应用:Vue3
  location /app-vue3/ {
    alias /var/www/app-vue3/;
    index index.html;
    try_files $uri $uri/ /app-vue3/index.html;
  }

  # 子应用:React
  location /app-react/ {
    alias /var/www/app-react/;
    index index.html;
    try_files $uri $uri/ /app-react/index.html;
  }

  # 子应用:jQuery / 原生
  location /app-jquery/ {
    alias /var/www/app-jquery/;
    index index.html;
    try_files $uri $uri/ /app-jquery/index.html;
  }

  # 可选:开启 gzip 压缩
  gzip on;
  gzip_comp_level 5;
  gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/json image/svg+xml;

  # 可选:基础安全头
  add_header X-Content-Type-Options nosniff;
  add_header X-Frame-Options SAMEORIGIN;
}

注意:使用 alias 映射子路径到对应目录;若使用 root,需确保路径拼接正确。

方案二:子域名(跨域加载)

  • 基座:base.example.com,子应用:vue2.example.comreact.example.com 等;
  • 基座的 entry 使用完整 URL:https://vue2.example.com/
  • 如需跨域携带 Cookie(含鉴权资源),需在子应用服务器添加 CORS 头:
# 子应用服务器(仅当需要跨域凭证时)
server {
  listen 443 ssl;
  server_name vue2.example.com;
  root /var/www/app-vue2;
  index index.html;

  # 允许基座域名跨域访问(凭证场景不要使用 *)
  add_header Access-Control-Allow-Origin https://base.example.com always;
  add_header Access-Control-Allow-Credentials true always;
  add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
  add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;

  if ($request_method = OPTIONS) {
    return 204;
  }

  location / {
    try_files $uri $uri/ /index.html;
  }
}

一般只需开放静态资源跨域;接口跨域由后端网关统一处理更稳妥。

Hash vs History 模式部署要点

  • Hash 模式:静态托管即可,不需要 try_files 回退;
  • History 模式:所有非文件请求回退到各自 index.html,否则刷新会 404;
  • 基座与子应用可以混用不同模式,但建议统一以减少心智负担。

资源路径与 publicPath

  • 子应用务必使用“运行时 publicPath 注入”(window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__),避免构建时写死路径;
  • 若部署到子路径(如 /app-react/),构建工具(Vite 的 base 或 Webpack 的 publicPath)需与运行时注入协同(建议保持 auto + 运行时注入)。

缓存与版本

  • *.js/*.css 等静态资源开启长期缓存(带 hash 文件名),对 HTML 禁用缓存;
  • 发布策略:先上传静态资源,再替换 index.html,避免短时间 404;
  • 可结合 Cache-Control: immutable 与版本号管理,确保灰度与回滚安全。

HTTPS 与 HTTP/2

  • 建议启用 HTTPS 与 HTTP/2,提升首屏与并发加载性能:
server {
  listen 443 ssl http2;
  server_name example.com;
  # ssl_certificate /path/fullchain.pem;
  # ssl_certificate_key /path/privkey.pem;
  # 其余与 80 端口配置一致
}

反向代理到应用服务器(可选)

  • 若基座/子应用由 Node/Nest/Express 提供静态与 SSR,可用反向代理:
location /app-react/ {
  proxy_pass http://127.0.0.1:7003/; # 由后端应用处理 SPA 回退
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

部署检查清单

posted @ 2025-08-15 16:57  Math点PI  阅读(249)  评论(0)    收藏  举报