逐步淘汰 Legacy Angular:我们如何迁移到 Next.js、GraphQL 和 Monorepo(无需一次性大改)
Source: Dev.to

概览
如果你曾在一个长期维护的前端项目工作过,你一定了解其中的情况。应用不断壮大,功能堆积,截止日期接踵而至,突然之间你就坐拥一座技术债务的山。
这正是我们当时的处境。
我们拥有一个大型的 Angular 14 应用,600 多个组件,采用单体结构,复杂度日益提升,导致开发速度变慢。完整重写听起来很诱人,但也充满风险、成本高昂,并且会对业务造成干扰。
于是,我们没有选择一次性大改,而是设计了一套迁移策略,让我们能够 逐步替换遗留代码,使用的技术包括:
- Next.js
- GraphQL 联邦
- Monorepo 架构
- 作为框架桥梁的 Web 组件
本文将介绍该架构、迁移方法以及我们迄今为止学到的经验。
The Legacy Landscape: What We Started With
我们的 Angular 应用多年来一直是业务的支柱。它负责:
- 客户和用户注册工作流
- 服务提供商搜索
- 支付处理
- 基于角色的用户管理
- 复杂的多步骤表单
- AWS Cognito 身份验证
它运行良好,提供了价值。但它已经开始显露出老化的迹象。
传统系统的关键挑战
-
单体架构 – 一个根模块拥有 637 个声明的组件,导致代码库难以理解。
-
手动依赖注入 – 自定义 HTTP 服务在 60 多处 手动实例化,绕过了 Angular 的 DI。
-
紧耦合 – 组件直接绑定到特定的 API 结构。
-
可重用性受限 – UI 组件仅限于 Angular,无法在其他地方复用。
-
构建缓慢 – 随着应用规模增长,构建时间不断增加。
-
技术债务
- Angular 14 - Bootstrap 4 - jQuery dependencies
该应用在多个环境(dev、test、uat、prod)中有机地增长。完整重写可能需要 12–18 个月,并带来严重的业务风险。因此我们需要一种更安全的方案。
如何将各部分组合在一起
- Angular 继续运行旧版 UI。
- 新功能使用 React 构建。
- React 应用以 Web 组件的形式发布。
- GraphQL 位于前端和后端之间。
- Next.js 负责身份验证。
这使我们能够一次替换一个功能,而不会影响业务。
我们的迁移策略:绞杀树模式
我们采用了 Strangler Fig 模式,在旧系统持续运行的同时,逐步替换其部分功能。
我们的做法基于三大核心支柱:
- Monorepo 基础
- 基于 GraphQL 的 API
- 作为桥梁的 Web 组件
使用 Turborepo 的 Monorepo
我们使用 pnpm 和 Turborepo 构建了一个 monorepo。
monorepo/
├── apps/
│ ├── auth-service/
│ ├── graphs/
│ ├── services/
│ └── notification-service/
├── packages/
│ ├── design-system/
│ ├── authentication/
│ ├── logger/
│ ├── database/
│ └── web-components/
好处
- 跨应用共享代码
- 端到端 TypeScript
- 更快的构建(约提升 70%)
- 原子化跨栈 PR
- 协调的版本管理
GraphQL 作为 API 层
我们没有使用单体 REST API,而是创建了基于领域的 GraphQL 服务。
type Business {
id: ID!
name: String!
subscriptionPlans: [Plan!]!
defaultPlanId: Int
}
type Query {
searchBusinesses(country: String!, searchTerm: String!): [Business!]!
}
优势
- 明确的领域划分
- 独立部署
- 强类型
- 高效的客户端驱动查询
- 支持 Federation 的架构
Web 组件:在 Angular 中使用 React
我们使用 Web 组件将 React 功能嵌入到 Angular 应用中。
import { r2wc } from '@r2wc/react-to-web-component';
import { UserRegistrationWithApollo } from './UserRegistration';
const UserRegistrationWC = r2wc(UserRegistrationWithApollo, {
props: {
businessGraphApiUrl: 'string',
providerGraphApiUrl: 'string',
},
});
customElements.define('user-registration', UserRegistrationWC);
为什么有效
- 与框架无关的 UI
- 渐进式迁移
- 现代 React 模式
- 跨应用可复用
Apollo Client:多 Graph 通信
export const createApolloClients = (
businessUri: string,
providerUri: string
) => {
const businessClient = new ApolloClient({
link: authLink.concat(httpLink(businessUri)),
cache: new InMemoryCache(),
});
const providerClient = new ApolloClient({
link: authLink.concat(httpLink(providerUri)),
cache: new InMemoryCache(),
});
return { businessClient, providerClient };
};
Next.js 用于认证
export async function POST(request: Request) {
const { refreshToken } = await request.json();
const newTokens = await refreshCognitoToken(refreshToken);
return Response.json({
accessToken: newTokens.accessToken,
idToken: newTokens.idToken,
});
}
Why Next.js
- 用于身份验证的 API 路由
- 支持 Docker 的构建
- 在 Angular 和 React 之间共享
- 为迁移做好未来兼容
数据库层:Prisma + SQL Server
@Injectable()
export class DataService {
constructor(private prisma: PrismaClient) {}
async getBusiness(id: number) {
return this.prisma.business.findUnique({
where: { id },
include: {
subscriptionPlans: true,
locations: true,
},
});
}
}
迁移工作流
步骤 1:在 React 中构建
export const Feature = () => {
const { data, loading } = useQuery(GET_DATA_QUERY);
if (loading) return null;
return (
<div>
<h2>{data.title}</h2>
{/* Feature implementation */}
</div>
);
};
步骤 2:包装为 Web 组件
const FeatureWC = r2wc(FeatureWithApollo, {
props: {
apiUrl: 'string',
userId: 'string',
},
});
customElements.define('app-feature', FeatureWC);
步骤 3:在 Angular 中使用
import '@company/wc-feature';
步骤 4:功能标志
(标志的实现已在 Angular 配置中处理;为简洁起见,HTML 占位符已省略。)
步骤 5:删除旧代码
- 删除标志
- 删除 Angular 组件
- 清理服务
- 更新测试
6 个月后的关键指标
- 迁移了 15 个主要功能
- 构建速度提升 70 %
- 重复代码减少 40 %
- 80 % 的新功能使用 React 构建
- 零迁移相关事故
最终思考
让旧版应用退役并不一定意味着冒险的重写。
通过结合:
- A monorepo
- GraphQL
- Web components
- Next.js
- Turborepo
…我们能够逐步现代化,同时保持业务平稳运行。
strangler‑fig 模式真的有效。在彻底重写和永远与遗留系统共存之间,有一条中间道路。
技术栈概览
传统
- Angular 14
- Bootstrap 4 + jQuery
- REST API
- 单体架构
现代
- React 18+,Next.js 15
- NestJS + GraphQL
- Prisma + SQL Server
- Turborepo + pnpm
- 通过
@r2wc实现的 Web 组件 - Tailwind + Radix UI
- TypeScript 全面使用
- GitHub Actions + Docker