Bài này là vòng system-design frontend 45 phút, không phải quiz cây thư mục. Người phỏng vấn xem bạn làm rõ product, chọn SPA vs SSR vs hybrid, vẽ hộp kèm kịch bản lỗi, đặt cache và invalidation, và nêu rõ a11y, auth, và rollout. Vue/Nuxt trước; Next cùng ý hybrid với RSC.
Đọc 16.1 trước (khung nói và whiteboard). 16.2 là tầng cache và invalidation: câu nối tiếp thường gặp. 16.3 là component library như một product. 16.4 và 16.5 là ví dụ làm sẵn: dashboard nội bộ, rồi hybrid marketing + app traffic cao. Thư mục thuộc 15.5; chúng không phải kiến trúc.
Tóm tắt: Câu trả lời system-design frontend là khung nói: làm rõ yêu cầu, nêu component, đặt data và state, rồi hiệu năng, rồi a11y/bảo mật, rồi rollout. Thư mục không phải kiến trúc (đó là 15.5). SPA vs SSR vs hybrid là quyết định product, không phải mặc định.
Vì sao quan trọng: Bốn mươi lăm phút. Junior vẽ cây thư mục. Senior làm rõ user, SEO, realtime, region, auth, scale, team/deploy, offline, SLA, rồi chọn SPA vs SSR vs SSG/ISR vs rule hybrid theo route. SSR một dashboard đã auth mà không cần SEO, hoặc micro-frontend cho sáu người, là tín hiệu.
- Cách hoạt động: 1. Yêu cầu. User, SEO, realtime, region, auth, scale, lịch team/deploy, offline, SLA. Nói to hai phút trước khi vẽ.
- 2. Component (hộp). CDN/edge, HTML (Nuxt/Nitro hoặc static), JS/CSS đã hash, BFF
/api, API product, auth/session, WS/SSE tùy chọn, flag service. Mỗi hộp một kịch bản lỗi: HTML CDN cũ, BFF timeout, WS rớt, flag down, JS 404 sau deploy. - 3. Data và state. URL cho filter share được. Query cache cho data server. Pinia cho UX client. 4. Hiệu năng. LCP/INP, TTFB, điện thoại tầm trung ở region tệ nhất. 5. A11y và bảo mật. Bàn phím, authz trên server, cookie không nằm trên HTML cache được. 6. Rollout. Flag, thời gian rollback, error budget.
- Mặc định một app. Đừng chọn micro-frontend trừ khi họ buộc ràng buộc team/deploy (15.4). Dashboard thì gần như luôn cần BFF. Next App Router: RSC cho content, client cho widget dashboard, cùng ý hybrid. Hydration mismatch từ
Date.now(), flag,windowlà hộp bạn nêu rõ.
[User] → [CDN / edge] → [HTML: Nuxt/Nitro hoặc static]
├─ JS/CSS đã hash (immutable)
├─ BFF (cùng origin /api)
│ ├─ API product
│ ├─ auth / session
│ └─ DTO gộp
├─ WS/SSE gateway (tùy chọn)
└─ flag service (timeout ngắn, fail-safe)
Browser cache ← → Service worker (tùy chọn, chủ yếu static)
Kịch bản lỗi cần nêu: HTML cũ, BFF timeout, WS rớt, flag down, JS 404 sau deploy
Không phải kiến trúc: app/features/orders/components/ ← đó là 15.5Điều gì xảy ra: Whiteboard là hộp và seam, không phải thư mục. Mỗi hộp có một kịch bản lỗi. Cây thư mục là bằng chứng mười dòng bạn đã ship Vue, rồi bạn chuyển sang ranh giới, SLA, và rollback.
| Bước (nói) | Bẫy nếu bỏ |
|---|---|
| 1. Yêu cầu nói to | SSR dashboard riêng tư; SPA site content |
| 2. Hộp + một lỗi mỗi hộp | Sơ đồ chỉ vẽ happy path |
| 3. Ownership data/state | Pinia global cho list từ server |
| 4–6. Hiệu năng, a11y/bảo mật, rollout | Không INP, không authz, không rollback |
Góc nhìn senior
- Nói yêu cầu trong hai phút trước khi vẽ. Làm rõ là điểm cho thấy bạn senior.
- Một kịch bản lỗi mỗi hộp. Sơ đồ không có HTML cũ / BFF timeout / JS 404 chỉ là phác thảo, chưa phải design.
- Thư mục là 15.5. Mười dòng thư mục feature chứng minh bạn đã ship, rồi rẽ hướng.
- Mặc định một app. Micro-frontend chỉ khi ràng buộc tổ chức ở 15.4 là thật.
- Next: RSC cho content, client cho widget. Cùng ý hybrid với
routeRulescủa Nuxt.
Lỗi hay gặp
- Triệu chứng: cây thư mục là cả câu trả lời. Nguyên nhân: bỏ hộp. Cách xử lý: khung sáu bước, rồi 15.5.
- Triệu chứng: SSR dashboard đã login. Nguyên nhân: SSR thành mặc định. Cách xử lý: cần SEO/LCP, không thì CSR + BFF.
- Triệu chứng: micro-frontend cho sáu người. Nguyên nhân: theo mốt. Cách xử lý: một app, package (15.4).
- Triệu chứng: chỉ happy path. Nguyên nhân: không có kịch bản lỗi. Cách xử lý: một lỗi mỗi hộp, nói ra miệng.
Nhà tuyển dụng hay hỏi gì
Thiết kế frontend cho X. Họ muốn bạn dẫn vòng thành lời, vẽ hộp kèm kịch bản lỗi, và từ chối coi cây thư mục là kiến trúc.
Trả lời khi phỏng vấn
Mình dẫn vòng thành lời. Hai phút về user, SEO, realtime, region, auth, scale, lịch deploy, SLA. Rồi mình vẽ hộp: CDN, HTML, asset đã hash, BFF, API product, auth, WS tùy chọn, dịch vụ flag với timeout ngắn. Mỗi hộp một kịch bản lỗi: HTML cũ, BFF timeout, WS rớt, flag down, JS 404 sau deploy. State cần share thì để trên URL; list từ server nằm query cache; Pinia là UX phía client. Mình chọn SPA hay SSR hay hybrid từ những yêu cầu đó, không phải mặc định. Thư mục là mười dòng nếu họ hỏi, rồi mình rẽ: đó là 15.5, không phải câu này. Với dashboard mình gần như luôn thêm BFF. Rollback và flag để phút cuối.
Nhớ nhanh
- Sáu bước: yêu cầu, hộp, data/state, hiệu năng, a11y/bảo mật, rollout.
- Một kịch bản lỗi mỗi hộp. Thư mục là 15.5, không phải design.
- SPA hay SSR hay hybrid là quyết định product. Mặc định một app.
- Nói yêu cầu trước khi vẽ. Làm rõ là điểm cho thấy bạn senior.
Câu hỏi nối tiếp thường gặp
- Khi nào SSR là mặc định sai? Dashboard đã auth, không cần SEO. CSR kèm BFF.
- Một kịch bản lỗi cho hộp dịch vụ flag? Timeout; fail-safe về variant biết lần cuối.
- Thư mục nằm chỗ nào? Mười dòng, rồi 15.5. Chúng không phải các hộp.
- Nuxt hay Next trong khung này? Cùng hybrid. RSC so với
routeRuleslà cách map, không phải framework mới.
Tóm tắt: Cache ở tầng ngoài cùng còn an toàn. Invalidation mới là câu phỏng vấn. Static đã hash sống mãi (immutable). HTML và JSON nhận TTL kèm tag. HTML cá nhân hóa không bao giờ public. localStorage giữ preference và bản nháp, không phải token auth.
Vì sao quan trọng: “Bạn cache thế nào?” Junior liệt kê localStorage. Senior nói tầng và invalidation: “user đổi avatar, cache nào còn hiện cái cũ?” Ai cũng gán được max-age. Thiếu cache key là bug bảo mật (Cache-Control: public trên /api/me).
- Cách hoạt động: HTTP browser cho
/_nuxtđã hashimmutablevà một ít JSON GET public. Memory / TanStack Query cho data màn hình, có key,invalidateQueriessau mutation. localStorage / IDB cho preference và bản nháp, có version, xóa lúc logout, không bao giờ token. CDN / edge cho HTML, JSON public, ảnh, purge bằng surrogate key. Origin / BFF (Nitro, Redis) cho payload SSR và bản gộp: tag theo entity, đừng cache kép mù. - Pattern invalidation cần nêu rõ: (1) Hash tên file: cache tốt nhất; HTML không được sống lâu bằng JS. (2) TTL cho “đủ tốt”; ETag /
If-None-Matchcho polling rẻ. (3) Surrogate-Key / cache tag: purge mọi thứ gắnproduct:9hoặcuser:9. (4) Purge theo event lúc publish. Key cache (locale, auth), không thì là bug bảo mật. Service worker precache asset đã hash và network-first cho HTML, có kill switch: đừng cache HTML mãi trên SaaS ship hằng ngày. - GraphQL ở CDN: hash theo query hoặc TTL ngắn; POST khó cache. Nuxt: key
useAsyncData,cachedEventHandler,routeRules. Next 15:fetchmặc định không cache;revalidateTag/revalidatePathsau mutation. Hard refresh không phải strategy invalidation.
// Avatar đổi: xử lý mọi tầng còn có thể hiện cái cũ.
await fetch("/api/me/avatar", { method: "POST", body })
queryClient.invalidateQueries({ queryKey: ["me"] }) // memory / TanStack Query
// CDN: PURGE /cdn-cgi/tags/user:9 (surrogate-key): không phải hard refresh
// HTML cho /app là no-store; /_nuxt/avatar-hash.js đã hash là immutable
localStorage.removeItem("draft:profile") // không bao giờ để token ở đây
// Next 15: revalidateTag("user-9") sau Server Action
// SW: network-first cho HTML; header kill switch bỏ qua precacheĐiều gì xảy ra: Mutation invalidate query, purge tag CDN, và để asset đã hash yên. Token không bao giờ nằm trong localStorage. Hard refresh không có trong list.
| Tầng | Lưu | Invalidate |
|---|---|---|
| HTTP browser | JS đã hash immutable; một ít JSON GET | Đổi hash; ETag cho file chưa hash |
| Memory / Query | Data màn hình, vài giây–phút | Query key + invalidateQueries |
localStorage / IDB | Preference, bản nháp, không phải token | Version key; xóa lúc logout |
| CDN / edge | HTML, JSON public, ảnh | Tag / surrogate key; TTL HTML ngắn |
| Origin / BFF | Payload SSR, aggregate | Tag theo entity; không cache kép mù |
Góc nhìn senior
- Invalidation là câu phỏng vấn. Đổi avatar: query + tag CDN
user:9. Không bao giờ lấylocalStoragelàm source of truth cho avatar. - HTML cá nhân hóa không bao giờ
public. Thiếulocalehoặc auth trên cache key là bug bảo mật. - Service worker: kill switch; đừng cache HTML mãi trên SaaS ship hằng ngày.
- TTL ổn cho ISR marketing, sai cho tồn kho. Tag theo entity bạn nêu ra được.
- Hard refresh không phải strategy. CDN bỏ qua trang
Set-Cookiebị cache nhầm: đó là incident.
Lỗi hay gặp
- Triệu chứng:
/api/metrả về nhầm user. Nguyên nhân:publickhông có key auth. Cách xử lý:private/no-store.- Triệu chứng: avatar cũ sau save. Nguyên nhân: chỉ mutate memory. Cách xử lý: query + tag CDN.
- Triệu chứng:
index.htmlmột năm tuổi từ SW. Nguyên nhân: precache HTML. Cách xử lý: HTML network-first + kill switch.- Triệu chứng: “cứ hard refresh”. Nguyên nhân: không có câu chuyện invalidation. Cách xử lý: hash, TTL, tag, event.
Nhà tuyển dụng hay hỏi gì
Bạn cache thế nào, và user đổi avatar thì tầng nào còn hiện cái cũ? localStorage làm cả câu trả lời là junior.
Trả lời khi phỏng vấn
Mình cache asset đã hash mãi và HTML ngắn, có tag. Invalidation là câu phỏng vấn: đổi avatar thì purge user-9 ở CDN, invalidate query, và token auth không bao giờ nằm trong localStorage. HTML cá nhân hóa không bao giờ public. Mình không gắn service worker trên SaaS ship hằng ngày trừ khi có kill switch. Thiếu locale trên cache key HTML là bug bảo mật. Fetch của Next 15 mặc định không cache; mình revalidate tag sau mutation. Hard refresh không phải strategy. Ở admin nội bộ, ảnh profile nằm trong query cache và tag CDN, không phải localStorage.
Nhớ nhanh
- Tầng ngoài cùng còn an toàn. Invalidation là câu phỏng vấn, không phải
max-age. - Static đã hash mãi. HTML/JSON: TTL kèm tag. Không bao giờ public HTML cá nhân hóa.
- Đổi avatar: query + tag CDN. Token không bao giờ trong
localStorage. - Service worker cần kill switch. Hard refresh không phải strategy.
Câu hỏi nối tiếp thường gặp
- Cache GraphQL thế nào? Hash theo query hoặc TTL ngắn. POST khó; ưu tiên GET có tag ở BFF.
- Service worker khác HTTP cache chỗ nào? SW precache asset đã hash. HTML là network-first kèm kill switch.
- User đổi avatar, tầng nào? Query, tag CDN
user:9, không phải JS đã hash, không bao giờ token localStorage. - Vì sao thiếu key locale trên HTML là bug bảo mật? Bạn có thể trả HTML nhầm ngôn ngữ, hoặc HTML nhầm user.
Tóm tắt: Component library là một product: token, primitive accessible, package có version với peer vue, và ownership. Widget domain ở lại trong app (15.6). Bốn team không cần bốn Button. Padding đẩy layout là major.
Vì sao quan trọng: “Thiết kế component library cho bốn product team”. Họ muốn token, primitive APG, semver, và một guild, không phải Storybook đẹp. Hex trong mọi SFC, “button” icon không phải <button>, và major bump mỗi tháng là các kịch bản lỗi.
- Cách hoạt động: Biến CSS với tên semantic (
--color-danger). Dark mode đổi token; class trên<html>lấy từ cookie để tránh nháy. Bàn phím, focus trap, pattern listbox, contrast trên token.exportsmap, entrypoint theo component,sideEffectscho CSS. RFC cho primitive mới; request riêng của app nhận lời không kèm cách compose. - Styled + token nhanh hơn cho một brand; headless nếu brand lệch mạnh. Bắt đầu mono, tách
@acme/ui-buttonnếu build đau. App ghim semver; Renovate; đừngworkspace:*mãi khi lịch deploy đã tách. Dark-launchButtonNexthoặc flag trong package. Visual regression: Playwright/Chromatic, token làm fixture. - DS React + Vue: share token, primitive native mỗi framework. Đừng bọc Vue trong React. Storybook không được là runtime duy nhất; Nuxt SSR phải pass. Topic này là thiết kế 45 phút; 15.6 là đường cắt ownership so với component app.
// packages/ui/src/button/index.ts: entry theo component, peer vue
export { default as UiButton } from "./UiButton.vue"
export type { UiButtonProps } from "./UiButton.vue"
// package.json: "exports": { "./button": "./src/button/index.ts" }
// "peerDependencies": { "vue": "^3.5" }, "sideEffects": ["**/*.css"]
export const tokens = {
"--color-danger": "oklch(0.6 0.2 25)", // semantic; dark mode đổi value
}
// Đổi visual đẩy padding → major. orderId domain → từ chối, xem 15.6.Điều gì xảy ra: App import @acme/ui/button, không phải barrel của tất cả. Vue ở lại peer. Token là semantic. Đẩy padding là major bạn ghi changelog.
| Ship | Từ chối |
|---|---|
| Token + primitive APG + docs | Hex trong mọi SFC; widget click trên div |
Peer vue; exports theo component | Bundle Vue; một barrel 3 MB |
| Semver: visual break = major | Major mỗi tháng; padding lệch im lặng |
| Guild + roadmap công khai | Ba Button.vue không có owner |
Góc nhìn senior
- DS là một product. Làm sai phải khó trong API primitive (chỗ rò 15.6:
orderIdtrên Button). - A11y không thương lượng. Dialog không có focus trap thì không phải primitive.
- Changelog + codemod cho major. App ghim; Renovate.
- Component chỉ chạy Storybook mà fail Nuxt SSR thì không ship.
- Khác với 15.6: ở đây bạn thiết kế library. Bên kia bạn vẽ đường cắt với widget của app.
Lỗi hay gặp
- Triệu chứng: hex trong mọi SFC. Nguyên nhân: không có token. Cách xử lý: biến CSS semantic, dark mode là một lần swap.
- Triệu chứng: major mỗi tháng. Nguyên nhân: không kỷ luật semver. Cách xử lý: visual break = major; phần thêm đứng sau flag.
- Triệu chứng: fail Nuxt SSR. Nguyên nhân:
windowchỉ có trên Storybook. Cách xử lý: story SSR.- Triệu chứng: bốn team, bốn Button. Nguyên nhân: không có owner. Cách xử lý: một package, một guild, một roadmap.
Nhà tuyển dụng hay hỏi gì
Thiết kế component library cho bốn product team. Token, a11y, versioning, ownership: không phải một Storybook đẹp.
Trả lời khi phỏng vấn
Mình ship token và primitive thành package có version với peer Vue. App ghim semver rồi compose, không fork Button. A11y không thương lượng: dialog không có focus trap thì không phải primitive. Padding làm lệch layout là major. Widget checkout domain để trong app. Ownership là guild với roadmap công khai. Mình bắt đầu mono, tách package khi build đau. Visual regression dùng token làm fixture. React và Vue share token, primitive native theo từng framework. Ở admin nội bộ, team mình import @acme/ui/button; PayButton ở lại checkout.
Nhớ nhanh
- Library = token + primitive APG + package có version + một guild.
- Visual break = major. Widget domain ở lại trong app (15.6).
- Peer
vue. Export theo component. Không barrel 3 MB. - Storybook không phải runtime duy nhất. Nuxt SSR phải pass.
Câu hỏi nối tiếp thường gặp
- Visual-regression test thế nào? Chromatic/Playwright trên primitive; token làm fixture.
- DS React + Vue? Dùng chung token. Primitive native mỗi framework. Đừng bọc Vue trong React.
- Khi nào thay đổi visual là major? Nó làm lệch layout hoặc phá contract đã ghi.
- Bạn nói không với gì? Prop domain trên primitive. Đề xuất compose trong app.
Tóm tắt: Dashboard analytics / ops là nặng CSR, một DTO dashboard BFF (hoặc gọi widget trong budget), filter trên URL, hàng đã virtualize, permission trên server, và error boundary mỗi widget. Dùng khung sáu bước 16.1, nói to.
Vì sao quan trọng: Đây là dàn bài 45 phút đã làm sẵn. Bẫy: mười hai waterfall từ browser, filter chỉ trong Pinia (không share được, nút back gãy), 50k hàng DOM, giấu nút mà để lại endpoint, một chart throw làm trắng cả app.
- Cách hoạt động: Yêu cầu. User nội bộ, live cỡ nào, số hàng, export, đa region, saved view. SEO yếu → Nuxt nặng CSR, chart sau
<ClientOnly>. Hộp. BFFGET /bff/dashboard(hoặc widget song song với timeout gateway). Permission bị strip trên server; giấu cột chỉ là UX. - Data/state.
?from=&to=&status=&q=là nguồn sự thật. Saved view là resource phía server (?view=id). Cursor thường không nằm trên URL với grid infinite; page index dành cho table phân trang. Hiệu năng. Virtualize table; downsample chart trong BFF. Mặc định poll 30s + ETag; SSE cho badge; WS chỉ khi người ta sửa queue. - A11y/bảo mật. Authz trên server; export CSV qua endpoint có log, không lấy từ DOM. Secret không nằm trên URL. Rollout. Flag cho widget mới; timeout mỗi widget; last-good + marker stale. Tách vendor chart. Để ý timezone (
Zkhác local) và chart-lib leak lúc unmount.
// Filter trên URL để support share được view. Không chỉ Pinia.
const from = useRouteQuery("from", "7d")
const status = useRouteQuery("status", "open")
const { data, pending, error, refresh } = await useAsyncData(
() => `dash:${from.value}:${status.value}`,
() => $fetch("/bff/dashboard", { query: { from: from.value, status: status.value } }),
)
// Mỗi widget: boundary riêng + timeout. Table virtualize ở client.
// Cột ẩn ≠ thiếu field: BFF đã strip billing cho role này.Điều gì xảy ra: Support dán URL và thấy cùng một lát. BFF chịu trách nhiệm authz. Widget fail độc lập. Pinia không giữ filter.
| Lựa chọn | Chọn khi | Bẫy |
|---|---|---|
| Một DTO BFF | Widget chung một khoảng thời gian | Một join chậm chặn widget đầu |
| Widget song song | Nguồn độc lập | Waterfall N×N từ browser khi không có gateway |
| Filter URL | View support share được | Secret hoặc query string 4 kB |
| Poll 30s + ETag | Hầu hết dashboard ops | WS đẩy thay cả table |
Góc nhìn senior
- N widget × N API từ browser là bẫy kinh điển. Để BFF phía trước, hoặc budget các call song song.
- Giấu nút là UX. Authz ở server. Catalog không đăng ký route mà role không được thấy.
- “Select all 40k rows” ở client là bug. Export là việc của server trên cùng query.
- Mobile thường là màn rút gọn, không phải cùng 12 widget.
- Kiểm chứng: time-to-first-widget, p95 của DTO, INP lúc scroll, tỉ lệ 403, memory trên tab mở lâu.
Lỗi hay gặp
- Triệu chứng: view không share được, nút back gãy. Nguyên nhân: filter chỉ trong Pinia. Cách xử lý: URL là nguồn sự thật.
- Triệu chứng: màn trắng. Nguyên nhân: một chart throw. Cách xử lý: boundary mỗi widget.
- Triệu chứng: 50k hàng DOM, INP chết. Nguyên nhân: không virtualize. Cách xử lý: virtualize + downsample BFF.
- Triệu chứng: nút ẩn, endpoint mở. Nguyên nhân: authz chỉ ở UI. Cách xử lý: strip trên server.
Nhà tuyển dụng hay hỏi gì
Thiết kế dashboard analytics / ops. Họ muốn bạn áp khung 16.1: CSR, BFF, filter URL, virtualize, permission trên server, widget fail.
Trả lời khi phỏng vấn
Ops nội bộ, SEO yếu, nên Nuxt nặng CSR với BFF GET /bff/dashboard. Filter nằm trên URL để support share được view. Mình virtualize table và downsample chart trong BFF. Permission bị strip trên server; giấu cột là UX. Mỗi widget có boundary và timeout riêng. Mình không mở socket cho cả table: poll 30s kèm ETag, SSE cho badge. Export là endpoint có log trên cùng query, không lấy từ DOM. N widget nhân N API browser là bẫy mình từ chối. Ở admin nội bộ, empty state không đủ quyền là route mà role không bao giờ đăng ký, không phải nút ẩn.
Nhớ nhanh
- Shell nặng CSR, DTO BFF, filter trên URL, virtualize, authz server.
- Error boundary mỗi widget. Cột ẩn ≠ thiếu field.
- N×N API browser là bẫy. Export là việc của server.
- Poll + ETag mặc định. WS chỉ khi người ta sửa queue.
Câu hỏi nối tiếp thường gặp
- Export CSV của tập đã lọc thế nào? Endpoint server, cùng query, có log. Không lấy từ DOM.
- Mobile? Màn rút gọn, không phải 12 widget xếp chồng. Cùng filter URL chỗ nào vẫn vừa.
- Khi nào cursor thuộc URL? Table phân trang, không phải cursor opaque của grid infinite.
- Một DTO BFF khác widget song song? Chung khoảng thời gian → một DTO. Nguồn độc lập → song song sau gateway.
Tóm tắt: Một app Nuxt hoặc Next, một design system, hai profile runtime: content public ở CDN (SSG/ISR, purge lúc publish), /app/** sau auth là no-store. Tách deploy chỉ khi lịch release của team đòi vậy. Request homepage không có cookie user.
Vì sao quan trọng: “Site marketing và product đã login. Một codebase hay hai? Nuxt hay Next?” Một Cache-Control global thì hoặc rò first name, hoặc giết SEO. SSR cả app đã login lúc Super Bowl ad là tín hiệu kia. Vary: Cookie trên / đẩy hit ratio về ~0.
- Cách hoạt động: Marketing: SSG/ISR/SSR cache cao, purge lúc publish.
/app/**: HTML no-store, shell + client, BFF/api. Cookie auth host-only hoặc theo path, không bao giờ trên trang cache được. Homepage ẩn danh: cá nhân hóa sau paint. JS marketing nhỏ; đừng tải dashboard trên/. Kill switch chiến dịch ở edge. - Đánh đổi. Subdomain
app.cô lập cookie/cache; path/appgọn link và một origin CSP. Chọn cô lập khi incident cache làm bạn sợ. ISR 30s khác purge on-demand: purge lúc publish, TTL làm lưới an toàn. Hai framework chỉ khi đã có hai org. A/B hero bằng edge bucket hoặc một cookie bucketing, không phảiVary: Cookietrên mọi thứ. - Nuxt
routeRuleshoặc Next RSC +revalidateTag. URL i18n trùng cần canonical. Navigation trong app không được inherit layout marketing nặng analytics. Copy pháp lý sai: CMS + purge, không rebuild cả. Ảnh OG không được generate mỗi request trên origin.
CDN (cache HTML theo path + locale, KHÔNG cookie)
├─ / ISR/SSG (CMS) : cache dài, purge lúc publish
├─ /pricing SSR/ISR : A/B qua edge bucket, không Vary: Cookie
├─ /blog/:id ISR + OG
└─ /app/** HTML no-store : shell + app client, BFF /api
Cookie auth: host-only trên host app, không bao giờ trên trang cache được
Request homepage: ẩn danh. Cá nhân hóa sau paint.
Kill switch ở edge cho chiến dịch / CMS publish xấu.Điều gì xảy ra: / cache được và ẩn danh. /app là no-store. Chunk dashboard không bao giờ load trên /. CMS publish xấu thì rollback bằng cách khôi phục revision trước và purge tag, không phải rebuild cả.
| Phần | Render / cache | Từ chối |
|---|---|---|
/ marketing | ISR/SSG, purge lúc publish | Cookie user trên request |
/pricing | SSR/ISR, bucket A/B edge | Vary: Cookie trên mọi thứ |
/app/** | HTML no-store + BFF | SSR cả app đã login ở traffic quảng cáo |
| Deploy | Một repo, hai routeRules | Hai framework trừ khi đã có hai org |
Góc nhìn senior
- Một repo, hai profile. Product Vue thì chọn Nuxt, team React thì chọn Next, không phải hai framework.
- Request homepage không mang cookie user. Cá nhân hóa sau paint.
Vary: Cookietrên/làm CDN mất hết hit. - Subdomain khác
/app: cô lập so với một origin. Chọn cô lập sau incident rò cache. - Kill switch chiến dịch ở edge. Rollback CMS bằng revision trước + purge tags.
- Chunk dashboard không load trên
/. JS marketing giữ nhỏ.
Lỗi hay gặp
- Triệu chứng: tên user trên homepage đã cache. Nguyên nhân: cookie trên
/. Cách xử lý: HTML ẩn danh, cá nhân hóa sau paint.- Triệu chứng: origin quá tải lúc chạy quảng cáo. Nguyên nhân: SSR
/appở scale marketing. Cách xử lý: shell no-store, CDN các route public.- Triệu chứng: hit ratio ~0. Nguyên nhân:
Vary: Cookie. Cách xử lý: edge bucket hoặc một cookie A/B chỉ trên/pricing.- Triệu chứng:
echartstrên/. Nguyên nhân: layout dùng chung. Cách xử lý: tách chunk dashboard; layout marketing giữ mỏng.
Nhà tuyển dụng hay hỏi gì
Site marketing và product đã login. Một codebase hay hai? Họ muốn hybrid route rules, hai profile runtime, và cache không rò tên user.
Trả lời khi phỏng vấn
Một repo, hai profile. Marketing là ISR ở CDN, purge lúc publish; /app là no-store kèm BFF. Product Vue thì mình chọn Nuxt, team React thì chọn Next, không phải hai framework. Request homepage không mang cookie user. Mình không Vary Cookie trên /. Chunk dashboard không load trên /. A/B hero bằng edge bucket. Rollback CMS publish xấu bằng revision trước và purge tags. Tách deploy chỉ khi đã có hai lịch release riêng. Ở shape admin kèm marketing, bọn mình path-scope cookie trên /app và giữ / ẩn danh.
Nhớ nhanh
- Một app, hai profile: content public trên CDN,
/appno-store. - Request homepage không mang cookie user. Cá nhân hóa sau paint.
- Đừng
Vary: Cookietrên/. Đừng load dashboard trên/. - Tách deploy chỉ khi lịch release của team đòi vậy. Kill switch ở edge.
Câu hỏi nối tiếp thường gặp
- A/B hero mà không phá cache thế nào? Edge bucket hoặc một cookie bucketing trên
/pricing, không phảiVary: Cookietrên/. - Rollback CMS publish xấu thế nào? Revision trước + purge tags. Không rebuild cả.
- Subdomain khác
/app? Cô lập so với một origin CSP. Chọn cô lập sau khi rò cache. - Khi nào tách thành hai deploy? Đã có hai lịch release riêng, không phải vì route khác nhau.