Hub

Câu hỏi thực hành

Đây là những câu hỏi kiểu “bạn làm việc thế nào?”. Junior kể tool rồi dán interceptor axios xóa storage và đá về login. Senior nói hàng đợi refresh, ticket thành thật, git trunk-based, incident mitigate trước, và bản đồ tuần 1 của một codebase Vue.

Đọc 18.1 trước (401 không phải logout). Rồi 18.2–18.3 cho cách cắt việc và triage. 18.4–18.6 là cơ chế git. 18.7 là thời điểm xử lý incident. 18.8 là tuần 1. Vue 3 / Nuxt trước; cơ chế 401 áp cho React/Next. Khác leadership 17.x: artefact ở đây là playbook, không phải giọng mentoring.


Tóm tắt: 401 nghĩa là “access token fail”, chưa phải “logout user”, cho đến khi refresh cũng fail. Bỏ qua route auth, single-flight một lần refresh, replay một lần, rồi mới logout mọi tab với URL trả về an toàn. 403 là chuyện quyền: đừng logout. Khác Q&A JWT login-flow và hàng đợi refresh: ở đây UI redirect và hàng đợi sống cùng nhau.

Vì sao quan trọng: “API trả 401, client làm gì?” Interceptor junior xóa storage rồi router.push("/login"). Họ chờ bạn nói refresh, hàng đợi, vòng lặp, URL trả về, đa tab, và 401 khác 403. Login POST 401 vì mật khẩu sai: đó không phải hết session.

  1. Cách hoạt động: Bỏ qua /login, /logout, /refresh. Một promise refresh in-flight; mọi request khác chờ. Thành công → retry request gốc một lần. Fail → xóa query cache, BroadcastChannel logout, đi tới login với redirect chỉ là path nội bộ tương đối (safeInternalPath).
  2. Giữ URL trả về để form dài không mất, nhưng open-redirect là lỗ hổng: chỉ nhận path bắt đầu bằng / và không phải //. Redirect hay hiện modal “hết session” trên form dài (lưu draft trước) là product quyết.
  3. SSR: đừng để document bị redirect loop. 401 lúc server render /app thì render location login một lần. Đừng gọi useRoute() ngoài setup: hãy truyền currentRoute. Tám useAsyncData chạy song song không được thành tám redirect.
let refresh: Promise<boolean> | null = null
const authChannel = new BroadcastChannel("auth")
function safeInternalPath(p: string) {
  return p.startsWith("/") && !p.startsWith("//") ? p : "/app"
}
export async function on401(ctx: { url: string; path: string; retry: () => Promise<unknown> }) {
  if (/\/api\/auth\/(login|logout|refresh)/.test(ctx.url)) throw new Error("auth-route-401")
  if (!refresh) {
    refresh = $fetch("/api/auth/refresh", { method: "POST" }).then(() => true).catch(() => false)
      .finally(() => { refresh = null })
  }
  if (await refresh) return ctx.retry()
  queryClient.clear()
  authChannel.postMessage({ type: "logout" })
  await navigateTo({ path: "/login", query: { redirect: safeInternalPath(ctx.path) } })
}

Điều gì xảy ra: Tám 401 chia một refresh. Mật khẩu sai trên /login thì throw, không xóa session. Sau khi refresh fail, mọi tab logout và login nhận /orders/42, không phải https://evil.test. 403 không bao giờ vào hàm này.

StatusClientKhông
401 trên APIRefresh một lần, rồi logoutĐá thẳng
401 trên /loginHiện lỗi fieldXóa session
403Empty không quyền / CTALogout
Refresh 401Logout mọi tab + trả về an toànRetry refresh trong vòng lặp

Góc nhìn senior

  • CSRF trên POST refresh: cookie same-site kèm CSRF token nếu bạn dùng cookie-session. Đó là security 10.x.
  • Test: tám 401 song song → một refresh; redirect độc bị bỏ; 403 vẫn giữ session.
  • Vòng login SSR: document bị redirect về login rồi 401 tiếp. Render login, đừng fetch /me trên route đó.
  • Q&A hàng đợi JWT là phần trong interceptor; topic này là redirect và URL trả về.

Lỗi hay gặp

  • Logout mọi 401. Triệu chứng: một blip đá user. Nguyên nhân: không refresh. Cách xử lý: refresh single-flight.
  • redirect=https://evil.test. Triệu chứng: open redirect. Nguyên nhân: lấy query thô. Cách xử lý: safeInternalPath.
  • Coi 403 như 401. Triệu chứng: trang cấm lại logout. Cách xử lý: copy khác, ở lại session.
  • Tám redirect từ tám useAsyncData. Triệu chứng: nháy. Nguyên nhân: không promise chung. Cách xử lý: singleton refresh.

Nhà tuyển dụng hay hỏi gì

API trả 401, client làm gì? Interceptor junior thì blog nào cũng có. Họ chờ bạn nói refresh, hàng đợi, vòng lặp, URL trả về, và đa tab, kèm 401 khác 403.

Trả lời khi phỏng vấn

Mình không xóa storage rồi đá về login mỗi lần 401. Mình bỏ qua route auth, single-flight refresh, replay một lần, rồi mới logout mọi tab với URL trả về an toàn. 403 không phải 401. Mật khẩu sai trên form login không phải hết session. Open-redirect là lý do path trả về phải nội bộ. Tám fetch song song chia một refresh. Các mục JWT đã nói chuỗi login và shape hàng đợi; đây là redirect mình ship trên sản phẩm admin.

Nhớ nhanh

  • 401 → refresh một lần → logout. 403 ở lại session.
  • Single-flight. URL trả về nội bộ an toàn. BroadcastChannel các tab.
  • Bỏ route auth. 401 lúc login là lỗi field.
  • Tám useAsyncData không được thành tám redirect.

Câu hỏi nối tiếp thường gặp

  • Test thế nào? Tám 401 song song, một refresh; query độc bị bỏ; 403 vẫn ở trong session.
  • CSRF trên POST refresh? Cookie same-site kèm token nếu cookie-session. Doc security.
  • 401 khác 403, client làm gì? Refresh khác empty không quyền. Không bao giờ logout lúc 403.
  • SSR tránh vòng redirect login thế nào? Render login; đừng fetch me trên route đó.

Tóm tắt: Jira hay Linear không quan trọng. Việc của bạn là lát dọc, status thành thật, rủi ro sớm, và một increment user nhìn thấy được. Tool là database. Done nghĩa là empty/error/bàn phím, không phải “trông như Figma”.

Vì sao quan trọng: “Jira hay Linear?” Họ không quan tâm tool nào. Họ quan tâm bạn cắt việc thế nào, giữ status thành thật, và nêu rủi ro trước Friday. Việc FE hay giấu trong “90% xong, chờ API”. Nếu board không hiện điều đó, bạn đang nói dối.

  1. Cách hoạt động: Một path user kèm loading/error/empty, không phải “hết component rồi hết API”. Definition of done cho control Vue: empty, error, không quyền, bàn phím, một test trên chỗ rủi ro.
  2. Viết rủi ro VAT-API lên ticket lúc planning. Spike là spike, không phải feature. WIP mới là vấn đề thật; tool map chỉ là footnote. Team FE sản phẩm thường làm Kanban kèm lớp planning tuần; đừng chết vì trivia Scrum khác Kanban.
  3. PM viết giải pháp chứ không phải vấn đề là 17.5 (nói không). Bug prod không kế hoạch khác sprint là 18.3 /

    • 1
    • 8.7. Status “in review” ba ngày mà không nêu reviewer là bug process bạn chịu trách nhiệm.

Góc nhìn senior

  • Cycle time, WIP, blocker quá một ngày không có owner, % mục tiêu sprint đã ship: đó là thứ đo, không phải story point như bản tin thời tiết.
  • Ở sản phẩm admin mình cắt “export CSV một table, kể cả empty và 403”, không phải “dựng framework export”.
  • Vàng thành thật thắng board xanh giấu API.
  • Kiểm chứng: một ticket người lạ làm xong được; một rủi ro ghi ngày trước Friday.

Lỗi hay gặp

  • Lát ngang (hết API, rồi hết UI). Triệu chứng: không gì demo được. Cách xử lý: một path.
  • Coi spike là feature. Triệu chứng: “90% xong” mãi. Cách xử lý: time-box, rồi quyết.
  • Chiến tranh tool. Triệu chứng: trượt vòng. Nguyên nhân: họ hỏi về việc, không phải Jira.
  • Done = khớp Figma. Triệu chứng: không empty, không bàn phím. Cách xử lý: DoD gồm những thứ đó.

Nhà tuyển dụng hay hỏi gì

Jira hay Linear? Họ không quan tâm tool nào. Họ quan tâm bạn cắt việc thế nào, giữ status thành thật, và nêu rủi ro trước Friday.

Trả lời khi phỏng vấn

Mình không quan tâm Jira hay Linear. Mình cắt một path user đi hết được, viết rủi ro VAT-API lên ticket lúc planning, và không coi spike là feature. Done nghĩa là empty state và bàn phím, không phải trông như Figma. Nếu board nói 90 phần trăm đang chờ API, mình để đúng status đó, không phải thẻ xanh. WIP là vấn đề; tool là footnote. Nói không với PM viết giải pháp là leadership 17.5. Bug production không kế hoạch là 18.3. Ở sản phẩm admin, lát cắt là export CSV cho một table, kể cả 403.

Nhớ nhanh

  • Tool là database. Lát dọc. Status thành thật.
  • Done gồm empty, error, không quyền, bàn phím.
  • Spike là spike. WIP mới là vấn đề thật.
  • Nêu rủi ro API lên ticket trước Friday.

Câu hỏi nối tiếp thường gặp

  • Làm việc với PM viết giải pháp chứ không phải vấn đề thế nào? 17.5: nhắc lại mục tiêu, đề lát mỏng hơn.
  • Xử lý bug prod không kế hoạch khác sprint thế nào? Mức nghiêm trọng 18.3; 18.7 nếu đang cháy.
  • Definition of done của control Vue gồm gì? Empty, error, 403, bàn phím, một test chỗ rủi ro.
  • Scrum khác Kanban cho team FE sản phẩm? Kanban kèm lớp planning tuần. Đừng chết vì nhãn.

Tóm tắt: So báo cáo với product contract. Nếu user kỳ vọng hợp lý và mình làm gãy, đó là defect. Mức nghiêm trọng (thanh toán, khóa auth, mất data, màn trắng) quyết hotfix hay sprint sau, không phải loại ticket Jira.

Vì sao quan trọng: “Đây là bug hay feature?” Họ test xem bạn cãi nhãn Jira hay nói contract, mức nghiêm trọng, và impact lên user. Support ghi mọi thứ là bug. Sales ghi mọi thứ là hotfix. Khác 18.2 (cách cắt việc) và 18.7 (khi production đang cháy).

  1. Cách hoạt động: Contract nằm trong spec, ADR, copy UI, API docs, và legal. Nút Export hiện trên UI mà không làm gì là UI-contract defect. “Xin thêm export” là thay đổi. Fail WCAG trên control đã ship là defect, không phải nice-to-have.
  2. Mức nghiêm trọng, không phải loại ticket, quyết thời điểm xử lý. Sev-1 (thanh toán, khóa auth, mất data, màn trắng trên /): mitigate ngay. Sev-2: trong ngày. Sev-3: xếp lịch. Cosmetic ở lại backlog. On-call kèm PM quyết mức; frontend mang blast radius từ RUM.
  3. “Won’t fix” cần lý do ghi lại. Một “bug” thầm lặng mà thật ra là đổi rule giá thì thuộc product và legal. Đừng dán lại nhãn một miss để bảo vệ dashboard SLA. Revert nếu blast radius là cả release; fix forward nếu revert tệ hơn.
Tín hiệuDefectThay đổi
ContractĐã hứa X, hoặc trước đó từng XX mới
Hướng xử lý thường gặpHotfix hoặc sprint này, tùy mức nghiêm trọngƯu tiên trên roadmap
Rủi roRegression; thêm testScope, design, flag
Fail a11y đã shipDefect trên control đóViệc a11y mới trên màn mới
Nút chết còn hiệnDefect UI contract"Xin thêm nút này"

Góc nhìn senior

  • Ai quyết mức nghiêm trọng: on-call và PM, kèm phạm vi ảnh hưởng từ RUM. Frontend không tự gắn Sev-1.
  • Requirement v1 còn thiếu mà user đã thấy trên UI thì vẫn là defect, dù engineering chưa từng build.
  • Đổi nhãn để bảo vệ dashboard SLA là nói dối process. Ghi lại miss.
  • Kiểm chứng: escaped defect, tỷ lệ hotfix, time-to-mitigate Sev-1, thời gian cãi nhãn (nhắm về 0).

Lỗi hay gặp

  • Cãi loại Jira trong khi checkout đang gãy. Triệu chứng: user phải chờ. Sửa: mức nghiêm trọng trước, nhãn sau.
  • Coi requirement v1 còn thiếu là “feature” để tránh hotfix. Triệu chứng: SLA trông xanh. Sửa: ghi lại miss.
  • Ship một “bugfix” thực ra là đổi schema, không có PM. Triệu chứng: product bất ngờ. Sửa: đó là thay đổi.
  • Dùng “là feature” để bỏ RCA. Triệu chứng: lỗ hổng mở lại. Sửa: RCA trên escaped defect.

Nhà tuyển dụng hay hỏi gì

Đây là bug hay feature? Họ không quan tâm loại Jira. Họ muốn nghe bạn nói contract, mức nghiêm trọng, và impact với user. Họ cũng muốn biết bạn có đổi nhãn một miss để bảo vệ SLA hay không.

Trả lời khi phỏng vấn

Mình không cãi loại Jira. Mình hỏi team mình đã hứa gì và user đáng ra kỳ vọng gì. Nút chết còn hiện là defect trong UI contract; export mới là thay đổi. Accessibility trên control đã ship là defect. Mức nghiêm trọng đến từ phạm vi ảnh hưởng: checkout gãy là Sev-1, bất kể nhãn. On-call và PM quyết, mình mang RUM. Mình không đổi nhãn một miss để bảo vệ dashboard SLA. Revert nếu cả release nằm trong phạm vi ảnh hưởng; fix forward nếu revert tệ hơn. Cháy production không nằm trong plan là 18.7. Cách mình cắt việc đã plan là 18.2.

Nhớ nhanh

  • Loại issue chỉ là sổ sách. Contract mới quyết defect hay thay đổi.
  • Mức nghiêm trọng, không phải nhãn, quyết hotfix hay sprint.
  • Accessibility trên control đã ship không phải nice-to-have.
  • Đừng đổi nhãn một miss để bảo vệ dashboard SLA.

Câu hỏi nối tiếp thường gặp

  • Ai quyết mức nghiêm trọng? On-call và PM. Frontend mang phạm vi ảnh hưởng từ RUM, không tự gắn Sev-1.
  • PM muốn dán feature để bảo vệ SLA? Ghi lại miss. Đừng đổi nhãn.
  • Fix forward hay revert, chọn thế nào? Revert nếu phạm vi ảnh hưởng là cả release; fix forward nếu revert tệ hơn.
  • Case xám: nút hiện, feature chưa từng build? Defect UI contract. User kỳ vọng hợp lý là có.

Tóm tắt: Team Vue product ship mỗi ngày thì chạy trunk-based: branch sống ngắn, PR, CI gate, flag cho việc chưa xong. GitFlow (develop, release/*, hotfix/*) chỉ khi bạn thật sự phải giữ vài version đang live.

Vì sao quan trọng: “GitFlow hay trunk-based?” Nhiều deck vẫn vẽ GitFlow. Team Vue product ship mỗi ngày thì thường không nên. Họ muốn main luôn releasable, không phải develop thối. Khác 14.2 (pipeline CI) và 18.6 (squash lúc merge).

  1. Cách hoạt động: main luôn releasable. Muốn vậy phải có CI gate và flag. main được bảo vệ: review, CI xanh, không force-push. Conventional commit (feat:, fix:, perf:, revert:) cho changelog và grep, không thay cho mô tả PR.
  2. Branch sống vài giờ đến vài ngày. Lát lớn hơn thì integrate sau flag. GitFlow khi bạn giữ mobile wrapper, on-prem, hoặc lịch release bị regulate. Đừng chạy vì blog nói “phổ biến nhất”. feature/user-profile dài cả tuần và develop thối là thất bại.
  3. Hotfix: branch từ tag prod, hoặc revert rồi fix forward trên trunk nếu bạn deploy từ main. Dùng GitFlow, flag, và preview env là ba cách giấu việc chưa xong nếu không cái nào có owner. main xanh mà CDN đang serve HTML cũ là bug delivery, không phải bug git.
git checkout main && git pull
git checkout -b feat/csv-export-orders
# UI chưa xong đứng sau flag csv_export
git commit -m "feat: csv export for orders table"
git push -u origin feat/csv-export-orders
# PR: lint, vue-tsc, unit, preview, rồi squash vào main
# GitFlow (develop + release/*) chỉ khi vẫn ship v1.4 và v1.5
# hotfix: branch từ tag prod, không từ develop già một tuần

Điều gì xảy ra: Branch ngắn từ main, việc chưa xong đứng sau flag, squash-merge qua CI. GitFlow tắt trừ khi hai version cùng đang live. Dòng hotfix lấy từ tag prod để bạn không dính develop thối.

DùngKhiMùi
Trunk-basedVue product mỗi ngàyFeature branch dài cả tuần
GitFlowVài version đang livedevelop thối
Hotfix từ tag prodSHA live không phải HEAD của mainHotfix từ develop cũ
Flag + PR ngắnLát lớn hơn một ngàyBa chỗ giấu, không owner

Góc nhìn senior

  • Commit thẳng vào main không qua CI không phải tốc độ. main được bảo vệ mới là tốc độ.
  • Một main trong monorepo. CODEOWNERS trên /packages/ui và auth.
  • Không admin force-push, trừ disaster recovery đã ghi lại.
  • Kiểm chứng: tuổi thọ branch, thời gian SHA tới prod, tỷ lệ revert, số phút main đỏ.

Lỗi hay gặp

  • GitFlow vì blog nói “phổ biến nhất”. Triệu chứng: develop thối. Sửa: trunk, trừ khi có vài version.
  • feature/user-profile dài cả tuần. Triệu chứng: không merge được. Sửa: flag và PR ngắn.
  • Conventional commit cho có hình thức khi main không bisect được. Sửa: squash lúc merge (18.6).
  • main xanh, HTML cũ trên CDN. Triệu chứng: “git ổn”. Sửa: đó là delivery, không phải git.

Nhà tuyển dụng hay hỏi gì

GitFlow hay trunk-based? Nhiều deck vẫn vẽ GitFlow. Họ muốn nghe khi nào bạn dùng GitFlow, và “luôn releasable” cần gì ngoài git.

Trả lời khi phỏng vấn

Mình ship trunk-based: branch ngắn từ main, flag cho việc chưa xong, main được bảo vệ. Mình dùng GitFlow khi team mình thật sự giữ vài version đang live: mobile wrapper, on-prem, lịch release bị regulate. Không phải vì một deck đã vẽ. Mình không cãi chore hay ci trong review. Feature branch dài cả tuần là mùi. Hotfix lấy từ tag prod hoặc revert trên trunk. Luôn releasable cần CI gate và flag, không phải cảm giác. main xanh với HTML CDN cũ là bug delivery. Squash lúc merge là 18.6. Bản thân pipeline là 14.2.

Nhớ nhanh

  • Trunk-based cho Vue product mỗi ngày. GitFlow chỉ khi vài version đang live.
  • main chỉ releasable khi có CI gate và flag.
  • Hotfix từ tag prod, không từ develop thối.
  • Một main trong monorepo. Không admin force-push.

Câu hỏi nối tiếp thường gặp

  • Ship hotfix thế nào? Branch từ tag prod, hoặc revert trên trunk nếu prod là HEAD của main.
  • Monorepo: bao nhiêu main? Một. CODEOWNERS chia các package.
  • Khi nào GitFlow mới đáng dùng? Vài version đang live, on-prem, hoặc lịch release bị regulate.
  • Luôn releasable cần gì ngoài git? CI gate, flag, và CDN khớp digest.

Tóm tắt: Rebase branch sống ngắn nếu bạn là người push duy nhất. Không bao giờ rewrite lịch sử dùng chung. Marker biến rồi, chạy feature: semantic conflict không hiện <<<<<<. Push với --force-with-lease.

Vì sao quan trọng: “Rebase hay merge, và conflict bạn không thể cứ chọn một phía”. Họ muốn semantic conflict, --force-with-lease, và chạy path sau khi marker biến. Git resolve text, không phải hành vi. Khác 18.4 (workflow nào) và 18.6 (squash).

  1. Cách hoạt động: Rebase branch ngắn lên origin/main nếu chỉ bạn push. Merge main vào branch dùng chung. Sau conflict, chạy suite: hai PR có thể cùng thêm useUser, register /orders hai lần, hoặc ghi đè i18n key rồi vẫn merge sạch.
  2. --force-with-lease từ chối đè những commit trên remote bạn chưa thấy. Không bao giờ rebase commit đã nằm trên main. Không bao giờ rebase feature branch public mười người đang dùng. Không bao giờ force-push branch đã rebase khi reviewer đã checkout mà chưa báo trước.
  3. Conflict binary (ảnh, Figma): chọn một phía, đừng merge. Conflict lockfile: generate lại (pnpm install), đừng sửa tay pnpm-lock.yaml. Conflict khó thì hãy pair, đừng đoán mò bốn mươi phút.
git fetch origin
git rebase origin/main
# CONFLICT src/stores/user.ts: hai PR cùng thêm useUser
# Git resolve được text; không resolve hành vi
git add src/stores/user.ts
git rebase --continue
git push --force-with-lease origin HEAD
pnpm test && pnpm exec playwright test user-profile
# lockfile: pnpm install, commit lock đã generate lại, đừng merge tay

Điều gì xảy ra: Rebase phát lại commit của bạn lên main hiện tại. Hai helper useUser có thể merge sạch rồi crash lúc chạy. --force-with-lease chỉ cập nhật branch remote khi không ai khác đã push. Dòng cuối mới là test thật: click lại path.

Rebase branch của bạnMerge main vào
Lịch sửTuyến tínhThêm merge commit
Rủi roRewrite commit của bạnAn toàn hơn cho branch dùng chung
Push--force-with-leasePush thường
KhiBạn là người push duy nhấtMười người dùng chung branch

Góc nhìn senior

  • Ví dụ semantic conflict: auto-import trùng, route trùng, i18n ghi đè, hai field Pinia cùng compile.
  • rerere là nạng cho monster branch sống dai. Thà đừng có branch đó (18.4).
  • Cứ nhận incoming mù quáng là cách xóa feature bạn vừa build.
  • Kiểm chứng: tỷ lệ conflict (cao quá nghĩa là branch quá dài), regression sau những merge “đã resolve”.

Lỗi hay gặp

  • git push --force sau rebase. Triệu chứng: bạn đè commit của đồng đội. Sửa: --force-with-lease.
  • Dừng khi compiler xanh. Triệu chứng: hai useUser crash lúc chạy. Sửa: chạy lại path.
  • Sửa tay pnpm-lock.yaml. Triệu chứng: dependency ảo. Sửa: generate lại.
  • Rebase main hoặc branch dùng chung. Triệu chứng: lịch sử public bị rewrite. Sửa: không bao giờ.

Nhà tuyển dụng hay hỏi gì

Rebase hay merge, kèm một conflict không thể chỉ chọn một phía. Họ muốn --force-with-lease, một ví dụ semantic conflict, và “mình chạy lại feature sau khi marker biến mất”.

Trả lời khi phỏng vấn

Mình rebase branch ngắn với --force-with-lease khi chỉ mình push. Mình không bao giờ rewrite main. Marker biến rồi thì mình chạy test và click lại path. Hai field Pinia có thể merge sạch rồi vẫn crash. Conflict lockfile thì mình generate lại, không sửa tay. Branch dùng chung thì mình merge main vào. File binary: chọn một phía. Tỷ lệ conflict cao nghĩa là branch quá dài, đó là 18.4. Squash thì làm lúc merge PR, đó là 18.6.

Nhớ nhanh

  • --force-with-lease từ chối đè những commit bạn chưa thấy.
  • Git resolve text. Semantic conflict cần chạy lại feature.
  • Generate lại lockfile. File binary thì chọn một phía.
  • Không bao giờ rewrite main hoặc branch dùng chung.

Câu hỏi nối tiếp thường gặp

  • --force-with-lease là gì? Push thất bại nếu remote đã đổi kể từ lần fetch cuối.
  • Conflict binary, ảnh, Figma? Chọn một phía. Đừng merge blob.
  • Resolve conflict lockfile thế nào? Generate lại bằng package manager. Không bao giờ merge tay.
  • Vì sao merge sạch vẫn crash lúc chạy? Helper trùng, route trùng, hoặc i18n key không có marker.

Tóm tắt: Squash lúc PR merge để main bisect được: một commit cho mỗi thay đổi review được. Không bao giờ squash lịch sử public đã nằm trên main. Sửa bằng commit mới hoặc git revert. Đừng squash cả tuần việc không liên quan thành một cục.

Vì sao quan trọng: “Bạn có squash không?” Có phạm vi đúng: feature branch ồn khác lịch sử public đã trên main khác cả tuần việc không liên quan trong một commit. git bisect và git revert là tool production. Khác 18.4 (workflow) và 18.5 (conflict lúc rebase).

  1. Cách hoạt động: GitHub “Squash and merge” cho branch wip và “fix typo”. Title PR thành commit trên main và gắn số PR. Giữ vài commit logic riêng chỉ khi mỗi cái đều xanh và team muốn revert từng phần. Commit fixup lúc review vẫn để private (--fixup, rồi rebase lúc cuối).
  2. Khi đã lên main, lịch sử là audit log. GitOps thích kiểu này. Merge WIP 400 commit thì team khổ. Một squash cả tuần việc không liên quan cũng vậy. Rebase-merge chỉ khi author đã chọn lọc commit. Merge commit giữ topology và ồn hơn.
  3. Không bao giờ interactive-rebase main. Không bao giờ squash mất một revert. Không bao giờ rewrite commit đã review rồi force-push mà không có --force-with-lease. Changelog từ conventional commit sẽ sai khi mọi squash đều là feat:. Tách refactor lẫn lộn ra khỏi squash.
git rebase -i origin/main
# pick feat, squash wip, squash "fix typo"
git commit --amend -m "feat: csv export for orders (#842)"
# KHÔNG BAO GIỜ rewrite main:
#   git rebase -i main          # sai: lịch sử public
# khi đã lên main, lịch sử là audit log:
git revert abc123 --no-edit   # bug checkout; DS token là PR khác
git log --oneline origin/main | head

Điều gì xảy ra: Các commit wip ồn gộp thành một SHA review được trên main. Dòng revert chỉ chạy được vì checkout và token design-system là PR khác nhau. Nếu chúng nằm chung một squash, bạn không revert được một cái mà không kéo theo cái kia.

KiểuTrên mainKhi
Squash-mergeMột commit mỗi PRFeature branch ồn
Rebase-mergeCommit đã chọn lọc, phát lạiAuthor đã dọn lịch sử
Merge commitGiữ topology branchHouse style muốn đồ thị
Rewrite mainKhông bao giờSửa bằng revert hoặc commit mới

Góc nhìn senior

  • Bạn không revert sạch được một squash chứa ba feature. Tách PR.
  • GitHub thường giữ Co-authored-by lúc squash-merge. Hãy xác nhận nếu co-author quan trọng với DCO.
  • Signed commit và DCO là policy của org, không phải ý kiến về git workflow.
  • Kiểm chứng: bisect bug prod về một PR được không? Revert checkout mà không revert token DS được không?

Lỗi hay gặp

  • Squash main. Triệu chứng: audit log bị rewrite, GitOps rối. Sửa: revert hoặc thêm commit.
  • Một squash gồm checkout và token DS. Triệu chứng: không revert được một cái. Sửa: tách PR.
  • Interactive rebase lịch sử public. Triệu chứng: đồng đội lạc chỗ trên history. Sửa: không bao giờ.
  • Mọi squash mang title feat:. Triệu chứng: changelog nói dối. Sửa: khớp đúng loại thay đổi.

Nhà tuyển dụng hay hỏi gì

Bạn có squash không? Họ muốn nghe phạm vi: branch ồn thì có, main public thì không bao giờ, feature lẫn thì không. Rồi: revert một squash chứa ba feature thì sao?

Trả lời khi phỏng vấn

Mình squash PR ồn để main có một commit cho mỗi thay đổi review được. Mình không squash main. Nếu checkout và token design-system lên cùng lúc, mình không revert được một cái. Vì vậy mình tách PR. Lịch sử trên main là audit log; mình thêm commit hoặc revert. Rebase-merge chỉ khi mình đã chọn lọc commit. Merge commit nếu house muốn giữ đồ thị. Signed commit là policy của org. Trunk-based là 18.4. --force-with-lease sau rebase private là 18.5.

Nhớ nhanh

  • Squash lúc PR merge. Không bao giờ squash main.
  • Bạn không revert được một feature ra khỏi squash lẫn lộn. Tách PR.
  • main là audit log. Sửa bằng revert hoặc commit mới.
  • Title squash phải khớp loại thay đổi, không phải lúc nào cũng feat:.

Câu hỏi nối tiếp thường gặp

  • Revert squash merge chứa ba feature thế nào? Không sạch được. Lần sau tách; revert cả ba rồi đưa hai cái lên lại.
  • Signed commit / DCO? Policy của org. GitHub squash thường giữ co-author; hãy xác nhận.
  • Squash khác rebase-merge khác merge commit? PR ồn, lịch sử đã chọn lọc, topology. House style.
  • Khi nào giữ vài commit logic trong một PR? Mỗi cái đều xanh và team muốn revert từng phần.

Tóm tắt: User không login được và bạn đang on-call. Mitigate trước, debug sau: rollback digest, tắt flag, hoặc purge HTML lỗi. Rồi comms, evidence, và một fix hệ thống. Khác Q&A debug production (JSON có đúng không) và khác 19.5 (có gọi pager hay không).

Vì sao quan trọng: “User không login được. Bạn đang on-call. Làm đi”. Họ muốn mitigate trước: rollback, flag, purge HTML. Không phải soi Vue minify suốt bốn mươi phút. Thường thì bạn không SSH được vào CDN. Khác 17.7 (giọng cập nhật status) và 19.6 (các bước gạt flag chi tiết).

  1. Cách hoạt động: Xác nhận là thật: Sentry, RUM, và một lần repro. Check release, environment, flag, locale, browser. Rồi chọn đòn bẩy theo blast radius, không theo lý thuyết.
  2. 100% sau deploy: rollback digest GitOps. Khoảng 10% và có tag flag: đưa flag về 0% (19.6). Một region: CDN. Chỉ login: auth (18.1), không phải chart. Chunk 404 hoặc màn trắng: purge HTML. Đừng restart pod SSR vì lỗi index.html bị cache.
  3. Nói rõ đã biết gì, impact, lần cập nhật tiếp: có mốc giờ. Giữ event Sentry, SHA, và phần trăm flag trước khi rollback. Kiểm chứng bằng login canary và Sentry về baseline (để ý traffic). RCA để sau: map, timeline, một CI gate để lỗ này không mở lại. Đừng ship một fix đoán mò lên main lúc đang cháy.
  4. API 500 kèm toast frontend thì comms vẫn là của bạn nếu bạn phụ trách UI. Đừng giữ rollback frontend nếu JS ổn. “Chạy được trên staging” với SHA khác không phải evidence. Thiếu source map nghĩa là bạn đang đoán (19.1).
  5. Thực hành: một buổi game day làm sập service flag hoặc phá chunk hash. Thời gian mitigate và phần trăm user hồi (RUM) mới là điểm, không phải slide RCA đẹp thế nào.
Phạm viĐòn bẩyKhông
100% sau deployRevert digestDebug Vue minify trước
~10% + tag flagFlag về 0% (19.6)Tăng 50% để lấy tín hiệu
Chunk 404 / màn trắngPurge HTMLRestart pod Nitro
Chỉ loginPath auth (18.1)Rollback chart
API 500, JS ổnComms; đừng rollback FEVẫn rollback digest

Góc nhìn senior

  • Isolation (JSON có đúng không) là Q&A debug production. Topic này là lịch xử lý sau khi đã biết user đang cháy.
  • Có đánh thức ai không là 19.5. Lời bạn nói trên kênh là 17.7.
  • Giữ release, phần trăm flag, và một sample event trước khi rollback. Không thì RCA không có evidence.
  • Kiểm chứng: thời gian mitigate, RUM đã hồi, và sau đó đã có test, budget, hay alert chưa.

Lỗi hay gặp

  • Soi Vue minify khi user đang cháy. Triệu chứng: bốn mươi phút, vẫn down. Sửa: digest, flag, hoặc purge trước.
  • Restart Nitro vì HTML trên CDN. Triệu chứng: pod flap, HTML vẫn vậy. Sửa: purge HTML.
  • Ship fix đoán mò lên main lúc đang cháy. Triệu chứng: incident thứ hai. Sửa: mitigate, rồi mới PR đàng hoàng.
  • Đổ lỗi backend khi không có HAR. Triệu chứng: bạn đoán. Sửa: playbook isolation trong Q&A debug.

Nhà tuyển dụng hay hỏi gì

User không login được. Bạn đang on-call. Làm đi. Họ muốn mitigate trước: rollback, flag, purge. Không phải soi Vue minify, cũng không phải phiên tòa FE kiện BE từ Q&A debug.

Trả lời khi phỏng vấn

Mình check release, flag, và một lần repro, rồi mới mitigate. Một trăm phần trăm sau deploy thì revert digest. Mười phần trăm thì gạt flag. Chunk 404 thì purge HTML. Mình không debug Vue minify khi user đang cháy. Mình nói theo mốc giờ, giữ event Sentry, và để lại một CI gate để đúng lỗ này không mở lại. Mình không restart Nitro vì HTML trên CDN. API 500 mà bundle JS ổn thì là comms, không phải rollback frontend. Isolation (JSON có đúng không) là câu khác. Có page không là 19.5. Lời trên kênh là 17.7. Các bước gạt flag là 19.6.

Nhớ nhanh

  • Mitigate trước: digest, flag, hoặc purge HTML. Debug sau.
  • Giữ SHA, phần trăm flag, và một sample event trước khi rollback.
  • Đừng restart Nitro vì HTML trên CDN.
  • Isolation là Q&A debug. Topic này là lịch xử lý incident.

Câu hỏi nối tiếp thường gặp

  • Cái gì thì page, cái gì thì ticket? 19.5: triệu chứng trên critical path. Topic này giả định bạn đã bị page.
  • Luyện thế nào? Game day: làm sập service flag hoặc phá chunk hash. Chấm thời gian mitigate.
  • Rollback, revert đúng một PR, hay flag, chọn thế nào? Blast radius: 100%, PR khoanh vùng, hoặc flag 10%.
  • API 500 kèm toast FE, bạn rollback frontend không? Không nếu JS ổn. Bạn vẫn lo comms nếu bạn phụ trách UI.

Tóm tắt: Tuần 1 của một Nuxt monolith là bản đồ trong đầu và một contribution đúng kiểu production trước Friday. Chạy được, lần một path, tìm chỗ ghép, ship một ticket nhỏ kèm test. Không phải một tuần im lặng đọc code, cũng không phải rewrite folder.

Vì sao quan trọng: “Thứ Hai bạn vào một Nuxt monolith. Bạn làm gì?” Họ muốn một bản đồ và một ticket thật, không phải vẻ chắc chắn giả. Không ai có architecture doc còn đúng. Auto-import giấu dependency. Câu ước lượng Friday (17.3) muốn một list rủi ro.

  1. Cách hoạt động: Ngày 1: clone, build prod-mode, test; ai on-call, Sentry ở đâu, deploy thế nào; lướt ADR về auth và rendering. Ngày 2: lần login và một core path trong DevTools (network, Vue, Pinia).
  2. Ngày 3: map feature khác shared, tìm design system, chạy bundle analyzer một lần. Ngày 4–5: một ticket thật nhỏ trên path đó, kèm test. 1:1: hỏi chỗ chết người (hydration, module federation, e2e flaky). Pair deploy và auth; xen với lúc explore một mình.
  3. Access staging chậm: dùng preview và Storybook, nhưng nhất quyết phải có RUM prod trước tuần 2. Đừng giả định pattern Nuxt 3 trong app Nuxt 2 kèm Vuex. Dev trên mock giấu BFF là bẫy. Output tuần 1 là list rủi ro, không phải rewrite architecture.
KhiLàmTừ chối
Ngày 1Build prod, test, on-call, Sentry, deploy, ADR authReformat 200 file
Ngày 2Lần login + một core pathThư viện state mới
Ngày 3Feature vs shared, DS, analyzer một lầnCải thiện build
Ngày 4–5Ticket nhỏ + test; ghi “thứ có thể hại mình”Rewrite folder

Góc nhìn senior

  • 30/60/90 đầu: phụ trách một feature, vào on-call, viết một ADR. Tuần 1 chỉ là bản đồ và một ticket.
  • Review trước khi biết domain: invariant kiểu 18.1, a11y, test. Hỏi câu domain thành tiếng.
  • Pair deploy và auth. Leverage cao nhất. Xen với lúc explore một mình để vẫn dựng được bản đồ.
  • Kiểm chứng: bạn đã ship gì đó sau một flag, hoặc bugfix kèm test. Bạn vẽ được các hộp. Bạn biết ai phụ trách DS, ai phụ trách checkout.

Lỗi hay gặp

  • Im một tuần “đọc”. Triệu chứng: Friday bạn không vẽ được hộp. Sửa: lần một path ngày 2.
  • Rewrite folder structure. Triệu chứng: bạn tốn vốn chính trị. Sửa: một ticket nhỏ.
  • Lint 200 file tiện tay, hoặc refactor Pinia vì nhớ Vuex. Triệu chứng: nhiễu. Sửa: từ chối.
  • Bỏ env giống prod (flag, SSR). Triệu chứng: ticket ngày 5 nói dối. Sửa: nhất quyết phải có flag và build prod.

Nhà tuyển dụng hay hỏi gì

Thứ Hai bạn vào một Nuxt monolith. Bạn làm gì? Họ muốn một bản đồ trong đầu và một contribution đúng kiểu production trước Friday: không phải một tuần im, không phải rewrite folder.

Trả lời khi phỏng vấn

Tuần 1 mình chạy build prod, lần login, và tìm auth, BFF, flag, và error. Giữa tuần mình ship một ticket nhỏ kèm test trên path đó. Friday mình giải thích được request thành UI thế nào và ship thế nào, kèm một list rủi ro. Không phải vẻ chắc chắn giả. Mình không reformat 200 file hay kéo vào thư viện state mới ngày 2. Nếu access staging chậm mình dùng preview, nhưng nhất quyết có RUM prod trước tuần 2. Mình không giả định Nuxt 3 trong app Vuex. 30/60/90 đầu là phụ trách một feature, vào on-call, viết một ADR. Ước lượng kèm list rủi ro là 17.3.

Nhớ nhanh

  • Output tuần 1 là list rủi ro và một ticket, không phải rewrite architecture.
  • Pair deploy và auth. Leverage cao nhất.
  • Ghi “không map, không flag, global bus”.
  • Đừng giả định pattern Nuxt 3 trong app Nuxt 2 kèm Vuex.

Câu hỏi nối tiếp thường gặp

  • 30/60/90 đầu? Chịu trách nhiệm một feature, vào on-call, viết một ADR. Tuần 1 chỉ là bản đồ kèm một ticket.
  • Chưa biết domain thì review code thế nào? Invariant, a11y, test. Hỏi câu domain thành tiếng.
  • Tuần 1 bạn từ chối làm gì? Rewrite folder, thư viện state mới, lint tiện tay 200 file.
  • Access staging chậm thì lấy RUM prod ra sao? Preview trước; nhất quyết phải có RUM prod chậm nhất tuần 2.