Trạng thái: Architecture proposal — chưa thay đổi source code.
Cập nhật: 2026-10-15.
Phạm vi: chuyển EventHub hiện tại thành npm Workspaces gồm hai ứng dụng Nuxt độc lập:webvàconsole.Nguồn:
- Source EventHub hiện tại.
monorepo-migration-proposal.md.monorepo-target-architecture.md.2026-10-13-technical-seed-next-monorepo-anatomy.md.
Kiến trúc đích được đề xuất:
apps/web: EventHub public hiện tại, Nuxt 3, ssr: true, không còn route hoặc dependency của Builder.apps/console: Event Builder nội bộ, ssr: false, đích cuối là Nuxt 4 và static output.package.json, nuxt.config.ts, source, test, Docker/hosting contract và artifact riêng.package-lock.json ở root.apps/web tạo Nitro Node artifact tại apps/web/.output/.apps/console chạy nuxi generate, tạo static artifact tại apps/console/.output/public/.EventPageRenderer, model/validation và preview protocol.| Chủ đề | Quyết định đề xuất |
|---|---|
| Monorepo tool | npm Workspaces; chưa cần Nx hoặc Turborepo |
| App structure | apps/web + apps/console |
| Dependency declaration | Mỗi app/package có package.json riêng |
| Lockfile | Một package-lock.json tại root |
| Web | Nuxt 3, SSR, Nitro Node server |
| Console | Tách trên Nuxt 3 trước, sau đó nâng riêng lên Nuxt 4; CSR với ssr: false |
| Console output | Static output bằng nuxt generate |
| Web output | Node artifact bằng nuxt build |
| Shared code | Extract theo nhu cầu thực tế, không tạo một package common tổng hợp ngay từ đầu |
| CI/CD | Hai pipeline release độc lập; shared package change có thể trigger cả hai |
package-lock.json riêng cho từng app mâu thuẫn với mô hình npm Workspaces chuẩn:
npm ci tại root dùng một dependency graph và một lockfile.apps/<app>/package.json.EventHub hiện là một Nuxt application duy nhất:
layers/collab-event và layers/promotion cung cấp public routes và event runtime.layers/event-builder cung cấp route /builder.RUN_TYPE dev, dev2, qa2..output.Cấu trúc này làm hai product có cùng release boundary dù mục đích vận hành khác nhau:
Migration phải đạt các kết quả:
package.json và Nuxt config theo app.ssr: false và static output.event-hub/
├── nuxt.config.ts
├── package.json
├── package-lock.json
├── Dockerfile
├── pages/ layouts/ components/
├── composables/ stores/ services/ apis/ utils/
├── plugins/ middleware/ server/
├── configs/
│ └── runtime/run-type/
├── layers/
│ ├── collab-event/
│ ├── promotion/
│ └── event-builder/
├── public/ markup/ tests/
└── .output/ # một artifact cho toàn ứng dụng
Root nuxt.config.ts đang:
ssr: true.collab-event và promotion.event-builder khi RUN_TYPE thuộc dev, dev2, qa2.Root Nuxt app
├── collab-event layer
├── promotion layer
└── event-builder layer # chỉ dev/dev2/qa2
Vì Builder là một layer của Web, nó hiện thừa hưởng:
app.vue và layout behavior.Các build script hiện tại đều chạy tại root:
npm run build:dev
npm run build:dev2
npm run build:qa
npm run build:qa2
npm run build:sandbox
npm run build:live
Tất cả gọi nuxt build với RUN_TYPE tương ứng và ghi:
.output/
├── public/
├── server/
│ └── index.mjs
└── nitro.json
Dockerfile hiện tại:
20.11.1..output/.node server/index.mjs.Repository không chứa Jenkinsfile, .gitlab-ci.yml, Helm chart hoặc Kubernetes manifest. Pipeline Web thực tế nằm ngoài repository và phải được Infra cung cấp trước khi cutover.
Research hiện tại ghi nhận:
layers/promotion: 79 file import root qua @/.layers/collab-event: 14 file import root qua @/.layers/event-builder: 13 file import root qua @/.@promotion/entities/event-page với mật độ rất cao.@promotion/shared/lib, @promotion/shared/ui.Các dependency Builder cần phân loại:
Event Builder
├── Builder-only
│ ├── canvas
│ ├── inspector
│ ├── workflow
│ ├── persistence UI
│ └── code generation UI
├── Shared event runtime
│ ├── event-page model/validation
│ ├── EventPageRenderer
│ ├── preview messaging
│ ├── stage policy
│ └── event config helpers
└── Platform dependencies
├── useRequest / API client
├── auth
├── runtime config
├── alert/tooltip
└── shared constants/types
RUN_TYPE..well-known assets.event-hub/
├── apps/
│ ├── web/ # @event-hub/web
│ │ ├── package.json
│ │ ├── nuxt.config.ts # Nuxt 3, ssr:true
│ │ ├── app.vue
│ │ ├── error.vue
│ │ ├── configs/
│ │ ├── pages/
│ │ ├── layouts/
│ │ ├── components/
│ │ ├── composables/
│ │ ├── stores/
│ │ ├── services/
│ │ ├── apis/
│ │ ├── plugins/
│ │ ├── middleware/
│ │ ├── server/
│ │ ├── public/
│ │ ├── markup/
│ │ ├── layers/
│ │ │ ├── promotion/ # Web-only local layer
│ │ │ └── collab-event/ # Web-only local layer
│ │ ├── tests/
│ │ └── Dockerfile # Node/Nitro image
│ │
│ └── console/ # @event-hub/console
│ ├── package.json
│ ├── nuxt.config.ts # ssr:false
│ ├── app.vue
│ ├── configs/
│ ├── pages/
│ │ ├── index.vue
│ │ ├── builder/index.vue
│ │ └── preview/[slug].vue
│ ├── layouts/
│ ├── plugins/
│ ├── middleware/
│ ├── layers/
│ │ └── event-builder/ # Console-only local layer
│ ├── public/
│ ├── tests/
│ ├── deploy/
│ │ └── nginx.conf
│ └── Dockerfile # static image, nếu deploy bằng container
│
├── packages/
│ ├── nuxt-layers/
│ │ └── event-runtime/ # chỉ code thực sự dùng bởi cả hai app
│ │ ├── package.json
│ │ ├── nuxt.config.ts
│ │ ├── entities/event-page/
│ │ ├── widgets/event-renderer/
│ │ ├── shared/lib/
│ │ ├── shared/ui/
│ │ └── config/events/
│ ├── event-contract/ # optional: TS thuần sau khi boundary ổn định
│ │ ├── package.json
│ │ └── src/
│ └── configs/ # optional: eslint/tsconfig/tailwind khi có lợi ích rõ
│
├── scripts/
│ └── ci/
├── docs/
├── patches/
├── package.json # workspace orchestration
├── package-lock.json # lockfile duy nhất
├── .npmrc
├── .nvmrc
└── .gitlab-ci.yml # nếu Console dùng GitLab CI
apps/webSở hữu:
promotion và collab-event..well-known, Sentry server.Không được:
apps/console.event-builder./builder.apps/consoleSở hữu:
Không được:
apps/web.packages/nuxt-layers/event-runtimeChỉ chứa code có ít nhất hai consumer:
EventPageRenderer.Không dùng @/ hoặc ~/, vì các alias này trỏ về app consumer. Import nội bộ phải dùng package name hoặc alias riêng của layer.
packages/event-contractChỉ tạo khi model thuần đã tách khỏi Nuxt/Vue:
Không chứa component, composable, useRuntimeConfig hoặc Nuxt auto-import.
Quy tắc:
web và console không import lẫn nhau.apps/web/layers.package.json của đúng consumer; không dựa vào npm hoisting.Đích cuối:
Thứ tự migration được khuyến nghị:
Lý do không nâng Console đồng thời với lần move Builder:
@/, auto-import ngầm và Nuxt 3-only behavior.Nếu team quyết định tạo Console trực tiếp bằng Nuxt 4, phải có một compatibility spike trước khi migrate Builder và không được tính spike là bằng chứng production readiness.
Root workspace:
{
“name”: “event-hub-monorepo”,
“private”: true,
“workspaces”: [
“apps/*“,
“packages/*“,
“packages/nuxt-layers/*“,
“packages/configs/*”
]
}
Quy tắc:
package-lock.json ở root.npm ci chỉ chạy từ root.package.json.overrides để ép Nuxt 3 và Nuxt 4 thành một version.patch-package của Nuxt 3 cần một CI assertion riêng vì npm có thể hoist/nest khác sau khi thêm Nuxt 4.Root package.json chỉ orchestration:
{
“scripts”: {
“dev:web”: “npm run serve --workspace=@event-hub/web”,
“dev:console”: “npm run serve --workspace=@event-hub/console”,
“build:web:dev”: “npm run build:dev --workspace=@event-hub/web”,
“build:web:qa”: “npm run build:qa --workspace=@event-hub/web”,
“build:web:sandbox”: “npm run build:sandbox --workspace=@event-hub/web”,
“build:web:live”: “npm run build:live --workspace=@event-hub/web”,
“build:console:dev”: “npm run build:dev --workspace=@event-hub/console”,
“build:console:qa”: “npm run build:qa --workspace=@event-hub/console”,
“build:console:live”: “npm run build:live --workspace=@event-hub/console”,
“build:all:dev”: “npm run build:web:dev && npm run build:console:dev”,
“lint”: “npm run lint --workspaces --if-present”,
“typecheck”: “npm run typecheck --workspaces --if-present”,
“test”: “npm run test --workspaces --if-present”
}
}
build:all:* chỉ dùng cho local validation hoặc quality gate. Release pipeline phải gọi build của từng app riêng.
apps/web/package.json giữ script hiện tại:
{
“name”: “@event-hub/web”,
“private”: true,
“scripts”: {
“serve”: “cross-env RUN_TYPE=dev nuxt dev”,
“build:dev”: “cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=dev SENTRY=off nuxt build”,
“build:qa”: “cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=qa SENTRY=off nuxt build”,
“build:sandbox”: “cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=sandbox SENTRY=on nuxt build”,
“build:live”: “cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=live SENTRY=on nuxt build”
}
}
Build:
npm ci
npm run build:web:live
Artifact:
apps/web/.output/
├── nitro.json
├── public/
└── server/
└── index.mjs
Runtime:
node apps/web/.output/server/index.mjs
apps/console/package.json:
{
“name”: “@event-hub/console”,
“private”: true,
“scripts”: {
“serve”: “cross-env RUN_TYPE=dev nuxt dev”,
“build:dev”: “cross-env RUN_TYPE=dev nuxi generate”,
“build:qa”: “cross-env RUN_TYPE=qa nuxi generate”,
“build:live”: “cross-env RUN_TYPE=live nuxi generate”
}
}
apps/console/nuxt.config.ts:
export default defineNuxtConfig({
ssr: false,
extends: [‘./layers/event-builder’, ‘@event-hub/event-runtime’],
app: {
head: {
meta: [{ name: ‘robots’, content: ‘noindex, nofollow’ }]
}
}
});
Build:
npm ci
npm run build:console:dev
Artifact:
apps/console/.output/public/
├── index.html
├── 200.html
├── 404.html
├── _nuxt/
└── assets copied from public/
Console không có Node/Nitro runtime sau deploy. Static server phải có SPA fallback:
location / {
try_files $uri $uri/ /200.html;
}
Mỗi artifact phải có:
Trong phase đầu, có thể giữ nguyên Jenkins command hiện tại:
npm run build:live
Root script proxy sang Web rồi copy artifact về root:
{
“scripts”: {
“build:live”: “npm run build:live --workspace=@event-hub/web && node scripts/ci/sync-web-output.mjs”
}
}
sync-web-output.mjs phải copy bảo toàn symlink:
apps/web/.output/ → ./.output/
Sau một release cycle ổn định, Jenkins chuyển sang native:
npm ci
npm run build:live --workspace=@event-hub/web
docker build -f apps/web/Dockerfile apps/web
Khi đó bỏ root .output, compatibility script và root Dockerfile.
Mục tiêu: có bằng chứng trước khi move source.
Actions:
dev, qa, sandbox, live.nitro.json, bundle/chunk list và kích thước artifact.scripts/ đang bị .gitignore.Exit criteria:
Mục tiêu: đổi repository layout nhưng chưa migrate Builder behavior.
Actions:
apps/web/package.json.apps/web.promotion và collab-event vào apps/web/layers.apps/console tối thiểu, chưa có Builder.package-lock.json.Exit criteria:
npm ci tại root thành công.Rollback:
Mục tiêu: chứng minh hai app có build boundary thật.
Actions:
RUN_TYPE.Exit criteria:
.output của app này không ảnh hưởng app kia.Mục tiêu: tạo shared boundary tối thiểu đủ để Builder và Web cùng render event.
Actions:
packages/nuxt-layers/event-runtime.EventPageRenderer.@promotion/* sang package name.@/ và ~/ khỏi shared package.event-contract nếu phần model đã thực sự framework-agnostic.Exit criteria:
Mục tiêu: Console sở hữu Builder nhưng Web vẫn có fallback tạm thời nếu cần.
Actions:
layers/event-builder vào apps/console/layers/event-builder.Exit criteria:
apps/web.Mục tiêu: nâng riêng Console, không thay đổi Web.
Actions:
srcDir tường minh.Exit criteria:
Rollback:
Actions:
SUPPORTED_BUILDER_ENABLED_RUN_TYPES.extends./builder.Exit criteria:
Actions:
apps/web.[email protected], [email protected].Exit criteria:
Mức ảnh hưởng: cao trong migration, thấp sau khi ổn định.
Thay đổi:
apps/web.process.cwd() trong build hooks sẽ thay đổi thành app directory.@/ trong layers không còn an toàn..well-known copy phải chạy với đúng app cwd.Giảm thiểu:
RUN_TYPE.Mức ảnh hưởng: cao.
Rủi ro:
patch-package có thể patch sai Nuxt location.Giảm thiểu:
npm ls --all trước/sau.Mức ảnh hưởng: trung bình, nhưng blast radius production cao.
Các vùng nhạy cảm:
.well-known.Lưu ý hiện tại:
/api/healthz./healthcheck/_check.html cần Infra xác nhận vì không phải explicit route trong source.RUN_TYPE, SENTRY, BUILD_VERSION phải được bảo toàn.Mức ảnh hưởng: cao, vì từ một layer thành một product.
Static Console không có Nitro server, do đó:
Preview hiện giả định same-origin với public promotion route.
Khuyến nghị:
/preview/[slug].EventPageRenderer từ shared runtime.Nếu bắt buộc iframe Web thật:
frame-ancestors.postMessage phải validate origin và source.“Internal” không thay thế authentication và authorization.
Phải xác nhận:
.onstove.com hay không.seed-core có đọc được trên domain Console hay không.Nếu Console ở domain không thuộc .onstove.com, cookie .onstove.com không thể được browser gửi như same-site app cookie. Cần corporate SSO, edge proxy hoặc backend admin API phù hợp; không thể sửa bằng Nuxt route guard.
npm ci tại root..output được tạo tạm từ apps/web/.output.npm ci
npm run build:live --workspace=@event-hub/web
docker build -f apps/web/Dockerfile apps/web
apps/web/.output/
Infra phải cập nhật:
package-lock.json.Build command:
npm ci
npm run build:live --workspace=@event-hub/console
Artifact path:
apps/console/.output/public/
Path rule ban đầu:
apps/web/** → Web
apps/console/** → Console
packages/nuxt-layers/event-runtime/** → Web + Console
packages/event-contract/** → Web + Console
package.json → Web + Console
package-lock.json → Web + Console
patches/** → app liên quan; mặc định cả hai nếu chưa xác định
Release build sandbox/live không nên bị skip chỉ vì path filter; khi được trigger release, app đó phải build đầy đủ.
| Contract | Web | Console |
|---|---|---|
| Build command | nuxt build | nuxi generate |
| Render | SSR | CSR |
| Output | apps/web/.output | apps/console/.output/public |
| Runtime | Node/Nitro | Static host/Nginx |
| Health | Nitro route | Edge/Nginx route |
| Config | Build-time + server runtime hiện tại | Build-time public config |
| Rollback | Previous Node image | Previous static image/bundle |
comments (0)