MST
星途 面试题库

面试题:Next.js 动态路由与微前端架构结合的复杂应用案例

设想你在一个大型企业级项目中,采用微前端架构,不同的前端团队负责不同的业务模块。现在有一个需求,在主应用中通过 Next.js 的动态路由实现一个通用的内容展示页面,该页面可能展示来自不同微前端模块的特定内容,例如 /content/[moduleName]/[contentId]。这里的 [moduleName] 决定了内容来自哪个微前端模块,[contentId] 用于获取具体内容。请详细说明如何设计和实现这个功能,包括如何处理微前端之间的通信、动态加载模块以及确保整体的性能和可维护性。
46.1万 热度难度
前端开发Next.js

知识考点

AI 面试

面试题答案

一键面试

1. 路由设计

在 Next.js 主应用中,利用动态路由实现 /content/[moduleName]/[contentId] 这种形式的路由。

// pages/content/[moduleName]/[contentId].js
import React from'react';

const ContentPage = ({ moduleName, contentId }) => {
  return (
    <div>
      <h1>展示来自 {moduleName} 模块的 {contentId} 内容</h1>
    </div>
  );
};

export async function getStaticPaths() {
  // 这里可以从 API 或者其他数据源获取所有可能的 moduleName 和 contentId 组合
  const paths = [];
  // 示例数据获取
  const modules = await fetch('/api/modules').then(res => res.json());
  for (const module of modules) {
    const contents = await fetch(`/api/modules/${module.name}/contents`).then(res => res.json());
    for (const content of contents) {
      paths.push({ params: { moduleName: module.name, contentId: content.id.toString() } });
    }
  }
  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const { moduleName, contentId } = params;
  // 这里可以根据 moduleName 和 contentId 从 API 获取具体内容数据
  const contentData = await fetch(`/api/modules/${moduleName}/contents/${contentId}`).then(res => res.json());
  return { props: { moduleName, contentId, contentData }, revalidate: 60 };
}

export default ContentPage;

2. 微前端通信处理

  • 事件总线:可以使用类似于 mitt 这样的轻量级事件总线库。在主应用和各个微前端模块初始化时,都引入事件总线实例。例如,主应用向微前端模块请求内容时,可以发送一个事件,携带 moduleNamecontentId 信息。
import mitt from'mitt';

const emitter = mitt();

// 主应用发送事件
emitter.emit('request - content', { moduleName, contentId });

// 微前端模块监听事件
emitter.on('request - content', ({ moduleName, contentId }) => {
  // 根据 moduleName 判断是否是自己模块的内容请求
  if (currentModuleName === moduleName) {
    // 获取并处理内容
    const content = getContentById(contentId);
    // 可以再通过事件总线将内容返回给主应用
    emitter.emit('content - response', { moduleName, contentId, content });
  }
});
  • 共享状态管理:使用 ReduxMobX 等状态管理库。主应用和微前端模块共享一个状态仓库,主应用将请求的 moduleNamecontentId 存入状态中,微前端模块监听状态变化,当检测到相关请求且是自己模块的内容时,获取并更新状态中的内容数据。

3. 动态加载模块

  • Webpack 的动态导入:在 Next.js 项目中,可以使用 import() 语法动态导入微前端模块。
const loadModule = async (moduleName) => {
  try {
    // 假设微前端模块打包后有一个统一的引入路径
    const module = await import(`/micro - frontends/${moduleName}`);
    return module;
  } catch (error) {
    console.error(`加载 ${moduleName} 模块失败`, error);
    return null;
  }
};
  • Next.js 的 next/dynamic:结合 next/dynamic 实现按需加载微前端模块,并处理加载状态。
import dynamic from 'next/dynamic';

const DynamicModule = dynamic(() => loadModule(moduleName), {
  loading: () => <div>加载中...</div>,
  ssr: false
});

4. 性能和可维护性

  • 代码拆分:确保每个微前端模块都进行了合理的代码拆分,只加载当前需要的代码,减少初始加载体积。例如,在微前端模块内部,使用 WebpacksplitChunks 配置,将公共代码提取出来。
  • 缓存策略:对于获取的内容数据以及加载的微前端模块,实现缓存机制。比如在客户端使用 localStoragesessionStorage 缓存数据,在服务器端利用 Redis 等缓存中间件,减少重复请求。
  • 模块化和组件化:在主应用和微前端模块中,都遵循良好的模块化和组件化原则。每个微前端模块负责自己独立的业务逻辑,接口清晰,易于维护和替换。主应用只负责路由和整体协调,不耦合过多具体业务代码。
  • 错误处理:在动态加载模块、获取内容数据以及微前端通信过程中,都要进行全面的错误处理。例如,在加载微前端模块失败时,给出友好的提示信息,在获取内容数据失败时,重试或显示缓存数据。