Giới thiệu
Trong những năm gần đây, kiến trúc micro‑frontend đã trở thành một xu hướng mạnh mẽ giúp các đội phát triển lớn chia nhỏ ứng dụng front‑end thành các phần độc lập, có thể triển khai và nâng cấp riêng biệt. Khi áp dụng vào môi trường React, Next.js nổi bật nhờ khả năng server‑side rendering (SSR), static site generation (SSG) và hệ sinh thái plugin phong phú. Bài viết này sẽ đi sâu vào cách kết hợp Next.js với Module Federation của Webpack 5 để xây dựng một hệ thống micro‑frontend thực tế, đồng thời đưa ra các chiến lược tối ưu hiệu năng, quản lý phụ thuộc và quy trình CI/CD.
Lý do chọn Next.js cho micro‑frontend
Next.js cung cấp các tính năng sau, khiến nó trở thành nền tảng lý tưởng cho micro‑frontend:
- Routing tự động: Thư mục
pagestạo ra các route mà không cần cấu hình. - Hỗ trợ SSR/SSG: Giúp cải thiện SEO và thời gian tải trang ban đầu.
- API Routes: Cho phép triển khai logic back‑end nhẹ ngay trong cùng dự án.
- Hệ sinh thái plugin: Webpack 5 được tích hợp sẵn, cho phép bật
ModuleFederationPluginmà không cần cấu hình phức tạp.
Những ưu điểm này giúp chúng ta tập trung vào việc tách các phần UI thành các remote (các micro‑frontend) và một host chịu trách nhiệm hợp nhất chúng.
Module Federation trong Webpack 5
Module Federation cho phép một bundle (remote) xuất khẩu các module và một bundle khác (host) nhập khẩu chúng tại thời điểm chạy. Điều này tạo ra khả năng tải động các phần của ứng dụng mà không cần rebuild toàn bộ. Các khái niệm chính:
- Remote: Ứng dụng cung cấp các module qua
exposes. - Host: Ứng dụng tiêu thụ các module qua
remotes. - Shared: Các thư viện chung (React, React‑DOM, …) được chia sẻ để tránh tải lại.
Next.js mặc định sử dụng Webpack 5, vì vậy chúng ta chỉ cần mở next.config.js và chèn ModuleFederationPlugin vào.
Cấu hình Next.js làm host và remote
Thiết lập remote application
Giả sử chúng ta có một micro‑frontend Dashboard sẽ được triển khai độc lập. Đầu tiên tạo dự án Next.js:
npx create-next-app@latest dashboard-remote --typescript cd dashboard-remote
Tiếp theo, cài đặt Webpack 5 (đã có sẵn) và tạo file next.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
webpack(config, { isServer }) {
if (!isServer) {
config.plugins.push(
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js',
exposes: {
'./Header': './components/Header.tsx',
'./Stats': './components/Stats.tsx',
},
shared: {
react: { singleton: true, requiredVersion: require('react/package.json').version },
'react-dom': { singleton: true, requiredVersion: require('react-dom/package.json').version },
},
})
);
}
return config;
},
};
Trong components/Header.tsx và components/Stats.tsx viết các component đơn giản:
import React from 'react';
export const Header: React.FC = () => (
<header style={{ background: '#0070f3', color: '#fff', padding: '1rem' }}>
<h1>Dashboard Remote</h1>
</header>
);
import React from 'react';
export const Stats: React.FC = () => (
<section style={{ padding: '1rem' }}>
<h2>Statistics</h2>
<p>Số liệu mẫu: 12345</p>
</section>
);
Chạy npm run dev để kiểm tra, server sẽ tạo file remoteEntry.js trong thư mục .next/static/chunks. Đảm bảo cấu hình output.publicPath trong next.config.js để file này có thể truy cập công khai (ví dụ /remoteEntry.js).
Thiết lập host application
Tạo một dự án Next.js mới làm host:
npx create-next-app@latest host-app --typescript cd host-app
Thêm cấu hình Module Federation để tiêu thụ remote:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
webpack(config, { isServer }) {
if (!isServer) {
config.plugins.push(
new ModuleFederationPlugin({
name: 'host',
remotes: {
dashboard: 'dashboard@http://localhost:3001/_next/static/chunks/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: require('react/package.json').version },
'react-dom': { singleton: true, requiredVersion: require('react-dom/package.json').version },
},
})
);
}
return config;
},
};
Trong pages/index.tsx import các module từ remote bằng dynamic của Next.js để thực hiện lazy loading:
import dynamic from 'next/dynamic';
import React from 'react';
const RemoteHeader = dynamic(() => import('dashboard/Header'), { ssr: false });
const RemoteStats = dynamic(() => import('dashboard/Stats'), { ssr: false });
const Home: React.FC = () => (
<div>
<RemoteHeader />
<main style={{ margin: '2rem' }}>
<h2>Welcome to Host Application</h2>
<RemoteStats />
</main>
</div>
);
export default Home;
Khởi động remote trên cổng 3001 (có thể thay đổi package.json scripts để chạy next dev -p 3001), sau đó khởi động host trên cổng 3000. Khi truy cập http://localhost:3000, bạn sẽ thấy header và stats được tải từ remote mà không cần build lại host.
Quản lý chia sẻ phụ thuộc (shared modules)
Việc chia sẻ các thư viện như react và react-dom là bắt buộc để tránh việc React được tải hai lần, gây lỗi Invalid hook call. Module Federation cho phép định nghĩa singleton: true và requiredVersion. Ngoài ra, nếu dự án sử dụng UI library (ví dụ antd), chúng ta có thể thêm:
shared: {
antd: { singleton: true, eager: true },
// các thư viện khác
}
Đối với các thư viện lớn, việc bật eager: true giúp chúng được tải ngay trong bundle host, giảm thời gian khởi tạo. Tuy nhiên, cần cân nhắc kích thước bundle.
Chiến lược tải động và tối ưu hiệu năng
Next.js cung cấp dynamic để lazy load các component. Khi kết hợp với Module Federation, chúng ta có thể:
- Prefetch remote entry trong
_app.tsxbằng cách chèn<link rel="prefetch" href="/remoteEntry.js" />để trình duyệt tải trước. - Chunk splitting tự động: mỗi remote component sẽ trở thành một chunk riêng, giảm thời gian tải ban đầu.
- Cache control: cấu hình
Cache‑Controlheaders trên CDN để remote entry được cache lâu, giảm tải mạng.
Dưới đây là ví dụ cấu hình next.config.js để bật assetPrefix khi deploy trên CDN:
module.exports = {
assetPrefix: process.env.CDN_URL || '',
// các cấu hình khác
};
Triển khai CI/CD cho micro‑frontend Next.js
Với kiến trúc micro‑frontend, mỗi remote và host có thể được triển khai độc lập. Dưới đây là một pipeline GitHub Actions mẫu cho remote:
name: Deploy Remote Dashboard
on:
push:
branches: [main]
paths:
- 'dashboard-remote/**'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
working-directory: dashboard-remote
- name: Build
run: npm run build
working-directory: dashboard-remote
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID_DASHBOARD }}
working-directory: dashboard-remote
Host application có pipeline tương tự, chỉ thay đổi working-directory và project-id. Khi một remote được cập nhật, host sẽ tự động nhận phiên bản mới thông qua remoteEntry.js được cache‑busting (sử dụng query string ?v=timestamp).
Kiểm thử tích hợp
Đối với micro‑frontend, việc kiểm thử tích hợp giữa host và remote là quan trọng. Cypress cung cấp khả năng chạy test trên môi trường thực tế. Một test mẫu:
describe('Micro‑frontend integration', () => {
it('should render remote Header and Stats', () => {
cy.visit('http://localhost:3000');
cy.contains('Dashboard Remote').should('be.visible');
cy.contains('Statistics').should('be.visible');
});
});
Chạy Cypress trong CI bằng npm run cypress:run và báo cáo kết quả trong GitHub Actions.
Kết luận
Việc kết hợp Next.js với Module Federation mang lại một giải pháp mạnh mẽ cho kiến trúc micro‑frontend, giúp các team độc lập phát triển, triển khai và mở rộng các phần UI mà không gây xung đột phiên bản. Bằng cách quản lý chia sẻ thư viện, tối ưu tải động và thiết lập quy trình CI/CD tự động, chúng ta có thể đạt được hiệu năng cao, khả năng mở rộng linh hoạt và giảm thiểu rủi ro khi cập nhật. Nếu muốn nâng cao kỹ năng và đi sâu hơn vào việc xây dựng ứng dụng Next.js thực tế, Tham khảo khóa học "Lập trình Front-End với NextJS + TypeScript" tại đây.






