DS ForgeCourses
기존 Lab

BROWSER → RUNTIME → REACT

Frontend,껍데기 제거

API 암기를 멈추고 클릭에서 픽셀까지 추적한다.

JavaScript 실행 컨텍스트, 브라우저 파이프라인, TypeScript의 소거, React render/commit, 네트워크 경쟁과 운영 장애를 하나의 실행 흐름으로 연결한 실전 교재이자 애플리케이션입니다.

01JavaScriptstack · heap · jobs
02Browsernetwork · DOM · pixels
03TypeScriptcompile-time only
04Reactrender · commit · effects

END-TO-END TRACE

한 번의 클릭은 어디까지 가는가

각 단계에서 무엇이 실행되고, 무엇이 아직 실행되지 않았는지 구분한다.

  1. 01CLICKinput event → task
  2. 02HANDLERstate update queued
  3. 03RENDERpure calculation
  4. 04COMMITDOM mutation
  5. 05BROWSERstyle → layout → paint
  6. 06PIXELScompositor presents

EVIDENCE, NOT VIBES

근거 등급

SPEC
표준·공식 API 계약
VERIFIED
이 프로젝트에서 재현한 동작
PRACTICE
상황에 따라 달라지는 실무 권고
POLICY
이 실습에서 강제하는 선택

LIBRARY ≠ FRAMEWORK ≠ BUILD TOOL

선택한 실행 구조

React의 실행 모델을 가리지 않으면서 server/client 경계와 비공개 hosting을 실제로 검증하기 위해 React 19 + Vinext의 App Router 호환 계층 + Vite 8을 선택했다. 순수 Vite SPA보다 전달 경계를 더 많이 실습하고, 범용 Next 배포보다 adapter 중복을 줄이는 선택이다.

React, framework, build tool, hosting 책임 분리
LAYEROWNSDOES NOT OWN
Reactcomponent · state · render · commitroutes · bundles · deployment
Vinext / App Routerserver/client graph · HTML · metadatahook semantics · browser paint
Vitetransform · module graph · bundle · HMRruntime type validation
Sites Workerrequest · headers · private deliveryclient state · authorization UI

CURRICULUM / 00—11

추상화를 한 겹씩 벗긴다

긴 모듈은 접을 수 있습니다.

P00JavaScript 실행 모델선언 생성부터 event loop까지, 코드 한 줄이 실행되는 실제 순서145
OUTCOME

학습자는 scope·closure·this·Promise 순서를 명세 용어로 추적하고, stale closure와 취소되지 않은 비동기 작업을 재현하고 고친다.

parsingexecution contextlexical environmentcall stackheapclosurescopedeclaration instantiationhoistingprototypethisECMAScript moduletaskmicrotaskPromisetimerevent loopAbortControllerunhandled rejection

1. 실체와 오해

SPEC

JavaScript는 ‘위에서 아래로 한 줄씩’만 실행되는 언어가 아니다. 소스는 먼저 문법 목표에 따라 parse되고, Script 또는 Module의 선언을 환경 레코드에 생성하는 과정 뒤에 문장이 평가된다. 흔히 말하는 hoisting은 선언문이 텍스트 위로 이동하는 현상이 아니다. var 바인딩은 선언 인스턴스화 중 생성·undefined로 초기화되고, let/const/class 바인딩은 생성되지만 선언 평가 전에는 초기화되지 않아 TDZ에 있다. 함수 선언은 해당 인스턴스화 알고리즘에서 함수 객체로 초기화된다. 또한 ECMAScript는 Promise Job을 정의하지만, click·timer·network·render의 스케줄링은 브라우저 호스트인 HTML 명세가 연결한다.

2. 선수 지식

POLICY

브라우저 개발자 도구의 Console과 Sources 패널, const/let, 함수 선언과 호출, 객체·배열, import/export, try/catch를 사용할 수 있어야 한다. 실습 전 ‘언어’와 ‘호스트’를 분리한다. Array와 Promise는 ECMAScript가 정의하지만 document, fetch, setTimeout, AbortController는 웹 플랫폼이 제공하거나 통합한다. 이 모듈에서 stack과 heap은 관찰에 유용한 구현 모델이며, ECMAScript가 강제하는 물리 메모리 배치라고 주장하지 않는다.

3. 15분 개념

SPEC

15분 모델은 네 단계다. ① parse 단계에서 문법 오류와 일부 early error를 찾는다. ② Script/Module/Function/Block 선언 인스턴스화가 Environment Record에 바인딩을 만든다. ③ 실행 중 identifier는 현재 LexicalEnvironment에서 outer reference를 따라 해석되고, 호출은 새 execution context를 stack 위에 둔다. ④ 현재 task의 동기 코드가 끝나면 브라우저가 microtask checkpoint를 수행하고, 이후 적절한 시점에 렌더링하거나 다른 task를 고른다. closure는 함수 코드와 외부 lexical environment에 대한 참조가 함께 살아 있는 결과다. 객체의 속성 탐색은 own property 뒤 [[Prototype]] 체인을 따르며, lexical scope 탐색과 다른 축이다.

4. browser/runtime 내부

VERIFIED

Execution context에는 실행 상태와 Realm, ScriptOrModule이 있고 ECMAScript 코드 문맥에는 LexicalEnvironment·VariableEnvironment·PrivateEnvironment가 더 있다. context stack의 맨 위가 실행 중인 문맥이지만, 이 구조는 명세 장치이며 DevTools 프레임과 일대일 대응을 보장하지 않는다. 엔진은 보통 활성 호출 프레임을 stack 계열 메모리에, 도달 가능한 객체와 closure가 붙든 환경을 heap 계열 메모리에 관리하고 tracing GC로 회수한다. 일반 함수의 this는 호출 형태와 strict mode에 따라 정해지고, arrow function은 자신만의 this binding을 만들지 않는다. prototype은 상속/속성 위임이고 scope는 이름 해석이므로 서로 대체할 수 없다.

5. 최소 실행 코드

VERIFIED

아래 파일을 module script로 실행하면 동기 로그 뒤 Promise reaction과 queueMicrotask가 등록 순서대로 실행되고, timer task가 나중에 실행된다. makeCounter가 반환된 뒤에도 count 환경이 closure를 통해 도달 가능하다. method 호출의 this는 counter 객체지만, detached 호출은 module의 strict mode에서 undefined다. 이 예제는 순서를 관찰하는 코드이지 모든 task source 사이의 전역 FIFO를 주장하지 않는다.

execution-order.ts
function makeCounter(start = 0) {
  let count = start;
  return () => ++count;
}

const next = makeCounter(40);
const counter = {
  value: 2,
  read(this: { value: number }) {
    return this.value;
  },
};

console.log("sync", next(), counter.read());
Promise.resolve().then(() => console.log("promise", next()));
queueMicrotask(() => console.log("microtask", next()));
setTimeout(() => console.log("timer", next()), 0);
console.log("sync end");

// sync 41 2 -> sync end -> promise 42 -> microtask 43 -> timer 44

6. production 코드

PRACTICE

비동기 작업의 소유권을 함수 경계에 드러낸다. caller가 AbortSignal을 전달할 수 있게 하고, 응답이 HTTP 성공인지 확인하며, unknown 입력을 검증한 뒤 반환한다. timeout과 사용자 취소는 동일하지 않으므로 reason을 보존하고 UI에서 구분한다. Promise를 일부러 fire-and-forget할 때도 void 표기와 마지막 catch로 rejection의 책임자를 명확히 한다. 전역 unhandledrejection listener는 관측의 최후 방어선이지 정상 오류 처리 경로가 아니다.

load-status.ts
type Status = { id: string; state: "ok" | "degraded" };

function isStatus(value: unknown): value is Status {
  if (typeof value !== "object" || value === null) return false;
  const record = value as Record<string, unknown>;
  return (
    typeof record.id === "string" &&
    (record.state === "ok" || record.state === "degraded")
  );
}

export async function loadStatus(signal: AbortSignal): Promise<Status> {
  const response = await fetch("/api/status", { signal });
  if (!response.ok) throw new Error("HTTP " + response.status);
  const body: unknown = await response.json();
  if (!isStatus(body)) throw new TypeError("Invalid status payload");
  return body;
}

export function reportInBackground(signal: AbortSignal): void {
  void loadStatus(signal).catch((error: unknown) => {
    if (!signal.aborted) console.error("status report failed", error);
  });
}

7. 코드 해부

SPEC

ES module은 단순히 여러 파일을 합친 것이 아니다. 모듈은 자체 lexical environment와 strict semantics를 가지며, import는 복사본이 아니라 export binding에 대한 live binding이다. 로딩은 의존 그래프를 parse하고 연결(link/instantiate)한 뒤 평가하며, 순환 그래프와 top-level await는 평가 순서에 영향을 준다. 함수 호출에서는 매개변수와 지역 선언을 위한 환경이 생기고 return 시 실행 context는 빠지지만, 반환된 함수가 그 환경의 binding을 참조하면 GC root에서 이어지는 경로가 남아 closure 상태가 유지된다. call/apply/bind와 method syntax는 this를 바꾸지만 arrow function의 lexical this는 바꾸지 못한다.

8. click/network/render end-to-end trace

VERIFIED

사용자가 button을 활성화하면 HTML event loop의 user-interaction task에서 click이 dispatch된다. handler 호출 context가 stack에 올라 동기 검증과 state 변경을 수행하고 fetch를 시작한다. fetch의 네트워크 알고리즘 일부는 병렬로 진행되며 현재 handler는 끝난다. task 종료 지점의 microtask checkpoint가 이미 queue된 Promise reaction을 비운 뒤 브라우저는 render opportunity를 선택할 수 있다. 응답이 사용 가능해지면 Fetch/HTML 통합이 Promise를 resolve하도록 일을 queue하고, then/await continuation은 Promise job, 즉 microtask로 실행된다. DOM을 바꾸면 style/layout/paint 대상이 될 수 있지만 microtask 자체가 곧 paint라는 뜻은 아니다.

9. 실패 주입

VERIFIED

실패 주입은 stale closure다. click 시점의 count를 캡처한 timer를 연속 세 번 만들고, 그 사이 count를 갱신한다. 각 callback은 생성된 render/호출의 오래된 binding 값을 읽으므로 ‘최신 변수’를 자동으로 보지 않는다. 순수 JavaScript에서는 최신 값을 보관한 명시적 객체/ref를 읽거나 callback 인자로 snapshot을 전달한다. React state 갱신이라면 이전 값을 받는 updater를 사용한다. lab은 timer를 취소하지 않은 버전도 제공해 unmount 이후 작업과 메모리 보존을 함께 관찰한다.

stale-closure장애 실습에서 재현 →

10. 테스트

PRACTICE

순서 테스트는 관찰하려는 경계를 좁힌다. 동기 로그, queueMicrotask/Promise reaction, 단일 timer의 상대 순서는 테스트할 수 있지만 서로 다른 task source의 전체 순서를 고정하지 않는다. fake timer는 timer API를 제어할 뿐 실제 브라우저 render opportunity, network stack, microtask starvation을 완전히 재현하지 않는다. 따라서 작은 unit test로 closure와 abort 분기를 검증하고, 실제 브라우저 E2E에서 click→await→DOM 결과와 pageerror/unhandledrejection 부재를 검증한다.

11. 성능

VERIFIED

Promise 자체가 ‘background thread’를 만들지 않는다. 긴 동기 loop는 현재 task와 main thread를 점유하고, 재귀적으로 계속 queue되는 microtask는 다음 task와 렌더링 기회를 지연시킬 수 있다. closure가 큰 객체를 참조하면 필요한 작은 값만 쓰더라도 환경 경로 때문에 객체가 계속 도달 가능할 수 있다. Performance 패널에서 long task와 allocation/retainer path를 측정한 뒤, CPU 작업은 chunking하거나 Web Worker로 옮기고 불필요한 참조·listener·timer를 cleanup한다. 최적화 판단은 heap snapshot과 trace 전후 차이로 한다.

12. 접근성

PRACTICE

실행 모델은 접근성에도 직접 영향을 준다. div click handler는 기본 keyboard activation과 button semantics를 만들지 않으므로 실제 button을 사용한다. 긴 task는 키 입력·focus 표시·screen reader용 live-region 갱신도 막는다. 비동기 결과는 focus를 임의로 이동시키지 말고 상태 메시지를 aria-live 영역에 짧게 알리며, 취소 버튼은 AbortController에 연결한다. 키보드로 시작한 작업이 timer나 rejection 때문에 조용히 사라지지 않는지 실제 screen reader와 브라우저로 확인한다.

13. 보안

PRACTICE

Prototype과 closure는 보안 경계가 아니다. 신뢰할 수 없는 key를 일반 객체에 병합하면 __proto__ 같은 이름을 통해 prototype pollution 계열 취약점이 생길 수 있으므로 허용 필드를 선택하고 Object.hasOwn으로 own property를 검사한다. module scope에 둔 token도 같은 realm의 XSS 코드가 호출 경로를 장악하면 안전하지 않다. rejection에 response body·Authorization 값·개인정보를 그대로 로그하지 않는다. CSP는 주입 피해를 줄이는 defense-in-depth이고 안전한 DOM sink와 입력 검증을 대신하지 않는다.

14. 운영

POLICY

운영 build는 error와 unhandledrejection을 수집하되 사용자·release·route·correlation id처럼 제한된 문맥만 보내고 비밀값은 redaction한다. source map은 오류 위치 복원에 사용하되 공개 제공 여부와 모니터링 서비스 upload를 배포 정책으로 관리한다. abort는 정상 제어 흐름으로 분류하고, timeout·offline·HTTP failure·schema failure를 같은 ‘network error’로 뭉개지 않는다. dashboard에는 rejection 수뿐 아니라 task duration과 사용자 동작에서 첫 상태 갱신까지의 지연도 함께 본다.

15. 완료 조건

POLICY

완료 조건: (1) 선언 인스턴스화와 평가를 분리해 var, let/const, 함수 선언의 초기 상태를 설명한다. (2) scope chain과 prototype chain, 일반 함수 this와 arrow this를 예제로 구분한다. (3) click task, Promise microtask, timer task, render opportunity의 trace를 그린다. (4) stale closure를 재현하고 의도적 snapshot·최신 holder·state updater 중 맞는 수정을 선택한다. (5) AbortSignal이 끝까지 전달되고 처리되지 않은 rejection이 0인 실제 브라우저 테스트를 통과한다. 이 조건은 기능 이해를 확인하지만 실제 학습 전이는 학습자 평가 전까지 unverified다.

16. 자가시험

POLICY

두 문항을 먼저 근거와 함께 답한 뒤 정답을 확인한다. 답에는 ‘hoisting 때문에’나 ‘event loop가 알아서’ 같은 축약어 대신 binding 생성·초기화 시점과 task/microtask 경계를 적는다. 오답이면 최소 코드의 console 순서를 직접 예측하고 브라우저에서 다시 실행한다.

2문제 중 0문제 응답

1. let 바인딩이 선언문 이전에 ReferenceError를 내는 가장 정확한 이유는?
2. 같은 task에서 Promise.then과 setTimeout(..., 0)을 이 순서로 등록하면 일반적으로 무엇이 먼저 실행되는가?
P01BrowserURL 입력에서 pixels까지: network, parsing, rendering, storage와 보안 경계170
OUTCOME

학습자는 navigation과 렌더링 pipeline을 trace하고 main-thread long task를 Worker/작업 분할로 완화하며 CORS·CSP·cookie 경계를 설명한다.

URLDNSTCPTLSHTTPHTTP cacheHTML parsingDOMCSSOMrender treestyle calculationlayoutpaintcompositemain threadcompositor threadWeb WorkercookiestorageCORSCSP

1. 실체와 오해

VERIFIED

‘URL→DNS→TCP→TLS→HTTP→DOM→CSSOM→render tree→layout→paint’는 좋은 출발점이지만 매 navigation의 고정 직렬 순서는 아니다. DNS·연결·TLS session·HTTP response는 cache되거나 재사용될 수 있고, HTTP/3는 TCP 대신 QUIC 위에서 동작한다. HTML은 streaming parse될 수 있으며 preload scanner와 subresource fetch가 겹친다. style, layout, paint는 변경 종류와 브라우저 구현에 따라 생략·부분 수행될 수 있고 compositor-only update도 있다. 따라서 이 모듈은 표준이 정한 관찰 결과와 Chromium의 검증된 구현 설명을 분리한다.

2. 선수 지식

POLICY

DevTools Network·Performance·Application 패널, request/response header, status code, origin(scheme/host/port), DOM tree와 기본 CSS box model을 알고 있어야 한다. curl만으로 browser 동작을 검증하지 않는다. curl에는 HTML parser, same-origin policy, preflight cache, rendering pipeline이 없기 때문이다. 실습에서는 cold load와 warm load, disable-cache 여부, CPU/network throttling 설정을 trace와 함께 기록한다.

3. 15분 개념

SPEC

15분 navigation 모델: ① 입력을 URL로 해석하고 navigation을 시작한다. ② cache/서비스 계층을 확인하고 필요하면 DNS로 이름을 주소에 매핑한다. HTTP/1.1·2의 HTTPS는 보통 TCP 연결 뒤 TLS handshake를 하고, HTTP/3는 QUIC이 transport와 TLS 1.3 handshake를 통합한다. ③ HTTP request/response와 redirect·cache 정책을 처리한다. ④ 받은 HTML bytes를 encoding에 따라 문자·token으로 만들고 tree-construction 알고리즘으로 DOM을 만든다. ⑤ CSS를 parse해 style 규칙을 만들고 computed style과 box geometry를 계산한다. ⑥ paint order/display list와 raster 결과를 layer로 composite해 frame을 표시한다.

4. browser/runtime 내부

VERIFIED

Chromium renderer의 main thread는 HTML/CSS parsing, JavaScript, style/layout, event dispatch와 document lifecycle의 많은 부분을 담당한다. compositor thread는 input, scrolling, layerization과 raster coordination을 맡아 일부 transform/opacity animation과 scroll을 main thread 없이 새 frame으로 합성할 수 있다. 이 thread 배치는 웹 표준 API contract가 아닌 Chromium 구현이다. Web Worker는 별도 agent/event loop에서 JavaScript를 실행하며 DOM에 직접 접근하지 못하고 message/structured clone 또는 명시적 transferable로 통신한다. CPU 병렬화 수단이지 network fetch를 ‘비동기로 만들기 위한’ 필수 도구가 아니다.

5. 최소 실행 코드

VERIFIED

아래 코드는 main thread의 입력 지연을 피하면서 배열 합계를 Worker에 계산시킨다. new URL(..., import.meta.url) 형태는 bundler가 worker asset을 추적할 수 있고 macOS/Windows 경로 문자열을 hardcode하지 않는다. Worker에 큰 ArrayBuffer를 보낼 때 transfer list를 사용하면 복사 비용을 피하는 대신 sender의 buffer가 detached된다는 소유권 변경을 받아들여야 한다.

sum-worker.ts
// sum-worker.ts
self.onmessage = (event: MessageEvent<ArrayBuffer>) => {
  const values = new Float64Array(event.data);
  let sum = 0;
  for (const value of values) sum += value;
  self.postMessage(sum);
};

// main.ts
const worker = new Worker(new URL("./sum-worker.ts", import.meta.url), {
  type: "module",
});
const values = new Float64Array([1, 2, 3, 4]);
worker.onmessage = (event: MessageEvent<number>) => {
  document.querySelector("output")!.textContent = String(event.data);
  worker.terminate();
};
worker.postMessage(values.buffer, [values.buffer]);

6. production 코드

PRACTICE

문서 shell에는 critical heading·navigation·loading 상태를 semantic HTML로 먼저 제공한다. CSS는 render-blocking 비용을 측정하고 critical path를 줄이되 무조건 inline하지 않는다. script는 ES module로 명시하고 code split chunk의 waterfall을 확인한다. 정적 asset에는 content hash와 긴 immutable cache를, HTML/API에는 변경 특성에 맞는 Cache-Control·ETag를 사용한다. Worker에는 message schema, error handler, terminate lifecycle을 둔다. Image와 font 크기를 예약하고 width/height 또는 aspect-ratio로 초기 layout을 안정화한다.

7. 코드 해부

SPEC

DOM은 document의 node/tree와 조작 API다. CSSOM은 stylesheet rule과 computed style을 다루는 객체 모델이며 DOM과 동일하지 않다. ‘render tree’는 교육·구현 문서에서 유용하지만 WHATWG의 단일 공용 객체 API가 아니다. CSS visual formatting model은 어떤 element가 box를 만들고 box가 어떻게 배치되는지를 규정하고, paint order는 stacking context를 따른다. display:none node는 DOM에 남아도 formatting box를 만들지 않는다. visibility:hidden은 geometry를 남길 수 있다. transform은 이미 rasterized layer의 composite만 유발할 수 있지만 layer 승격과 GPU memory 비용은 측정 대상이다.

8. click/network/render end-to-end trace

VERIFIED

주소창 Enter부터 첫 frame까지 trace한다. browser UI가 navigation을 만들고 network service가 cache/connection 정보를 확인한다. 연결이 필요하면 DNS와 선택된 HTTP protocol의 transport/TLS를 거친다. redirect마다 URL·origin·cookie 전송 조건이 다시 평가된다. response headers로 MIME, CSP, cache, Set-Cookie가 처리되고 renderer가 HTML stream을 parse한다. parser-blocking classic script는 parsing을 멈출 수 있지만 module/defer의 실행 규칙은 다르다. DOM/CSS 변경 뒤 style→layout→pre-paint/paint→raster→composite 경로가 나타나며, LCP 같은 milestone과 load 이벤트는 ‘화면이 완전히 끝났다’는 같은 시점이 아니다.

9. 실패 주입

VERIFIED

실패 주입은 main-thread long task다. 50–200ms CPU loop를 click handler 안에서 실행해 button의 pressed/pending feedback, keyboard input, timer와 paint가 지연되는 것을 Performance trace로 기록한다. 같은 계산을 작은 chunk로 나누어 task 사이에 양보하는 버전과 Worker+transferable 버전으로 고친다. Worker 생성·message serialization 비용 때문에 작은 작업은 오히려 느릴 수 있으므로 total duration뿐 아니라 interaction latency와 main-thread blocking을 함께 비교한다.

main-thread-long-task장애 실습에서 재현 →

10. 테스트

PRACTICE

Browser 기능은 실제 browser에서 검증한다. E2E는 cold navigation의 request chain, cache header, worker 결과와 console/page error를 확인한다. Playwright route mocking은 application 분기를 재현하는 데 유용하지만 DNS, TCP/TLS, 실제 CDN cache, CORS server policy와 bandwidth behavior를 대체하지 않는다. CORS preflight는 허용/거부 서버를 실제 origin이 다른 test server로 띄워 검증하고, CSP는 report-only와 enforce 모드를 나눠 inline script fixture가 실제 차단되는지 확인한다.

11. 성능

PRACTICE

Network waterfall과 main-thread flame chart를 같은 trace에서 읽는다. preload/preconnect를 무분별하게 늘리면 bandwidth·connection 경쟁과 unused resource 비용이 생긴다. forced synchronous layout은 DOM write 뒤 geometry read가 style/layout을 즉시 요구할 때 생길 수 있으므로 read/write를 batch한다. transform/opacity 중심 animation도 과도한 layer와 큰 raster surface를 만들 수 있다. cold/warm, 320px mobile viewport, CPU slowdown 조건을 고정하고 navigation timing·resource timing·long task·layout shift를 전후 비교한다.

12. 접근성

PRACTICE

DOM source order는 keyboard focus order와 accessibility tree의 중요한 출발점이다. CSS visual order만 바꾸면 보이는 순서와 읽기/탭 순서가 달라질 수 있다. 초기 HTML에 올바른 language, title, landmark, heading과 skip link를 제공해 script 실패 전에도 탐색 가능하게 한다. loading 중에도 focus indicator와 native control을 유지하고 skeleton이 접근성 tree를 오염시키지 않게 숨긴다. prefers-reduced-motion에서는 필수 정보는 남기고 비필수 scroll/transform animation을 제거한다.

13. 보안

SPEC

Cookie는 request 조건에 맞으면 browser가 HTTP header로 보내는 상태이고 localStorage/sessionStorage는 origin별 script API다. HttpOnly cookie는 script 접근을 막지만 CSRF를 자동 해결하지 않고, Secure는 HTTPS 전송 조건이며 SameSite 정책도 threat model에 맞춰야 한다. CORS는 서버가 cross-origin response를 script에 노출할지 결정하는 Fetch protocol이지 요청 자체나 CSRF 방어를 보장하지 않는다. CSP는 허용 resource/실행 정책으로 XSS 피해를 줄이는 defense-in-depth다. 중요한 session은 짧은 수명 Secure·HttpOnly cookie와 서버 측 CSRF 방어를 우선 검토하고 민감 token을 Web Storage에 기본 저장하지 않는다.

14. 운영

POLICY

배포 전 HTML·JS·CSS의 Cache-Control과 content hash 조합, CSP response header, frame-ancestors, MIME/nosniff, cookie attributes를 실제 production response에서 검사한다. CDN invalidate와 rollback 시 HTML이 오래된 chunk 이름을 가리키는 split-brain을 막도록 asset은 immutable하게 남기고 HTML cache를 짧게 운영한다. synthetic navigation과 real-user telemetry를 분리하며 browser/version·device class·network 정보 없이 평균값만 보지 않는다. Worker error와 CSP violation report도 release id와 연결하되 개인정보는 수집하지 않는다.

15. 완료 조건

POLICY

완료 조건: (1) HTTP/1.1·2의 TCP/TLS 경로와 HTTP/3 QUIC 경로를 구분한다. (2) DOM, CSSOM, formatting/render representation, style/layout/paint/composite를 trace에서 가리킨다. (3) main/compositor/worker 역할을 표준 보장과 Chromium 구현으로 라벨링한다. (4) long task 수정 전후 interaction latency를 측정한다. (5) 실제 두 origin에서 CORS와 CSP 차단, cookie/storage scope를 browser E2E로 검증한다. 기능과 trace 품질은 확인할 수 있지만 실제 사용자 체감 개선은 field 측정 전까지 unverified다.

16. 자가시험

POLICY

두 문항은 pipeline 암기가 아니라 생략·병렬화·보안 경계를 설명하는지 확인한다. 정답 뒤에는 자신의 cold-load Performance trace에서 해당 사건을 찾아 screenshot과 URL을 남긴다. 특정 Chrome thread 이름을 모든 browser의 명세 보장처럼 답하면 오답으로 처리한다.

2문제 중 0문제 응답

1. HTTPS navigation이 항상 DNS→TCP→TLS를 새로 순서대로 수행한다는 설명이 틀린 이유는?
2. CORS가 보장하는 것은 무엇인가?
P02TypeScriptcompile-time 증명과 runtime 검증의 경계를 코드로 고정하기165
OUTCOME

학습자는 구조적 타입과 narrowing을 이용해 상태를 모델링하고, network 경계의 unknown을 runtime에서 검증하며 assertion·generated type의 거짓 안전을 제거한다.

structural typinginferenceunionintersectionnarrowingdiscriminated uniongenericconditional typemapped typevarianceunknownneveranytype assertiondeclaration filecompile-time typeruntime valueAPI response runtime validationgenerated type limitations

1. 실체와 오해

SPEC

TypeScript type은 배포된 값에 붙어 다니는 runtime guard가 아니다. 대부분의 annotation, interface, type alias, generic argument와 assertion은 JavaScript emit에서 사라진다. const payload = await response.json() as User는 server 응답을 검사하지 않고 compiler에게 믿으라고 지시할 뿐이다. 반대로 enum처럼 JavaScript를 생성하는 일부 TypeScript 기능도 있으므로 ‘모든 TS 문법이 지워진다’고 일반화해서도 안 된다. 이 모듈의 핵심 경계는 내부의 정적 증명과 외부 I/O에서 들어오는 unknown 값 사이에 runtime parser를 두는 것이다.

2. 선수 지식

POLICY

JavaScript 객체·함수·array method, ES module, Promise/fetch와 HTTP JSON을 이해하고 tsc의 strict mode 오류를 읽을 수 있어야 한다. 편집기 hover가 보여 주는 inferred type과 실제 console의 typeof 값을 같은 것으로 취급하지 않는다. 이 프로젝트는 strict를 필수 기준으로 삼고, noUncheckedIndexedAccess와 exactOptionalPropertyTypes는 별도 실험에서 켜 영향과 migration 비용을 기록한다. 옵션을 완화할 때도 이유를 남긴다.

3. 15분 개념

VERIFIED

TypeScript는 주로 구조로 호환성을 판단한다. 필요한 member를 가진 값은 이름이 다른 type 선언에서 왔어도 호환될 수 있다. inference는 initializer와 호출 문맥에서 type을 계산한다. union A | B는 둘 중 하나이므로 공통으로 안전한 연산만 허용하고 runtime check가 narrowing한다. intersection A & B는 두 요구를 모두 만족한다. 공통 literal field를 둔 discriminated union은 switch로 좁힐 수 있고 default에서 never를 요구하면 새 variant 누락을 compile-time에 잡는다. any는 검사를 전파하며 끄고, unknown은 사용 전 검증을 강제하며, never는 가능한 값이 남지 않은 지점을 표현한다.

4. browser/runtime 내부

VERIFIED

Compiler는 source를 parse해 syntax tree를 만들고 binder/checker가 symbol과 type 관계를 계산한 뒤 target JavaScript와 선택적으로 .d.ts를 emit한다. generic은 type parameter로 관계를 보존한다. conditional type T extends U ? X : Y는 type 수준 분기를, mapped type { [K in keyof T]: ... }는 property 집합 변환을 표현한다. variance는 Container<T>의 호환성이 T의 호환성을 어느 방향으로 따르는지다. TypeScript에서는 T가 input·output 어디에 쓰이는지에 따라 구조적으로 나타나며, strictFunctionTypes와 method 예외 같은 호환성 규칙이 있다. variance annotation을 일상적인 ‘강제 캐스팅’으로 쓰지 않는다.

5. 최소 실행 코드

VERIFIED

아래 코드는 unknown JSON을 discriminated union으로 승격시키는 최소 runtime parser다. type predicate의 구현이 거짓이면 compiler도 속으므로 guard 자체를 정상·누락·잘못된 literal fixture로 테스트한다. assertNever는 새로운 state variant가 추가됐지만 render가 갱신되지 않은 경우 type error를 만든다. generic Result<T>는 payload와 오류의 관계를 재사용하지만 T를 runtime에서 검사해 주지는 않는다.

incident-schema.ts
export type Incident = {
  id: string;
  title: string;
  severity: "low" | "high";
};

export type LoadState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; message: string };

export function parseIncident(value: unknown): Incident {
  if (typeof value !== "object" || value === null) {
    throw new TypeError("incident must be an object");
  }
  const item = value as Record<string, unknown>;
  if (
    typeof item.id !== "string" ||
    typeof item.title !== "string" ||
    (item.severity !== "low" && item.severity !== "high")
  ) {
    throw new TypeError("invalid incident fields");
  }
  return { id: item.id, title: item.title, severity: item.severity };
}

function assertNever(value: never): never {
  throw new Error("Unhandled state: " + JSON.stringify(value));
}

export function label(state: LoadState<Incident>): string {
  switch (state.status) {
    case "idle": return "Idle";
    case "loading": return "Loading";
    case "success": return state.data.title;
    case "error": return state.message;
    default: return assertNever(state);
  }
}

6. production 코드

PRACTICE

외부 경계 함수는 unknown을 받고 검증된 domain type을 반환한다. schema library를 쓰든 수동 parser를 쓰든 parse 실패에 field path와 안전한 원인 코드를 제공하고 response 원문 전체를 production log에 남기지 않는다. DTO와 domain model을 분리해 optional/null, 날짜 문자열, enum drift를 한곳에서 변환한다. Generic fetchJson<T>()가 JSON을 T로 cast하는 API는 금지하고 fetchUnknown(url) 뒤 parser를 인자로 받게 한다. assertion은 DOM fixture처럼 외부 불변식이 별도 테스트로 증명된 좁은 지점에만 사용한다.

fetch-validated.ts
type Parser<T> = (input: unknown) => T;

export async function fetchValidated<T>(
  url: string,
  parse: Parser<T>,
  signal: AbortSignal,
): Promise<T> {
  const response = await fetch(url, { signal });
  if (!response.ok) throw new Error("HTTP " + response.status);
  const raw: unknown = await response.json();
  return parse(raw);
}

// The parser, not <Incident>, supplies the runtime proof.
const incident = await fetchValidated(
  "/api/incidents/42",
  parseIncident,
  new AbortController().signal,
);
console.log(incident.severity);

7. 코드 해부

SPEC

declaration file(.d.ts)은 runtime 구현 없이 JavaScript surface를 기술한다. declare는 ‘이 값이 runtime에 존재한다’고 compiler에게 알리므로 실제 script/global/export와 어긋나면 typecheck가 통과해도 crash한다. mapped type의 Partial<T>는 property를 optional로 바꾸지만 deep patch나 validation 의미를 자동 생성하지 않는다. conditional type은 union에 분배될 수 있어 예상보다 넓은 결과를 만들 수 있다. generated OpenAPI/GraphQL type도 입력 schema 시점의 정적 계약일 뿐, 잘못된 server, stale schema, proxy 변환, storage의 오래된 값까지 검증하지 않는다.

8. click/network/render end-to-end trace

VERIFIED

click에서 API 결과가 화면에 오르는 경계를 추적한다. handler의 input value는 DOM runtime value다. fetch Promise가 resolve된 뒤 response.json()은 unknown으로 취급한다. parser가 실제 property와 literal을 검사해 Incident를 만들고, 그 다음부터 reducer action과 component props는 정적 type 관계로 보호된다. discriminant를 switch에서 검사하면 control-flow analysis가 branch 안의 member를 좁힌다. build 시 annotation과 narrowing의 type 정보는 제거되고 실제 if/switch 검사와 반환 객체만 JavaScript로 남는다. 따라서 runtime trace에서 보이는 안전 장치는 parser의 조건문이지 interface가 아니다.

9. 실패 주입

VERIFIED

실패 주입은 XSS fixture를 string type과 assertion으로 ‘안전’하다고 오인하는 사례다. server가 { title: '<img src=x onerror=...>' }를 보내고 parseIncident가 title이 string인지까지만 확인하면 type contract는 만족하지만 HTML 안전성은 증명되지 않는다. React text interpolation은 문자열을 escape하지만 dangerouslySetInnerHTML이나 innerHTML sink에 넣으면 별도 sanitization과 정책이 필요하다. lab에서는 as SafeHtml 같은 assertion 버전이 typecheck를 통과함을 확인한 뒤, text sink 사용 또는 검증된 sanitizer 경계로 고친다.

xss-fixture장애 실습에서 재현 →

10. 테스트

PRACTICE

Type test와 runtime test를 분리한다. tsc/expect-type 계열 검사는 잘못된 assignment, generic inference, exhaustive switch가 compile 단계에서 실패하는지 본다. parser unit test는 실제 unknown fixture의 성공·누락·null·추가 enum·중첩 오류를 실행한다. integration test는 mocked fetch JSON이 parser failure UI로 이어지는지 확인하고, browser E2E는 XSS fixture가 text로 보이며 script/event handler가 실행되지 않는지 검사한다. generated type은 schema regeneration diff와 compatibility test를 함께 둔다.

11. 성능

VERIFIED

지워지는 type은 browser runtime 비용을 직접 추가하지 않지만 복잡한 conditional/mapped/recursive type은 tsc와 editor latency, declaration emit 크기를 키울 수 있다. 반대로 runtime schema validation은 실제 CPU와 bundle 비용이 있으므로 network 경계에서 한 번 수행하고 render마다 반복하지 않는다. 거대한 response를 전부 validate하는 비용은 performance mark로 측정하고 pagination/stream 단위 validation을 고려한다. any로 checker를 빠르게 만드는 대신 type 관계를 단순화하고 compiler diagnostics로 느린 파일을 찾는다.

12. 접근성

PRACTICE

UI state를 discriminated union으로 모델링하면 loading/error/success를 동시에 true로 만드는 불가능 상태를 줄일 수 있다. 그러나 type은 label, role, focus와 announcement의 품질을 검증하지 않는다. field error type에는 input id와 message를 연결할 정보 구조를 두고 실제 DOM에서 label/aria-describedby를 확인한다. exhaustive switch가 모든 상태를 렌더해도 screen reader가 변화를 인지하는지는 browser accessibility test와 수동 확인이 필요하다.

13. 보안

PRACTICE

UserId, Html, Url을 branded type으로 구분하면 accidental mix-up을 줄이지만 brand도 emit에서 사라져 보안 경계가 아니다. runtime constructor가 allowlist/URL origin/encoding 규칙을 확인한 뒤에만 branded value를 만들어야 한다. unknown을 any로 바꾸거나 double assertion(as unknown as T)하는 것은 trust boundary를 지운다. declaration package와 code generator도 supply-chain 입력이므로 version/lockfile을 고정하고 생성 diff를 review한다. validator 오류에는 secret 값 대신 field path와 code만 남긴다.

14. 운영

POLICY

CI는 formatter·linter 뒤 tsc --noEmit을 별도 gate로 실행하고 production build의 transpiler 성공과 typecheck 성공을 혼동하지 않는다. API schema/codegen은 deterministic command로 재생성하고 working tree diff가 남으면 실패시킨다. runtime parse failure에는 endpoint family, schema version, release, 안전한 error code를 계측해 server/client drift를 찾는다. 선언 파일만 갱신된 dependency도 runtime package와 같은 변경으로 review한다. strict 옵션 완화는 소유자·기간·제거 issue가 있는 예외로 관리한다.

15. 완료 조건

POLICY

완료 조건: (1) structural typing과 nominal typing의 차이를 예제로 설명한다. (2) union/intersection, inference, generic, conditional/mapped type과 variance를 작은 type test로 보인다. (3) unknown·never·any·assertion의 권한 차이를 설명한다. (4) discriminated union을 exhaustive하게 처리한다. (5) 실제 API fixture를 runtime 검증하고 generated type만으로는 실패를 막지 못함을 테스트한다. (6) XSS fixture가 text로만 나타난다. typecheck와 runtime 기능은 검증 가능하지만 팀 오류율 감소는 장기 데이터 전까지 unverified다.

16. 자가시험

POLICY

문항에 답할 때 compile-time과 runtime 문장을 따로 쓴다. ‘TypeScript가 막는다’는 답은 tsc가 어느 표현을 거부하는지와 배포 JavaScript에 어떤 검사가 남는지 설명해야 완전하다. 오답이면 emitted JavaScript를 열어 interface와 실제 guard 중 무엇이 남았는지 표시한다.

2문제 중 0문제 응답

1. response.json() as User가 runtime에서 보장하는 것은?
2. discriminated union의 default branch에서 never를 요구하는 이유는?
P03React 내부component 호출에서 render·reconciliation·commit과 effect까지190
OUTCOME

학습자는 React update를 render와 commit으로 추적하고 state snapshot·queue·key·effect lifecycle 오류를 재현하며 공개 contract와 Fiber 구현 세부를 구분한다.

component invocationReact elementrenderreconciliationFiber boundarycommitkeystate snapshotupdate queuebatchingstale closureHook stateEffect lifecycleEffect dependenciesrefcontexterror boundary

1. 실체와 오해

VERIFIED

React component는 React가 render 중 호출하는 함수다. JSX는 browser DOM node가 아니라 createElement/jsx runtime이 만드는 React element description으로 변환된다. state setter는 현재 변수 값을 즉시 바꾸지 않고 update를 queue해 다음 render를 요청한다. render는 component를 호출해 다음 UI를 계산하는 단계이며 pure해야 하고, commit은 필요한 DOM 변경을 적용하는 단계다. ‘virtual DOM이 항상 전체 DOM보다 빠르다’나 ‘Fiber field를 알면 scheduling이 보장된다’는 주장은 public contract가 아니다. 성능과 순서는 Profiler·공식 API behavior로 검증한다.

2. 선수 지식

POLICY

P00의 closure·task/microtask와 P02의 discriminated union, 함수·array map, DOM event·semantic HTML을 이해해야 한다. React DevTools Components/Profiler와 browser Performance 패널을 구분해 사용할 수 있어야 한다. 이 모듈은 client React API를 직접 다루며 router·server loader·file routing·SSR 같은 framework 기능을 React 자체 기능으로 부르지 않는다. StrictMode의 개발 전용 추가 호출은 production 횟수 예측 도구가 아니라 purity/cleanup 결함 탐지 도구로 쓴다.

3. 15분 개념

VERIFIED

15분 모델: ① createRoot(...).render 또는 state update가 render를 trigger한다. ② React가 function component를 호출하고 각 호출은 그 render의 props/state snapshot으로 element tree를 계산한다. ③ 이전 tree와 type·position·key를 사용해 무엇을 보존·변경·제거할지 reconcile한다. ④ commit에서 DOM mutation을 적용하고 refs를 연결하며 layout effect를 처리한 뒤 browser paint와 passive effect가 관련된다. 같은 event handler의 여러 update는 batch될 수 있고 updater function은 queue의 이전 결과를 받는다. Hook state는 함수 지역 변수 안이 아니라 tree의 component position과 Hook 호출 순서에 React가 연관시킨다.

4. browser/runtime 내부

VERIFIED

Fiber는 React 현재 구현의 작업/트리 bookkeeping 구조를 가리키지만 field layout, lane 값, traversal 순서는 public API가 아니며 release에서 바뀔 수 있다. 공식적으로 안정적인 학습 경계는 render가 component를 호출해 계산하고 React가 결과를 commit한다는 것, state가 tree position에 연결되고 key/type이 identity에 관여한다는 것이다. current source에서 Fiber node를 관찰하더라도 ‘모든 render가 한 번에 끝난다’, ‘component마다 DOM이 있다’, ‘render 직후 paint된다’고 추론하지 않는다. render는 재시작·중단될 수 있으므로 side effect를 넣지 않고, 외부 system 동기화는 event나 Effect/appropriate API로 둔다.

5. 최소 실행 코드

VERIFIED

이 counter는 snapshot과 update queue를 한 번에 보인다. bad 버튼의 세 호출은 같은 render의 count를 읽어 모두 같은 replacement value를 queue하므로 결과가 +1이다. good 버튼의 updater 세 개는 queue에서 이전 결과를 이어 받아 +3이다. console의 count는 handler가 생성된 render의 snapshot이라 setter 뒤에도 그대로다. 이것은 state setter가 Promise이거나 동기 mutation이라는 뜻이 아니다.

SnapshotCounter.tsx
import { useState } from "react";

export function SnapshotCounter() {
  const [count, setCount] = useState(0);

  return (
    <section aria-labelledby="counter-heading">
      <h2 id="counter-heading">Count: {count}</h2>
      <button onClick={() => {
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        console.log("this render's snapshot", count);
      }}>
        Bad +3
      </button>
      <button onClick={() => {
        setCount(value => value + 1);
        setCount(value => value + 1);
        setCount(value => value + 1);
      }}>
        Good +3
      </button>
    </section>
  );
}

6. production 코드

PRACTICE

render에서 props/state로 계산 가능한 derived value는 state와 Effect에 복제하지 않는다. list key는 database id처럼 sibling 사이에서 안정적이고 고유한 identity를 사용한다. Effect는 connection·subscription·timer처럼 외부 system을 현재 props/state에 동기화할 때만 쓰고 setup이 만든 자원을 cleanup이 대칭적으로 해제한다. 모든 reactive dependency를 적고 lint를 속이지 않는다. ref는 render에 필요 없는 mutable handle이나 DOM node에 사용하며 ref.current 변경으로 화면 갱신을 기대하지 않는다. context는 멀리 필요한 값에 쓰되 자주 바뀌는 거대한 객체 하나로 모든 render를 연결하지 않는다.

IncidentList.tsx
import { useEffect, useState } from "react";

type Incident = { id: string; title: string };

function parseIncidents(input: unknown): Incident[] {
  if (!Array.isArray(input)) throw new TypeError("expected incident array");
  return input.map((value, index) => {
    if (typeof value !== "object" || value === null) {
      throw new TypeError("incident[" + index + "] must be an object");
    }
    const item = value as Record<string, unknown>;
    if (typeof item.id !== "string" || typeof item.title !== "string") {
      throw new TypeError("invalid incident[" + index + "]");
    }
    return { id: item.id, title: item.title };
  });
}

export function IncidentList({ query }: { query: string }) {
  const [items, setItems] = useState<Incident[]>([]);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    void fetch("/api/incidents?q=" + encodeURIComponent(query), {
      signal: controller.signal,
    })
      .then(async response => {
        if (!response.ok) throw new Error("HTTP " + response.status);
        const input: unknown = await response.json();
        return parseIncidents(input);
      })
      .then(setItems)
      .catch((reason: unknown) => {
        if (!controller.signal.aborted) setError(String(reason));
      });
    return () => controller.abort("query changed");
  }, [query]);

  if (error) return <p role="alert">{error}</p>;
  return <ul>{items.map(item => <li key={item.id}>{item.title}</li>)}</ul>;
}

7. 코드 해부

VERIFIED

element는 type, props, key 같은 description을 담으며 mutable DOM node가 아니다. reconciliation에서 같은 parent 아래 같은 type/position은 state를 보존하는 출발점이고 key는 sibling identity를 명시한다. key가 달라지거나 type이 바뀌면 subtree state가 reset될 수 있다. useState 호출은 매 render 같은 순서여야 React가 slot을 연결할 수 있어 조건문 안 Hook이 금지된다. ref attachment와 useLayoutEffect는 commit과 밀접하고 useEffect는 일반적으로 화면 commit 뒤 외부 system을 동기화한다. Error Boundary는 descendant의 render/lifecycle 오류에 fallback을 제공하지만 event handler, 임의 async callback, boundary 자체 오류, server rendering 오류를 모두 잡는 만능 try/catch가 아니다.

8. click/network/render end-to-end trace

VERIFIED

사용자 click task에서 React handler가 현재 render snapshot을 읽고 update를 queue한다. handler가 끝나면 React는 안전한 batch 경계에서 queue를 처리해 새 state를 계산하고 component를 다시 호출한다. render에서 새 element description이 만들어지고 이전 tree와 reconcile된다. commit에서 필요한 DOM property/text/node가 변경되고 ref 및 layout Effect 단계가 진행된다. browser는 style/layout/paint를 수행해 pixels를 보일 수 있고, passive Effect는 외부 system을 동기화한다. Effect가 fetch를 시작하면 network response 뒤 microtask에서 setter가 다시 render를 queue한다. 이 각 경계의 timestamp를 React Profiler와 browser trace로 교차 확인한다.

9. 실패 주입

VERIFIED

주 실습은 wrong-key-state-move다. 입력 가능한 행을 index key로 render한 뒤 앞에 새 행을 삽입하면 React가 position/key identity에 state를 연결해 입력 state가 다른 데이터 행으로 이동해 보인다. stable item.id로 고친다. 같은 장에서는 두 보조 실패도 재현한다. stale closure는 interval callback이 생성 render의 count를 계속 읽는 문제이고 updater/ref/Effect 재동기화 중 의미에 맞는 해결을 고른다. effect-infinite-loop는 Effect가 매 render 새 object/function dependency를 보고 state를 다시 설정하는 순환이며, 불필요한 Effect 제거가 우선이다.

wrong-key-state-move장애 실습에서 재현 →

10. 테스트

PRACTICE

component test는 구현 함수 호출 횟수보다 사용자가 보는 결과를 검사한다. keyboard로 행을 편집하고 reorder한 뒤 값이 같은 id 행에 남는지 확인한다. fake timer로 stale interval을 빠르게 재현하되 실제 browser E2E에서 focus와 DOM identity도 확인한다. Effect cleanup test는 query 변경/unmount 시 abort·unsubscribe가 한 번의 논리적 lifecycle마다 대응하는지 본다. Error Boundary는 render throw fixture와 retry/reset UI를 테스트하고 event-handler 오류는 별도 경로로 처리한다. StrictMode 경고를 숨기지 않는다.

11. 성능

PRACTICE

render 호출 횟수 자체를 버그로 보지 않는다. React Profiler에서 느린 commit과 어떤 props/state가 바뀌었는지 측정한다. memo/useMemo/useCallback은 비교·closure 유지·복잡도 비용이 있으므로 expensive calculation 또는 참조 안정성이 실제 병목일 때만 사용한다. context value를 매 render 새 object로 만들면 consumer가 넓게 갱신될 수 있어 value 분리나 memo를 측정 후 적용한다. render 중 side effect와 layout thrashing을 제거하고 browser trace에서 React work 뒤 style/layout/paint가 병목인지도 구분한다.

12. 접근성

PRACTICE

React는 accessibility를 자동 완성하지 않는다. component는 가능한 native button, form, label, heading, list/dialog semantics를 반환한다. key 오류로 DOM이 재사용되면 focus와 accessible name이 의도하지 않은 데이터에 붙을 수 있다. conditional render로 focused node를 제거할 때 다음 logical target으로 focus를 복원하고 오류·저장 상태는 concise live region으로 알린다. ref는 imperative focus가 정말 필요한 commit 후 지점에 사용하며 render 중 ref.current를 읽어 UI를 결정하지 않는다.

13. 보안

VERIFIED

React는 JSX의 string child를 text로 escape하지만 URL 정책, authorization, CSRF, safe HTML을 자동 해결하지 않는다. dangerouslySetInnerHTML은 이름 그대로 raw HTML sink이며 신뢰 경계와 검증된 sanitization이 필요하다. props spread에 신뢰할 수 없는 객체를 그대로 넣지 말고 허용 prop을 선택한다. Error Boundary fallback이나 monitoring에 token·response body·개인정보를 출력하지 않는다. key에 secret을 넣어도 DOM attribute가 아니어서 안전해지는 것이 아니라 DevTools/telemetry 경로를 포함한 데이터 최소화 원칙을 적용한다.

14. 운영

POLICY

root와 중요한 workflow 경계에 복구 가능한 Error Boundary를 두고 release·route·component stack을 redacted error event와 연결한다. fallback에는 reload만 강요하지 말고 retry, 안전한 navigation, 지원 correlation id를 제공한다. React 개발/production build 차이를 모니터링 필터에 반영하고 StrictMode development signal을 production incident로 집계하지 않는다. feature flag 변경으로 tree position/key가 달라져 state가 reset되는지 canary에서 검사한다. source map version은 배포 artifact와 정확히 연결한다.

15. 완료 조건

POLICY

완료 조건: (1) component call→element→render/reconciliation→commit→browser paint 흐름을 그린다. (2) state snapshot, update queue, batching을 +3 예제로 설명한다. (3) Hook state와 key/type/position identity를 연결한다. (4) Effect setup/dependency/cleanup과 ref/context 역할을 구분한다. (5) wrong key, stale closure, effect loop를 재현하고 실제 browser에서 수정한다. (6) Error Boundary가 잡지 못하는 범위를 설명한다. 테스트 통과는 기능 검증이고 실제 개발자의 mental model 개선은 학습자 평가 전까지 unverified다.

16. 자가시험

POLICY

정답을 고른 뒤 React Profiler의 render/commit 관찰과 browser의 paint를 서로 다른 증거로 연결한다. 내부 Fiber property 이름을 답의 근거로 쓰지 않고 공식적으로 관찰 가능한 component identity와 state snapshot 규칙으로 설명한다. 틀린 문항은 minimal counter 또는 key lab을 keyboard로 다시 실행한다.

2문제 중 0문제 응답

1. 같은 handler에서 setCount(count + 1)을 세 번 호출했을 때 보통 +1인 이유는?
2. 동적 list에서 index key가 위험한 핵심 이유는?
P04상태 설계소유권·수명·진실의 원천으로 local/server/URL/form 상태를 분리하기175
OUTCOME

학습자는 상태를 종류별로 배치하고 reducer와 normalized entity 구조를 설계하며 optimistic rollback과 stale response race를 재현·수정한다.

local stateserver stateURL stateform statederived statenormalized statereducercache invalidationoptimistic updaterollbackracestale responsecancellation

1. 실체와 오해

PRACTICE

‘전역 state library를 쓰면 상태 문제가 해결된다’는 주장은 성립하지 않는다. 먼저 각 값의 소유자, 권위 있는 원천, URL 공유 필요, 지속 수명, 동시 쓰기 가능성을 분류해야 한다. dropdown open은 보통 local UI state, incident record는 server state, search/filter/page는 공유·뒤로가기가 필요한 URL state, 아직 제출하지 않은 input과 dirty/touched/error는 form state다. filteredItems처럼 다른 값에서 순수 계산 가능한 값은 derived state이며 중복 저장하면 동기화 버그가 생긴다. library는 이 경계를 구현할 뿐 대신 결정하지 않는다.

2. 선수 지식

POLICY

React state snapshot·key·Effect cleanup, TypeScript discriminated union, HTTP cache/ETag와 URLSearchParams를 이해해야 한다. 상태표를 그릴 때 ‘어디서 읽는가’보다 ‘누가 변경 권한을 갖는가’와 ‘새로고침/뒤로가기/다른 tab/server update 뒤 무엇이 남아야 하는가’를 먼저 답한다. 이 프로젝트는 server data를 component state에 영구 복제하지 않고, URL state의 encode/decode 함수를 testable pure function으로 둔다.

3. 15분 개념

PRACTICE

15분 분류표: local state는 한 subtree의 일시적 interaction, server state는 원격 권위와 freshness/cache/error를 가진 비동기 snapshot, URL state는 navigation history와 shareable address, form state는 제출 전 draft와 validation lifecycle이다. derived state는 render에서 계산한다. 여러 entity가 서로 참조하거나 여러 list가 같은 record를 보여 주면 byId와 orderedIds로 normalize해 한 id의 단일 client snapshot을 갱신한다. 복잡한 transition은 reducer action으로 사건을 표현한다. cache는 key·freshness·invalidation 규칙이 있어야 하고 optimistic mutation은 client mutation id, 이전 값, 확정/rollback 경로를 모두 가져야 한다.

4. browser/runtime 내부

VERIFIED

React는 각 render에 state snapshot을 주고 reducer action/update를 queue한다. reducer는 render 중 실행될 수 있으므로 pure해야 하며 fetch, timer, random id 생성, mutation을 넣지 않는다. URLSearchParams는 ordered list of name-value pairs라 같은 key가 반복될 수 있고 string encoding이므로 boolean/number/enum default를 명시적으로 parse한다. history.pushState는 새 navigation entry를 만들고 replaceState는 현재 entry를 바꾼다. server cache의 ‘stale’은 데이터가 틀렸다는 뜻이 아니라 정책상 재검증이 필요한 시점이다. 취소는 불필요한 일을 줄이지만 이미 server에 도달한 mutation을 되돌리는 transaction은 아니다.

5. 최소 실행 코드

VERIFIED

이 reducer는 normalized entity state와 optimistic lifecycle의 최소 형태다. id와 이전 entity를 action 생성 시 기록하므로 실패 응답에서 정확히 rollback할 수 있다. reducer 안에서는 request를 보내거나 Date.now를 호출하지 않는다. 두 mutation이 같은 entity에 겹치면 단일 previous만으로 충분하지 않으므로 production에서는 mutation id별 overlay/queue 또는 server version 비교가 필요하다.

incident-reducer.ts
type Incident = { id: string; title: string; acknowledged: boolean };
type State = {
  byId: Record<string, Incident>;
  orderedIds: string[];
  pending: Record<string, { id: string; previous: Incident }>;
};

type Action =
  | { type: "optimistic"; mutationId: string; next: Incident }
  | { type: "confirmed"; mutationId: string; server: Incident }
  | { type: "rolledBack"; mutationId: string };

export function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "optimistic": {
      const previous = state.byId[action.next.id];
      if (!previous) return state;
      return {
        ...state,
        byId: { ...state.byId, [action.next.id]: action.next },
        pending: {
          ...state.pending,
          [action.mutationId]: { id: action.next.id, previous },
        },
      };
    }
    case "confirmed": {
      const pending = { ...state.pending };
      delete pending[action.mutationId];
      return {
        ...state,
        byId: { ...state.byId, [action.server.id]: action.server },
        pending,
      };
    }
    case "rolledBack": {
      const entry = state.pending[action.mutationId];
      if (!entry) return state;
      const pending = { ...state.pending };
      delete pending[action.mutationId];
      return {
        ...state,
        byId: { ...state.byId, [entry.id]: entry.previous },
        pending,
      };
    }
  }
}

6. production 코드

PRACTICE

상태 schema를 기능별로 문서화한다. local: selectedRowId와 dialog open. URL: q, severity, cursor를 canonical encode/decode하고 typing 중에는 replace, 명시적 navigation에는 push를 선택한다. form: draft와 server entity를 분리해 background refetch가 사용자의 dirty field를 덮지 않게 한다. server cache: query key에 모든 입력을 포함하고 mutation success 시 server response로 entity를 갱신한 뒤 영향받는 list/detail key만 invalidate한다. optimistic update는 mutation id와 idempotency key를 만들고 실패 시 같은 mutation의 overlay만 제거하며 alert/live status로 결과를 알린다.

useIncidentSearch.tsx
import { useEffect, useState } from "react";

type Incident = { id: string; title: string };
type SearchState =
  | { status: "loading" }
  | { status: "ready"; items: Incident[] }
  | { status: "error"; message: string };

function parseIncidents(input: unknown): Incident[] {
  if (!Array.isArray(input)) throw new TypeError("expected incident array");
  return input.map((value, index) => {
    if (typeof value !== "object" || value === null) {
      throw new TypeError("incident[" + index + "] must be an object");
    }
    const item = value as Record<string, unknown>;
    if (typeof item.id !== "string" || typeof item.title !== "string") {
      throw new TypeError("invalid incident[" + index + "]");
    }
    return { id: item.id, title: item.title };
  });
}

export function useIncidentSearch(query: string): SearchState {
  const [state, setState] = useState<SearchState>({ status: "loading" });

  useEffect(() => {
    const controller = new AbortController();
    let current = true;
    setState({ status: "loading" });

    void fetch("/api/incidents?q=" + encodeURIComponent(query), {
      signal: controller.signal,
    })
      .then(async response => {
        if (!response.ok) throw new Error("HTTP " + response.status);
        const input: unknown = await response.json();
        return parseIncidents(input);
      })
      .then(items => {
        if (current) setState({ status: "ready", items });
      })
      .catch((error: unknown) => {
        if (current && !controller.signal.aborted) {
          setState({ status: "error", message: String(error) });
        }
      });

    return () => {
      current = false;
      controller.abort("superseded query");
    };
  }, [query]);

  return state;
}

7. 코드 해부

PRACTICE

Cache key는 endpoint 이름이 아니라 response를 결정하는 모든 입력의 canonical tuple이다. ['incidents', { q, severity, cursor }]처럼 구성하고 object ordering을 library가 안정적으로 처리하는지 확인한다. invalidation은 ‘모두 지우기’가 아니라 mutation이 바꾼 entity와 그것을 포함할 수 있는 query를 표시하는 dependency 정책이다. normalized state는 중복을 줄이지만 server ordering, pagination membership, optimistic overlay까지 하나의 byId로 해결하지 못한다. form draft는 normalized server entity와 다른 수명이라 복사 시점과 reset/merge 정책을 명시한다.

8. click/network/render end-to-end trace

VERIFIED

검색 입력 trace: native input event→React form/local draft update→debounce가 있다면 이전 timer cleanup→URL replaceState→canonical query key 변경→이전 request abort→loading snapshot→fetch→runtime parse→cache/entity update→render→commit→result count live announcement 순서다. 빠르게 A, AB를 입력하면 두 network response 순서는 보장되지 않는다. 최신 query ownership token 또는 cleanup flag와 AbortSignal을 함께 써 AB 요청만 state를 commit하게 한다. 뒤로가기는 popstate/router notification에서 URL을 다시 decode해 화면과 cache key를 복원해야 한다.

9. 실패 주입

VERIFIED

실패 주입은 late-response-overwrite다. A 검색 응답에 800ms, AB 응답에 100ms를 주면 AB가 먼저 보인 뒤 늦은 A가 최신 state를 덮는다. 먼저 request sequence id 또는 Effect cleanup의 current flag로 stale commit을 막고, AbortController로 이전 fetch와 body consumption도 취소한다. abort만 믿지 않는다. mock/server가 signal을 무시하거나 response가 이미 완료된 경계에서도 ownership check가 마지막 방어가 된다. test는 최종 결과뿐 아니라 stale A가 한 순간도 commit되지 않았는지 확인한다.

late-response-overwrite장애 실습에서 재현 →

10. 테스트

PRACTICE

Pure reducer test는 동일 input/action에 동일 output, 원본 불변성, optimistic→confirmed와 optimistic→rolledBack transition을 확인한다. URL codec property test는 decode(encode(state))의 canonical equality와 잘못된 enum/cursor default를 본다. integration test는 controllable deferred Promise 두 개로 response 순서를 뒤집어 stale commit을 검출한다. component test는 dirty form 중 background refetch가 input을 덮지 않는지 확인한다. 실제 browser E2E는 Back/Forward, copied URL, reload, keyboard focus, offline/timeout과 optimistic announcement를 검증한다.

11. 성능

PRACTICE

상태를 전역화하면 자동으로 빠르지 않다. broad context/store subscription은 작은 변경을 큰 subtree render로 전파할 수 있다. entity selector가 필요한 id만 반환하는지 Profiler로 측정하고 structural sharing을 유지한다. derived list filtering이 실제로 비싸고 입력이 안정적일 때 memoization을 고려하며 작은 계산은 그대로 둔다. cache cardinality는 검색어·cursor 조합으로 폭증할 수 있어 stale time, garbage-collection time, page cap을 정한다. optimistic overlay가 성공 후 남아 memory를 잡지 않는지 확인한다.

12. 접근성

PRACTICE

상태 분류에 focus와 announcement도 포함한다. dialog open은 local state지만 닫힐 때 focus를 돌려줄 trigger ref와 관계가 있다. URL filter 변경은 page navigation인지 in-place update인지 사용자에게 일관되게 보여야 하며, 결과 갱신은 매 keystroke 과다 발화 대신 debounce된 concise status를 사용한다. optimistic update는 즉시 보이는 변화와 ‘저장 중/실패해 복원됨’ 상태를 screen reader에도 전달한다. form error state는 field id와 연결하고 server error를 focus 가능한 summary와 inline message로 제공한다.

13. 보안

PRACTICE

client state는 authorization 근거가 아니다. hidden button, route state, normalized role field, optimistic owner 변경은 server 권한 검사를 대체하지 않는다. URL state는 address bar, history, referrer, analytics와 screenshot에 노출될 수 있으므로 token·email·민감 search를 넣지 않는다. persisted cache와 form draft에는 개인정보 수명·logout 삭제 정책을 둔다. optimistic mutation rollback이 UI만 되돌릴 뿐 server side effect를 취소하지 못할 수 있으므로 idempotency와 server version/precondition을 사용한다.

14. 운영

POLICY

운영 계측은 query key 원문 대신 allowlisted dimension과 hash/correlation id를 쓴다. cache hit, stale revalidation, cancellation, stale-response suppressed, optimistic confirm/rollback을 별도 event로 집계한다. rollback spike는 UI 오류율이 아니라 server conflict/validation/timeout 원인과 연결한다. persisted-state schema에는 version과 migration/clear 전략을 둔다. feature flag가 state shape를 바꾸면 backward-compatible decoder와 rollback path를 canary에서 검증하고, reducer action log에는 민감 payload를 넣지 않는다.

15. 완료 조건

POLICY

완료 조건: (1) capstone 값을 local/server/URL/form/derived로 분류하고 owner와 lifetime을 적는다. (2) entity를 normalize하고 pure reducer transition을 test한다. (3) cache key, freshness, invalidation을 문서화한다. (4) URL share/reload/Back이 같은 화면을 복원한다. (5) optimistic confirm/rollback과 overlapping mutation 정책을 구현한다. (6) 늦은 response를 cancellation+ownership check로 차단한다. 기능 검증과 상태모델 review는 가능하지만 support ticket 감소 같은 product 효과는 실제 운영 전까지 unverified다.

16. 자가시험

POLICY

각 답에는 ‘state를 어디에 둘지’뿐 아니라 authority와 수명을 적는다. 정답 확인 후 capstone의 실제 값 세 개를 같은 방식으로 분류한다. race 문항은 abort 유무만 답하지 말고 어떤 response가 commit 권한을 갖는지 설명해야 한다.

2문제 중 0문제 응답

1. 공유·새로고침·Back으로 복원돼야 하는 severity filter의 우선 상태 위치는?
2. AbortController와 별도로 최신 request ownership check가 유용한 이유는?
P05Network와 datafetch 성공 착시를 제거하고 timeout·retry·stream·auth 경쟁을 통제하기205
OUTCOME

학습자는 검증·취소 가능한 HTTP client를 만들고 retry/dedupe/pagination/stream/offline 정책을 적용하며 concurrent auth refresh를 single-flight로 고친다.

fetchHTTP error handlingtimeoutabortretrydeduplicationpaginationprefetchstreamingofflineauth refreshconcurrent refreshschema validationidempotent mutation

1. 실체와 오해

SPEC

fetch Promise가 fulfill됐다는 사실은 HTTP 요청의 업무 성공을 뜻하지 않는다. Fetch는 404·500 response도 정상 Response로 fulfill할 수 있으므로 response.ok/status를 검사해야 한다. network failure와 abort는 rejection 경로지만 CORS 차단 등은 보안상 상세 원인이 노출되지 않을 수 있다. timeout은 fetch 기본 성공/실패 상태가 아니라 AbortSignal 또는 상위 정책으로 만들어야 한다. retry는 모든 오류에 적용하는 마법이 아니다. replay-safe 여부, HTTP method의 idempotent semantics, body 재전송 가능성, server idempotency contract와 Retry-After를 함께 판단해야 한다.

2. 선수 지식

POLICY

HTTP method/status/header/body, same-origin/CORS, Promise/async-await, AbortController, TypeScript unknown parser, React Effect cleanup을 이해해야 한다. Network panel에서 request가 전송됐는지, response headers를 받았는지, service worker/cache가 개입했는지를 구분할 수 있어야 한다. 이 모듈의 client는 JSON을 무조건 가정하지 않고 content type과 empty body 정책을 endpoint contract에 맞추며, macOS와 Windows 모두 package script로 같은 test command를 실행한다.

3. 15분 개념

PRACTICE

15분 request policy: ① method·URL·headers·body·credentials와 caller signal을 만든다. ② 전체 deadline을 timeout signal과 결합한다. ③ fetch rejection과 HTTP non-2xx를 별도 error type으로 분류한다. ④ response body를 한 번 소비해 unknown으로 받고 schema parser로 domain value를 만든다. ⑤ retry는 GET/HEAD 또는 server가 보장한 idempotency key mutation 중 transient status/network failure에만 bounded backoff+jitter로 수행한다. ⑥ 동일 read key는 in-flight dedupe할 수 있지만 subscriber별 취소 소유권을 정한다. ⑦ cache/pagination/stream 결과를 state에 commit하기 전에 request ownership을 확인한다.

4. browser/runtime 내부

SPEC

Request와 Response body는 stream이며 한 번 읽기 시작하면 disturbed/locked 상태가 중요하다. retry할 때 이미 소비된 stream body를 그대로 재사용할 수 없으므로 재생 가능한 body를 새 Request로 만들어야 한다. response.body의 chunk는 JSON line이나 UTF-8 문자 경계와 일치하지 않는다. TextDecoder의 streaming mode와 application-level framing buffer가 필요하다. ReadableStream reader는 stream을 lock하며 cancel/release lifecycle을 관리해야 한다. AbortSignal은 fetch와 body consumption에 전달되지만 server가 이미 처리한 side effect를 취소한다는 보장은 없다. backpressure는 consumer 속도를 전달하는 stream 메커니즘이지 proxy buffering 제거 보장이 아니다.

5. 최소 실행 코드

VERIFIED

이 최소 client는 세 오류 경계를 드러낸다. AbortSignal.timeout이 deadline을 만들고 fetch rejection은 그대로 올라온다. non-2xx는 HttpError로, shape 오류는 parser의 TypeError로 구분된다. generic T는 runtime 검사기가 아니며 parse callback이 실제 증명을 제공한다. 지원 범위를 넓혀야 한다면 AbortSignal.timeout/any를 feature-detect하고 동일 semantics의 controller+timer 조합을 제공한다.

request-json.ts
export class HttpError extends Error {
  constructor(
    readonly status: number,
    readonly retryAfter: string | null,
  ) {
    super("HTTP " + status);
    this.name = "HttpError";
  }
}

type Parser<T> = (input: unknown) => T;

export async function requestJson<T>(
  url: string,
  parse: Parser<T>,
  callerSignal?: AbortSignal,
  timeoutMs = 5_000,
): Promise<T> {
  const timeout = AbortSignal.timeout(timeoutMs);
  const signal = callerSignal
    ? AbortSignal.any([callerSignal, timeout])
    : timeout;

  const response = await fetch(url, {
    headers: { Accept: "application/json" },
    signal,
  });
  if (!response.ok) {
    throw new HttpError(response.status, response.headers.get("Retry-After"));
  }
  const input: unknown = await response.json();
  return parse(input);
}

6. production 코드

POLICY

Production client는 endpoint별 parser, total timeout, caller cancellation, typed HTTP error와 request correlation id를 요구한다. retry policy는 최대 시도·최대 총 시간·허용 status·jitter·Retry-After 상한을 가진다. POST mutation을 retry하려면 client가 unique idempotency key를 보내고 server가 같은 key+payload를 한 번의 결과로 deduplicate한다는 계약과 보존 기간이 있어야 한다. refresh는 module 전역 단일 Promise가 아니라 auth client instance별 single-flight로 공유하고 한 번만 원 request를 replay한다. logout/invalid refresh는 대기 중 request를 모두 같은 terminal auth error로 끝낸다.

auth-client.ts
export class AuthClient {
  #refreshInFlight: Promise<void> | null = null;

  constructor(private readonly refreshToken: () => Promise<void>) {}

  #refreshOnce(): Promise<void> {
    if (this.#refreshInFlight) return this.#refreshInFlight;
    this.#refreshInFlight = this.refreshToken().finally(() => {
      this.#refreshInFlight = null;
    });
    return this.#refreshInFlight;
  }

  async get(input: RequestInfo | URL, signal?: AbortSignal): Promise<Response> {
    const first = await fetch(input, { credentials: "include", signal });
    if (first.status !== 401) return first;

    await first.body?.cancel();
    await this.#refreshOnce();
    if (signal?.aborted) throw signal.reason;

    // Exactly one replay; a second 401 is terminal.
    return fetch(input, { credentials: "include", signal });
  }
}

7. 코드 해부

PRACTICE

In-flight dedupe map은 canonical request key→Promise/entry를 저장하고 finally에서 같은 entry만 제거한다. 서로 다른 caller가 한 underlying request를 공유하면 한 caller의 abort가 모두를 취소할지, reference count가 0일 때만 취소할지 정책이 필요하다. pagination은 offset/page보다 cursor가 변경 중 dataset에서 안정적일 수 있지만 cursor는 opaque하게 취급하고 nextCursor null을 종료로 해석한다. prefetch는 다음 page 가능성, data-saver/network, cache 여유를 고려해 낮은 우선순위로 하며 navigation보다 경쟁시키지 않는다. cache key에 auth scope와 locale 등 response를 바꾸는 값을 빠뜨리지 않는다.

8. click/network/render end-to-end trace

VERIFIED

인증된 검색 trace: click/input→URL/query key→dedupe lookup→fetch Request→HTTP response headers. 200이면 body stream을 읽고 frame별로 decode/buffer/parse/validate해 streaming state를 append한다. 401이면 첫 request는 body 처리 없이 auth client의 refresh single-flight에 join한다. 한 refresh response가 cookie/token state를 갱신한 뒤 waiter가 각각 원 GET을 한 번 replay한다. 429/503 retry는 Retry-After 또는 bounded backoff 뒤 새 task에서 시도하고 caller deadline이 먼저 끝나면 abort한다. 각 결과는 request ownership을 확인한 뒤 cache/render에 commit한다.

9. 실패 주입

VERIFIED

주 실패 주입은 duplicate-auth-refresh다. 동시에 401을 반환하는 list/detail/prefetch 세 요청이 각각 refresh를 보내면 refresh token rotation 경쟁, rate limit, 서로의 새 credential 무효화가 발생할 수 있다. Network panel에서 refresh 3개를 확인한 뒤 모든 caller가 같은 in-flight Promise에 join하도록 고쳐 refresh 1개와 원 요청 replay 3개만 남긴다. 두 번째 401은 refresh loop를 만들지 않고 terminal error다. 보조 offline-timeout fixture에서는 offline hint를 믿지 않고 실제 fetch failure/timeout을 구분하며 cached fallback과 Retry button을 제공한다.

duplicate-auth-refresh장애 실습에서 재현 →

10. 테스트

PRACTICE

Unit test는 status classification, parser, Retry-After/backoff cap, abort reason, idempotency key reuse를 fake clock으로 검사한다. integration test는 동시에 resolve되는 세 401을 barrier로 묶어 refresh 호출이 정확히 한 번이고 각 GET이 최대 한 번 replay되는지 본다. streaming fixture는 UTF-8 multi-byte 문자와 JSON line을 chunk 중간에서 잘라 framing bug를 찾는다. 실제 browser E2E는 offline context, delayed headers/body, timeout, abort, page reload, credentials/CORS를 검증한다. route mock만으로 TLS, proxy buffering, service worker, real network loss를 검증했다고 말하지 않는다.

11. 성능

PRACTICE

Request 수만 줄이는 것이 목표가 아니다. dedupe가 latency와 bytes를 줄이는지, stale cache가 잘못된 UX를 늘리는지 함께 측정한다. 큰 JSON은 download 뒤 parse/validate가 main thread long task가 될 수 있다. pagination 또는 NDJSON streaming으로 first item latency를 줄이고 batch 단위로 state를 갱신해 record마다 React render하지 않는다. reader loop에서 무제한 queue를 쌓지 말고 backpressure/cancel을 존중한다. aggressive prefetch는 mobile bandwidth·battery·server load를 소비하므로 hit rate와 wasted bytes budget을 둔다.

12. 접근성

PRACTICE

Network state는 시각 spinner만으로 전달하지 않는다. form submit control에는 실제 disabled 또는 aria-disabled 정책과 visible pending label을 제공하고, 결과 영역은 aria-busy를 적절히 사용한다. streaming record마다 live announcement하면 과도하므로 일정 batch 또는 완료 요약을 polite region에 알린다. timeout/offline/401 fallback은 keyboard로 접근 가능한 Retry·Sign in control과 구체적 next action을 제공한다. retry 중 focus를 spinner로 옮기지 않고 오류가 사라질 때 focus가 body로 유실되지 않게 한다.

13. 보안

PRACTICE

credentials: include는 cross-origin cookie 전송과 CORS credential policy를 함께 요구하므로 기본값처럼 남발하지 않는다. access/refresh token storage와 rotation은 threat model에 맞추며 script-readable storage는 XSS 탈취 위험이 있다. refresh endpoint는 CSRF와 replay를 방어하고 rate limit·revocation을 둔다. URL/query/log/error에 token이나 response body를 넣지 않는다. redirect target은 same-origin/allowlist로 검증해 open redirect를 막는다. schema validation은 shape를 보지만 authorization과 output encoding을 대신하지 않는다. idempotency key는 충분히 예측 불가능하고 user/operation scope에 묶어야 한다.

14. 운영

POLICY

관측은 DNS/connect/TLS/TTFB/download/parse/validate/commit 단계를 가능한 범위에서 분리하고 endpoint family별 status·timeout·abort·retry·dedupe hit·stream cancel을 집계한다. abort와 offline을 server error rate에 섞지 않는다. retry storm을 막기 위해 client total budget, server Retry-After, global circuit/feature flag를 둔다. auth refresh failure가 급증하면 replay를 무한 확대하지 않고 safe sign-out 또는 degraded read-only mode로 전환한다. idempotency record retention과 cache eviction을 운영 용량에 포함하고 raw payload는 monitoring에 보내지 않는다.

15. 완료 조건

POLICY

완료 조건: (1) fetch rejection과 HTTP non-2xx를 구분한다. (2) caller abort와 timeout reason을 보존한다. (3) unknown response를 runtime parser로 검증한다. (4) bounded retry·dedupe cancellation·cursor pagination·prefetch budget을 문서화한다. (5) split chunk stream을 올바르게 decode/parse하고 cancel한다. (6) concurrent 401에서 refresh 한 번, replay 한 번 이하를 보장한다. (7) offline/timeout과 idempotent mutation을 실제 browser/server fixture로 테스트한다. 테스트는 기능 검증이며 production network 신뢰도는 field canary 전까지 unverified다.

16. 자가시험

POLICY

정답은 network transport 결과, HTTP semantics, application schema 결과를 세 층으로 나눠 설명한다. retry 문항에는 ‘몇 번’뿐 아니라 어떤 operation이 replay-safe한지와 total deadline을 적는다. 오답이면 Network panel에서 404 fulfill, abort rejection, duplicate refresh fixture를 다시 관찰한다.

2문제 중 0문제 응답

1. fetch('/missing')가 404 Response를 받으면 기본 Promise는 어떻게 되는가?
2. 동시 401 세 건의 refresh를 안전하게 조정하는 핵심은?
P06렌더링 아키텍처CSR부터 스트리밍 HTML까지, React의 능력과 프레임워크의 책임을 분리한다.135
OUTCOME

요청의 제약에 맞는 렌더링 방식을 선택하고 hydration·bundle·data waterfall을 추적한다.

CSRSSRstatic generationhydrationserver/client boundarystreaming HTMLbundle waterfalldata waterfallcache

1. 실체와 오해

VERIFIED

React는 렌더링 라이브러리이지 완성된 웹 전달 아키텍처가 아니다. React DOM은 브라우저 root, hydration, 서버 HTML·stream API를 제공하지만 URL 라우팅, 빌드 시 정적 생성, 데이터 로더, 응답 캐시, 배포 런타임은 보통 프레임워크나 애플리케이션이 결정한다. 따라서 ‘React가 SSR한다’는 말은 API 능력과 운영 통합을 섞은 축약이다.

2. 선수 지식

PRACTICE

HTTP 요청·응답, ESM import, DOM event, React component와 Suspense의 기본 의미를 알고 시작한다. 먼저 JavaScript가 비활성화된 문서가 무엇을 보여 주는지, 그 뒤 어떤 script와 JSON이 내려오는지 Network 패널에서 구분할 수 있어야 한다. ‘서버’는 원격 머신이 아니라 HTML을 만드는 실행 환경이라는 점도 고정한다.

3. 15분 개념

SPEC

CSR은 빈 shell과 JavaScript 뒤에 UI를 만들고, SSR은 요청 시 HTML을 만들며, static generation 계열은 배포 전 또는 재검증 시 HTML을 만든다. Hydration은 기존 서버 DOM에 React의 event 처리와 상태를 연결한다. Streaming HTML은 Suspense 경계별로 shell과 후속 조각을 보낼 수 있지만, 상호작용 가능 시점은 해당 JavaScript의 다운로드·실행·hydration에도 의존한다.

  • React 기능: element 렌더링, hydrateRoot, 서버 stream, Suspense 경계.
  • 프레임워크 기능: route 규칙, server/client module graph, data API, 캐시·재검증, asset pipeline, 배포 adapter.

4. browser/runtime 내부

VERIFIED

SSR 경로에서 서버는 component tree를 평가해 HTML byte stream을 만들고 브라우저의 HTML parser는 도착한 byte부터 DOM을 구성한다. hydrateRoot는 서버와 클라이언트의 첫 render가 같은 구조라고 가정하며 mismatch는 고쳐질 것이라는 보장이 없다. Server/client boundary를 넘는 props는 선택한 프로토콜로 직렬화 가능해야 하고, client bundle은 그 경계 아래 import graph를 포함하므로 경계 위치가 비용이다.

5. 최소 실행 코드

SPEC

아래는 프레임워크 없이 React가 제공하는 최소 server stream과 hydration 접점이다. 실제 서비스에는 route 매칭, asset manifest, 오류 응답, CSP nonce, cache header가 더 필요하다. 이 코드가 곧 static generation이나 framework server component를 의미하지는 않는다.

entrypoints.tsx
// server.tsx
const stream = await renderToReadableStream(<App initialData={data} />, {
  bootstrapModules: ["/client.js"],
  onError(error) { reportServerError(error); },
});
return new Response(stream, {
  headers: { "content-type": "text/html; charset=utf-8" },
});

// client.tsx
hydrateRoot(document, <App initialData={window.__INITIAL_DATA__} />, {
  onRecoverableError(error, info) { reportHydrationError(error, info); },
});

6. production 코드

PRACTICE

페이지별로 freshness, personalization, indexability, latency, failure isolation을 표로 만든 뒤 렌더링 방식을 선택한다. 공개 문서는 정적 생성과 CDN 캐시, 사용자별 화면은 authenticated SSR 또는 CSR, 느린 보조 패널은 streaming boundary가 후보가 된다. Cache-Control의 public/private와 Vary key를 명시하고 HTML cache, data cache, client cache를 하나의 ‘캐시’로 뭉뚱그리지 않는다.

render-policy.ts
type RenderPolicy = {
  mode: "static" | "request-ssr" | "csr";
  htmlCache: "public" | "private" | "none";
  revalidateSeconds?: number;
};

export const policyByRoute: Record<string, RenderPolicy> = {
  "/docs/:slug": { mode: "static", htmlCache: "public", revalidateSeconds: 300 },
  "/operations": { mode: "request-ssr", htmlCache: "private" },
};

7. 코드 해부

PRACTICE

HTML에 화면이 보여도 client component의 import가 거대한 chart library를 당기면 hydration 전에 bundle waterfall이 생긴다. 반대로 client effect가 mount 후 목록을 받고 각 row가 상세를 다시 요청하면 data waterfall이다. route chunk, critical CSS, font, API 요청을 initiator와 priority로 연결해 보고, Suspense나 dynamic import는 dependency를 숨기는 장식이 아니라 waterfall을 재배치하는 도구로 취급한다.

8. click/network/render end-to-end trace

VERIFIED

대시보드 URL 요청 → CDN/서버 cache 판정 → HTML shell byte 수신 → parser가 DOM·preload 발견 → CSSOM과 첫 paint → client module 실행 → hydrateRoot render → event handler 연결 → 사용자의 click → route/data 요청 → React render → commit 순으로 trace ID와 Performance mark를 잇는다. 서버 HTML 도착과 상호작용 가능은 다른 시각이며 둘 다 기록해야 한다.

9. 실패 주입

VERIFIED

실패 실습은 서버에서 현재 시간을 render하고 클라이언트 첫 render에서 다시 Date.now()를 호출한다. hydration 경고, DOM 보정 여부, event 동작을 기록한 뒤 서버가 만든 고정 timestamp를 prop으로 전달해 양쪽 첫 출력을 일치시킨다. suppressHydrationWarning은 원인을 고치는 기능이 아니며 의도적으로 다른 단일 노드에만 제한한다.

HydrationClock.tsx
// Broken: server and client usually produce different text.
export function Clock() {
  return <time>{Date.now()}</time>;
}

// Repaired: the serialized value is identical for the first render.
export function StableClock({ initialIso }: { initialIso: string }) {
  return <time dateTime={initialIso}>{initialIso}</time>;
}
hydration-mismatch장애 실습에서 재현 →

10. 테스트

PRACTICE

세 층을 분리한다. 문자열/stream test는 HTML과 cache header를, 실제 browser test는 hydration console error·JavaScript 비활성 문서·탐색 후 focus를, 배포 smoke test는 CDN cache와 asset URL을 검증한다. jsdom component test만으로 parser timing, module download, hydration mismatch, back-forward cache를 검증했다고 말하지 않는다.

11. 성능

PRACTICE

TTFB만 줄이거나 HTML을 stream했다고 성공이 아니다. LCP resource가 조기에 발견되는지, hydration 대상 JavaScript 양과 main-thread 실행 시간, interaction 직후 INP, unexpected shift를 함께 측정한다. Server/client boundary를 아래로 내리면 client bundle이 줄 수 있지만 잦은 왕복과 직렬화 비용이 늘 수 있으므로 실제 trace로 판단한다.

12. 접근성

SPEC

서버 HTML부터 landmark, heading 순서, link 이름, form label이 완전해야 느린 JavaScript와 실패 상황에서도 문서를 탐색할 수 있다. Streaming fallback은 상태를 설명하는 텍스트를 갖고, 교체 시 focus를 빼앗지 않는다. Route 전환은 문서 title과 주 heading을 갱신하고 팀 정책에 따라 focus 이동 또는 live announcement를 일관되게 수행한다.

13. 보안

SPEC

개인화 HTML을 shared cache에 저장하면 사용자 데이터가 교차 노출될 수 있다. 인증 응답에는 private/no-store 정책과 정확한 Vary를 적용하고, serialized initial data는 script 문맥 escaping을 거쳐야 한다. Streaming 응답의 inline bootstrap이 있다면 request별 CSP nonce 또는 hash 정책과 맞추며 server/client boundary를 비밀 경계로 오해하지 않는다. 브라우저에 전달된 값은 공개된 값이다.

14. 운영

POLICY

route별 render mode, cache owner, invalidation trigger, fallback behavior, runtime region을 운영 표에 기록한다. 배포 후 hydration recoverable error rate, HTML/cache status, chunk 404, LCP/INP/CLS를 release와 연결하고 이상 시 이전 asset manifest와 HTML을 함께 rollback한다. HTML만 rollback해 새 chunk를 가리키는 혼합 버전을 만들지 않는다.

15. 완료 조건

POLICY

완료 조건은 (1) CSR/SSR/static/streaming 선택표, (2) React와 framework 책임 표, (3) JavaScript off·slow network 실제 browser 기록, (4) hydration mismatch 실패-수정 test, (5) cache header와 invalidation 증거, (6) route별 bundle/data waterfall trace다. 빌드 성공만으로 렌더링 품질을 완료 처리하지 않는다.

16. 자가시험

POLICY

아래 두 문항을 근거 없이 맞히는 것이 아니라 Network initiator, server response, React 공식 API 문서로 설명한다. 오답이면 hydration 실습과 책임 경계 표를 다시 작성한다.

2문제 중 0문제 응답

1. 다음 중 React 자체가 route별로 자동 제공한다고 볼 수 없는 것은?
2. hydration mismatch를 가장 직접적으로 예방하는 방법은?
P07Form과 interaction브라우저의 form 계약 위에 복구 가능한 React interaction을 쌓는다.120
OUTCOME

검증·focus·중복 제출·optimistic UI·dirty navigation을 포함한 운영 form을 구현한다.

native formvalidationcontrolleduncontrolledfocuspending statedouble submitoptimistic UIserver errordirty statenavigation interruption

1. 실체와 오해

SPEC

form은 input을 모아 POST하는 낡은 껍데기가 아니라 keyboard submit, constraint validation, autofill, password manager, FormData, progressive enhancement를 묶은 브라우저 protocol이다. React state로 모든 값을 복제하면 이 계약이 자동으로 좋아지지 않는다. controlled/uncontrolled는 우열이 아니라 누가 현재 값을 소유하고 언제 render가 필요한가의 선택이다.

2. 선수 지식

PRACTICE

button의 기본 type, label과 accessible name, submit event와 preventDefault, FormData의 successful controls 규칙을 익힌다. 서버가 field error와 form-level error를 구분해 안정된 code로 반환한다고 가정한다. optimistic update 전에 mutation의 idempotency와 rollback 정보가 무엇인지 P05에서 확인한다.

3. 15분 개념

PRACTICE

HTML validation은 빠른 첫 방어이지만 서버 validation을 대체하지 않는다. 값 간 조건이나 즉시 formatting이 필요하면 controlled input, 대규모 단순 form이나 파일 입력은 uncontrolled/FormData가 자연스럽다. pending은 submit 시작부터 최종 성공·오류까지 하나의 state machine으로 관리하고, 성공 전 optimistic UI를 보였다면 실패 시 이전 snapshot과 focus/announcement를 복원한다.

4. browser/runtime 내부

VERIFIED

Enter는 form의 submit algorithm을 시작할 수 있고 submit button activation도 같은 submit event로 수렴한다. disabled control은 focus와 제출 데이터에서 빠질 수 있으므로 ‘중복 클릭 방지’와 ‘값 전송’ 요구를 같이 확인한다. React handler 안에서 state를 바꿔도 현재 handler가 읽는 값은 그 render의 snapshot이며, ref나 DOM FormData가 필요한 이유를 구분한다.

5. 최소 실행 코드

SPEC

최소 구현은 실제 form과 submit button을 유지하고 브라우저 validation을 통과한 뒤 FormData를 보낸다. JavaScript handler가 실패해도 action endpoint가 처리할 수 있도록 구성하면 progressive enhancement 경로가 남는다.

IncidentForm.tsx
<form action="/incidents" method="post" onSubmit={handleSubmit}>
  <label htmlFor="summary">Summary</label>
  <input id="summary" name="summary" required minLength={8} />
  <button type="submit">Create incident</button>
</form>

6. production 코드

POLICY

운영 구현은 idle → submitting → succeeded 또는 failed의 discriminated union을 사용한다. 첫 invalid field, 서버가 지정한 field, form-level alert의 focus 순서를 정의한다. request ID 또는 idempotency key를 mutation에 붙이고, pending 동안 동일 intent만 차단하며 취소·재시도 정책을 노출한다. dirty 판정은 초기 canonical value와 현재 값을 비교하며 단순히 한 번 입력했다는 boolean으로 고정하지 않는다.

submission-state.ts
type SubmissionState =
  | { status: "idle" }
  | { status: "submitting"; requestId: string }
  | { status: "failed"; requestId: string; fieldErrors: Record<string, string> }
  | { status: "succeeded"; incidentId: string };

function maySubmit(state: SubmissionState) {
  return state.status !== "submitting";
}

7. 코드 해부

PRACTICE

name은 서버 payload key, id/htmlFor는 label 연결, aria-describedby는 도움말·오류 연결을 맡는다. aria-invalid는 실제 invalid 상태에서만 설정한다. onChange마다 전체 form과 비싼 preview를 render한다면 state를 field 가까이 두거나 deferred computation을 검토하되, memoization으로 불분명한 ownership을 덮지 않는다.

8. click/network/render end-to-end trace

VERIFIED

사용자 Enter → browser constraint validation → submit event → synchronous guard가 request ID 생성 → pending render/commit → fetch → 서버 validation·idempotency 판정 → response schema validation → optimistic record 확정 또는 rollback → live region announcement → 성공 시 다음 의미 있는 heading으로 focus라는 전체 trace를 기록한다. focus 이동은 commit 뒤 실제 노드가 존재할 때 수행한다.

9. 실패 주입

VERIFIED

실패 실습은 submit handler가 첫 await 뒤에만 isPending을 설정하도록 만들고 빠르게 Enter와 click을 연속 실행한다. 두 network mutation과 중복 row를 증거로 남긴 뒤, handler 진입 시 동기 ref/state-machine guard와 서버 idempotency key를 함께 적용한다. button disabled만으로 server duplicate나 여러 탭을 막았다고 주장하지 않는다.

double-submit.lab.tsx
const inFlight = useRef(false);

async function submit(event: FormEvent<HTMLFormElement>) {
  event.preventDefault();
  if (inFlight.current) return;
  inFlight.current = true;
  try {
    await createIncident(new FormData(event.currentTarget), crypto.randomUUID());
  } finally {
    inFlight.current = false;
  }
}
double-submit장애 실습에서 재현 →

10. 테스트

PRACTICE

component test는 label로 입력하고 Enter submit, field error 연결, pending text, rollback을 검사한다. integration test는 실제 validation schema와 idempotency store를 사용한다. 실제 browser E2E는 keyboard-only, autofill 가능한 name/autocomplete, back navigation의 dirty prompt, 느린 응답 중 double submit을 검증한다. 임의 sleep 대신 요청과 accessible 상태를 기다린다.

11. 성능

PRACTICE

typing interaction의 INP와 commit duration을 큰 form에서 측정한다. 모든 keystroke를 global store·URL·network에 동기화하면 main thread와 history를 압박한다. validation은 cheap synchronous rule과 expensive asynchronous rule을 분리하고 후자는 debounce뿐 아니라 취소·stale response guard도 둔다.

12. 접근성

SPEC

placeholder를 label로 사용하지 않는다. 오류 summary는 오류 수와 field link를 제공하고 각 field 오류는 aria-describedby로 연결하며 제출 결과는 적절한 live region에서 한 번 알린다. pending 중 focus를 사라지는 disabled button으로 내몰지 말고 상태 텍스트를 노출한다. 서버 오류 후 사용자의 입력을 보존하고 첫 actionable 오류로 예측 가능하게 이동한다.

13. 보안

SPEC

client validation은 UX일 뿐 신뢰 경계가 아니다. 서버는 권한, schema, 길이, 허용 값, CSRF 방어를 다시 검증하고 rich text를 출력 문맥에 맞게 처리한다. hidden input의 user ID·price·role은 조작 가능하다. mutation은 SameSite cookie만 믿지 말고 threat model에 맞는 CSRF token/Origin 검사와 idempotency를 적용한다.

14. 운영

POLICY

form별 submit success/error/abandon rate를 기록하되 입력 원문·token·개인정보를 log하지 않는다. validation error code와 UI version을 연결하고, 특정 field error 급증을 schema/client 불일치 신호로 사용한다. navigation blocker와 optimistic flag는 feature flag로 되돌릴 수 있고 rollback runbook에 보존되는 사용자 입력을 명시한다.

15. 완료 조건

POLICY

완료 조건은 mouse와 keyboard 제출, native+server validation, double-submit fail/fix 증거, optimistic rollback, server error 후 값·focus 보존, dirty navigation 확인이다. Function 검증은 흐름 실행, Quality 검증은 오류 연결·중복 0건·interaction budget, Product 검증은 실제 사용자가 더 낮은 오류로 task를 끝내는지이며 마지막 항목은 학습자 시험 전 unverified다.

16. 자가시험

POLICY

두 문항의 정답뿐 아니라 브라우저 기본 기능을 보존하는 이유와 server trust boundary를 코드로 설명한다. 틀리면 JavaScript를 끄고 form을 제출하는 실습부터 반복한다.

2문제 중 0문제 응답

1. double submit의 가장 강한 방어 조합은?
2. 서버 오류 후 접근 가능한 기본 동작은?
P08접근성semantic 구조, keyboard, focus, announcement를 실제 task 흐름으로 검증한다.135
OUTCOME

WCAG 2.2 AA 기준의 dashboard interaction을 구현하고 자동·수동 접근성 검사를 구분한다.

semantic elementheadinglabelkeyboardfocus orderfocus restorationdialoglive regionscreen readercolor contrastreduced motiontouch targeterror announcement

1. 실체와 오해

SPEC

접근성은 aria 속성을 뒤에 붙이는 단계가 아니라 DOM 의미, 이름·역할·값, keyboard operation, focus, 시각 표현, 상태 announcement가 같은 interaction model을 설명하도록 만드는 일이다. 자동화 도구는 일부 규칙만 찾으며 screen reader의 task 완수나 focus 맥락은 사람이 검증해야 한다. div에 role을 붙이는 것보다 native element의 내장 동작을 먼저 사용한다.

2. 선수 지식

PRACTICE

DOM tree와 accessibility tree가 동일하지 않음을 이해하고 accessible name 계산의 label, text, aria-labelledby 우선순위를 익힌다. Tab/Shift+Tab, Enter, Space, Escape, arrow key의 widget별 기대를 WAI-ARIA APG에서 확인한다. WCAG success criterion, conformance level, technique를 같은 규범 강도로 오해하지 않는다.

3. 15분 개념

SPEC

페이지는 header/nav/main/aside/footer landmark와 건너뛰기 link, 끊기지 않는 heading hierarchy로 위치를 제공한다. 모든 control은 programmatic label과 visible focus를 갖는다. WCAG 2.2 AA에서 text contrast는 보통 4.5:1(큰 text 3:1), non-text UI 정보는 3:1이며 Target Size (Minimum)는 예외를 제외하고 24×24 CSS px 또는 충분한 spacing을 요구한다.

4. browser/runtime 내부

VERIFIED

브라우저는 DOM, CSS, ARIA를 조합해 accessibility API에 tree를 노출하고 screen reader는 이를 탐색한다. display:none, hidden, inert는 노출과 focusability에 영향을 주지만 opacity:0은 같은 보장을 하지 않는다. focus는 document의 단일 activeElement이며 DOM 제거 시 body로 유실될 수 있다. 따라서 조건부 render 전에 반환 지점을 저장하고 commit 후 유효성을 확인해 복원한다.

5. 최소 실행 코드

SPEC

필터 control은 button, 상태는 aria-expanded/aria-controls, 결과 갱신은 status로 표현한다. 시각 label이 있으면 aria-label로 다른 이름을 덮지 않는다. reduced motion은 장식 animation을 줄이되 중요한 상태 변화를 숨기지 않는다.

AccessibleFilter.tsx
<button
  type="button"
  aria-expanded={open}
  aria-controls="incident-filters"
  onClick={() => setOpen(value => !value)}
>
  Filters
</button>
<div id="incident-filters" hidden={!open}>{/* labeled controls */}</div>
<p role="status" aria-live="polite">{resultMessage}</p>

6. production 코드

PRACTICE

dialog는 native dialog 또는 검증된 primitive로 label, description, initial focus, contained Tab sequence, Escape close, background inert, close 후 trigger focus restoration을 하나의 계약으로 구현한다. destructive confirmation은 가장 안전한 초기 focus를 선택한다. route·panel 교체는 heading과 title을 갱신하되 사용자가 입력 중인 focus를 무조건 빼앗지 않는다.

FocusReturnDialog.tsx
const triggerRef = useRef<HTMLButtonElement>(null);
const dialogRef = useRef<HTMLDialogElement>(null);

function openDialog() { dialogRef.current?.showModal(); }
function closeDialog() {
  dialogRef.current?.close();
}
function restoreFocus() {
  requestAnimationFrame(() => triggerRef.current?.focus());
}

<button ref={triggerRef} onClick={openDialog}>Resolve incident</button>
<dialog ref={dialogRef} aria-labelledby="resolve-title" onClose={restoreFocus}>
  <h2 id="resolve-title">Resolve incident?</h2>
  <button onClick={closeDialog}>Cancel</button>
</dialog>

7. 코드 해부

SPEC

heading은 글자 크기가 아니라 문서 outline이고 label은 control의 이름이다. focus order는 대체로 DOM order를 따르므로 양수 tabindex로 시각 순서를 수선하지 않는다. live region에는 변화한 짧은 text를 넣고 매 render마다 전체 목록을 다시 읽게 하지 않는다. CSS :focus-visible로 keyboard focus를 보이되 outline: none만 남는 규칙을 금지한다.

8. click/network/render end-to-end trace

VERIFIED

keyboard 사용자가 ‘상세 열기’ → button의 이름·expanded state 확인 → panel/dialog commit → heading 또는 첫 유효 control로 focus → network pending을 status로 한 번 알림 → 성공/오류 announcement → 닫기 → 원래 trigger가 여전히 존재하면 focus 복원, 없으면 인접 heading으로 fallback하는 trace를 accessibility snapshot과 activeElement log로 남긴다.

focus-loss장애 실습에서 재현 →

9. 실패 주입

VERIFIED

실패 실습은 modal div에서 Tab을 막고 닫기 button이나 Escape 처리를 제공하지 않는다. Keyboard-only로 진입해 탈출 불가와 background focus를 기록한 뒤, native dialog/검증된 focus trap, Escape와 명시적 close, 반환 focus를 구현한다. trap은 focus를 영원히 가두는 기능이 아니라 modal이 열린 동안 순환시키고 사용자가 닫을 경로를 보장하는 계약이다.

keyboard-trap.lab.tsx
// Broken fixture: no Escape/close path and Tab is cancelled.
<div role="dialog" onKeyDown={event => {
  if (event.key === "Tab") event.preventDefault();
}}>Irreversible operation</div>

// Repair criteria: labeled modal, reachable close control,
// Escape support, contained focus, inert background, focus return.
keyboard-trap장애 실습에서 재현 →

10. 테스트

PRACTICE

eslint/axe 같은 정적·runtime 자동 검사는 missing name, contrast 일부, invalid ARIA를 빠르게 찾는다. 실제 browser에서 keyboard route, 200% zoom과 320 CSS px reflow, forced colors, reduced motion을 검사하고 NVDA+Firefox 또는 VoiceOver+Safari로 목록 검색→상세→수정→오류 복구 task를 수행한다. 자동 검사 0건은 screen reader 검증이 아니다.

11. 성능

PRACTICE

accessibility tree가 불필요하게 거대한 virtualized DOM이나 매 tick 전체 live region 변경으로 흔들리지 않게 한다. 그러나 DOM node를 줄이려고 화면 밖 heading·status를 의미 없이 제거하면 탐색 맥락을 잃는다. animation은 transform/opacity 중심으로 설계하고 prefers-reduced-motion에서는 duration과 이동 거리를 줄이며 상태 변화는 즉시 인지 가능하게 유지한다.

12. 접근성

POLICY

프로젝트 기준은 WCAG 2.2 AA이며 320 CSS px에서 양방향 수평 scroll 없이 주요 콘텐츠가 reflow되어야 한다. 모든 기능은 keyboard로 작동하고 focus는 가려지지 않으며 최소 target은 24×24 CSS px 규정을 충족하되 프로젝트 UI는 가능한 44×44를 권장한다. 의미 있는 error는 text, field association, live announcement를 모두 갖는다.

13. 보안

PRACTICE

접근성 API에 노출되는 hidden text에도 token·개인정보를 넣지 않는다. focus trap과 inert가 authorization boundary는 아니며 DOM에 존재하는 민감 정보는 읽힐 수 있다. 외부 link의 시각 icon만으로 새 창을 알리지 말고 이름에 맥락을 제공하며, custom keyboard shortcut은 입력 중 오작동과 clickjacking 유사 혼란을 피하도록 해제·재매핑 경로를 둔다.

14. 운영

POLICY

접근성 회귀는 feature release blocker로 분류하고 violation에 WCAG criterion, 영향 task, 재현 input 방식, browser/AT 조합을 기록한다. 자동 scan을 PR과 배포 smoke에 두고 월별 manual matrix를 유지한다. 디자인 token 변경은 contrast snapshot과 forced-colors 확인을 거치며 예외에는 소유자와 만료일이 있어야 한다.

15. 완료 조건

POLICY

완료 조건은 semantic outline, label/name audit, 전체 keyboard path, dialog focus lifecycle, status/error announcement, 320px·200% reflow, AA contrast, reduced-motion, 자동 scan, 한 개 이상의 screen reader 실제 task 기록이다. 학습자 task 성공률이 개선된다는 Product 검증은 학습자 테스트 전까지 unverified로 남긴다.

16. 자가시험

POLICY

두 문항을 accessibility tree와 focus trace로 설명한다. 정답을 외웠더라도 실제 keyboard trap과 focus-loss fixture를 재현·수정하지 못하면 모듈을 완료하지 않는다.

2문제 중 0문제 응답

1. modal을 닫은 직후 기본 focus 목적지는?
2. 자동 axe scan이 0건일 때 증명되는 것은?
P09성능현장 지표, main-thread profile, React render, 자원 waterfall을 하나의 원인 지도에 놓는다.150
OUTCOME

현재 Core Web Vitals와 장애 profile을 근거로 budget을 세우고 회귀를 재현·수정한다.

Core Web VitalsLCPINPCLSlong taskReact Profilerunnecessary rendermemoization costbundle splittree shakingimagefontlayout shiftvirtualizationmemory leakevent listener leak

1. 실체와 오해

VERIFIED

성능은 Lighthouse 점수 하나나 평균 load time이 아니다. 2026년 7월 공식 안정 Core Web Vitals는 LCP(loading), INP(interaction responsiveness), CLS(visual stability)이며 FID는 2024년에 INP로 대체되었다. Lab trace는 원인을 재현하고 field/RUM의 75번째 백분위수는 실제 사용자 분포를 보여 주므로 둘 중 하나만으로 결론내리지 않는다.

2. 선수 지식

PRACTICE

Performance panel의 network, main, raster, frames lane과 React DevTools Profiler의 render/commit 구분을 익힌다. HTTP cache와 CPU·network throttling이 무엇을 모사하는지 기록하고, 같은 기기·route·data·cache condition으로 before/after를 비교한다. production build에서 측정하며 개발 모드의 추가 검사 비용을 제품 비용으로 오해하지 않는다.

3. 15분 개념

SPEC

‘good’ 기준은 LCP ≤2.5초, INP ≤200ms, CLS ≤0.1이다. 페이지/사이트 판정은 mobile과 desktop을 구분해 page view의 75번째 백분위수를 사용한다. LCP는 가장 큰 후보의 표시 시점, INP는 사용자 interaction들의 대기·처리·다음 paint 지연을 대표하는 값, CLS는 예상치 못한 layout-shift session window의 누적 점수다. Threshold 통과는 사업 task의 빠름을 완전히 증명하지 않는다.

  • LCP needs improvement: >2.5s–4.0s, poor: >4.0s.
  • INP needs improvement: >200–500ms, poor: >500ms.
  • CLS needs improvement: >0.1–0.25, poor: >0.25.

4. browser/runtime 내부

VERIFIED

브라우저 main thread는 JavaScript task, style, layout, paint preparation을 번갈아 처리한다. Long Tasks API의 long task는 main UI thread를 50ms 이상 점유한 task이며 그 안에서 input이 기다릴 수 있다. Composite 가능한 transform은 일부 작업을 compositor로 넘길 수 있지만 DOM 읽기/쓰기가 섞여 강제 layout을 만들거나 큰 layer memory를 쓰면 이득이 사라진다. React render profile은 browser paint 전체가 아니라 component 작업의 일부다.

5. 최소 실행 코드

SPEC

PerformanceObserver로 field에 가까운 metric entry를 수집하되 지원 여부와 entry lifetime을 처리한다. 원문 URL·사용자 식별자를 metric label로 보내지 않고 route template, release, device class와 함께 sampling한다. 아래 longtask 관찰은 원인 후보를 알려 줄 뿐 어느 component가 책임자인지 자동 판정하지 않는다.

observe-long-tasks.ts
if (PerformanceObserver.supportedEntryTypes.includes("longtask")) {
  const observer = new PerformanceObserver(list => {
    for (const entry of list.getEntries()) {
      reportMetric({ name: "longtask", duration: entry.duration, start: entry.startTime });
    }
  });
  observer.observe({ type: "longtask", buffered: true });
}

6. production 코드

POLICY

route별 field budget과 대표 기기 lab budget을 함께 둔다. 초기 JavaScript, route chunk, LCP resource byte, main-thread time, React commit duration, live DOM node 수를 release artifact로 저장한다. Optimization은 측정된 bottleneck에만 적용하고 memo/useMemo/useCallback의 dependency 비교·memory·복잡성 비용을 포함해 before/after profile로 유지 여부를 결정한다.

performance-budget.ts
export const routeBudget = {
  "/operations": {
    initialJsGzip: 180_000,
    p75: { lcpMs: 2_500, inpMs: 200, cls: 0.1 },
    maxSingleTaskMs: 50,
  },
} as const;

7. 코드 해부

PRACTICE

Unnecessary render는 component 호출 횟수만 많다는 뜻이 아니라 동일 사용자 결과에 비해 측정 가능한 CPU/commit 비용이 생긴 render다. Tree shaking은 ESM의 정적 graph와 package sideEffects 표시에 의존하며 import 문법만 바꾼다고 보장되지 않는다. Bundle split은 초기 bytes를 줄이지만 chunk request와 data waterfall을 늘릴 수 있다. Image의 intrinsic size, responsive srcset/sizes, font preload·subset·font-display를 trace에서 연결한다.

8. click/network/render end-to-end trace

VERIFIED

검색 click의 Event Timing entry → input delay → handler의 filter/sort task → React render flamegraph → commit의 DOM mutation → style/layout → paint → presentation을 하나의 시간축에 놓는다. Network에서는 dynamic chunk와 API가 직렬인지 병렬인지, LCP image가 HTML에서 발견되는지 script 뒤에 발견되는지 확인한다. 한 frame의 문제를 total load 평균으로 희석하지 않는다.

9. 실패 주입

VERIFIED

실패 실습은 100,000개 incident를 click handler에서 동기 정렬·집계해 50ms를 넘는 main-thread task와 interaction 지연을 만든다. 실제 browser trace와 INP candidate를 저장한 뒤 계산을 chunk로 나누거나 Worker로 옮기고, 화면에는 필요한 slice만 계산한다. debounce는 실행 시점을 늦출 뿐 한 번의 긴 task를 짧게 만들지 않는다.

long-task.lab.ts
// Broken fixture: blocks input and the next paint.
button.addEventListener("click", () => {
  const ranked = incidents
    .slice()
    .sort((a, b) => severityScore(b) - severityScore(a));
  renderRows(ranked);
});

// Repair candidates: pre-index, virtualize, split work, or postMessage to a Worker.
main-thread-long-task장애 실습에서 재현 →

10. 테스트

PRACTICE

unit test는 selector 결과와 cleanup을, React Profiler test는 특정 interaction의 commit count를 회귀 guard로 쓸 수 있다. 실제 browser performance test는 production build, 고정 fixture, cold/warm cache를 분리해 trace와 metric을 수집한다. CI 가상 환경의 절대 시간은 흔들리므로 넉넉한 budget과 추세를 사용하고 field RUM으로 배포 판단을 보완한다.

11. 성능

POLICY

Layout-shift 실습은 크기 없는 hero image와 뒤늦게 삽입되는 banner로 CLS를 만든다. width/height 또는 aspect-ratio로 공간을 예약하고 overlay 또는 사용자 interaction 직후의 의도된 이동을 구분한다. 긴 목록은 virtualization하되 stable key, overscan, keyboard 접근, 총 항목 정보와 scroll anchor를 검증한다. Image·font 최적화가 시각 품질·언어 glyph를 훼손하지 않는지도 확인한다.

layout-shift.lab.css
/* Broken: dimensions become known after download. */
.hero img { max-width: 100%; }

/* Repaired: reserve the final geometry. */
.hero { aspect-ratio: 16 / 9; }
.hero img { width: 100%; height: 100%; object-fit: cover; }
layout-shift장애 실습에서 재현 →

12. 접근성

SPEC

Virtualization과 lazy rendering은 screen reader가 전체 목록 크기·현재 위치·focus target을 잃지 않도록 설계한다. Content-visibility로 생략된 영역과 find-in-page 동작을 실제 browser에서 확인한다. Skeleton은 pulse가 필수가 아니며 reduced motion에서는 정적으로 표시하고, 속도를 위해 focus indicator·status text·error announcement를 제거하지 않는다.

13. 보안

PRACTICE

RUM payload는 URL query, DOM text, user ID 같은 민감 값을 기본 수집하지 않는다. Performance trace와 source map의 접근 권한·보존 기간을 정의한다. Third-party performance script 자체가 supply-chain·main-thread 비용이므로 CSP, subresource 정책, sampling으로 제한하고 측정 도구가 측정 대상을 크게 왜곡하는지 비교한다.

14. 운영

POLICY

Memory 실습은 route mount마다 window listener와 interval을 추가하고 cleanup하지 않아 heap과 중복 callback이 증가하게 한다. 반복 탐색 후 heap snapshot과 listener count를 남기고 effect cleanup, stable listener identity, AbortSignal을 적용한다. Release dashboard는 p75 LCP/INP/CLS를 route·device·region·version별로 보고 sample 수와 변화량을 함께 표시한다.

memory-leak.lab.tsx
useEffect(() => {
  const controller = new AbortController();
  const timer = window.setInterval(sampleHealth, 5_000);
  window.addEventListener("resize", measureLayout, { signal: controller.signal });
  return () => {
    controller.abort();
    window.clearInterval(timer);
  };
}, []);
memory-leak장애 실습에서 재현 →

15. 완료 조건

POLICY

완료 조건은 공식 정의·날짜가 기록된 CWV 표, production browser trace, long-task fail/fix, layout-shift fail/fix, render profile, bundle report, memory/listener 반복 test, route budget이다. Function은 실습 재현, Quality는 budget과 회귀 test 통과, Product는 실제 사용자 task가 빨라졌는지이며 마지막은 field/학습자 데이터 전까지 unverified다.

16. 자가시험

POLICY

두 문항에는 공식 threshold와 trace의 인과관계를 함께 답한다. 숫자를 외워도 long task를 profile에서 찾고 React render·layout·paint 중 책임 구간을 분리하지 못하면 다시 실습한다.

2문제 중 0문제 응답

1. 현재 ‘good’ Core Web Vitals 조합은?
2. 100ms 동기 정렬을 debounce하면 무엇이 보장되는가?
P10보안브라우저 trust boundary와 출력 문맥을 따라 frontend 위협을 재현하고 차단한다.150
OUTCOME

XSS·CSRF·token·redirect·frame·supply-chain 위험을 threat model과 검증 가능한 control로 연결한다.

XSSDOM injectiondangerouslySetInnerHTMLCSRFcookie attributestoken storageCSPopen redirectclickjackingdependency supply chainsource mapsensitive logging

1. 실체와 오해

PRACTICE

React가 text child를 escape한다는 사실은 ‘React app은 XSS에 안전하다’는 뜻이 아니다. dangerouslySetInnerHTML, DOM API, URL, CSS, third-party widget, serialized bootstrap data는 서로 다른 출력 문맥을 가진다. 또한 frontend에 들어간 secret은 사용자에게 전달된 secret이다. 보안은 sanitizer 하나가 아니라 입력·권한·출력 encoding·browser policy·운영 감시의 겹친 control이다.

2. 선수 지식

PRACTICE

origin과 site의 차이, cookie 자동 전송, same-origin policy, CORS가 읽기 권한을 제어하는 방식, HTML/attribute/URL/JavaScript 문맥을 복습한다. 공격자 능력을 ‘사용자가 만든 문자열 저장’, ‘피해자가 로그인된 상태로 link 방문’, ‘dependency 배포 손상’처럼 명시한다. 위험도는 payload가 보이는지보다 자산·권한·영향으로 판정한다.

3. 15분 개념

SPEC

XSS는 신뢰하지 않은 데이터가 executable 문맥이 되어 피해자의 origin 권한으로 실행되는 문제다. CSRF는 브라우저가 인증 cookie를 자동 포함하는 성질을 이용해 사용자가 의도하지 않은 state change를 보내는 문제다. CSP는 허용할 resource와 script 실행을 제한하는 defense-in-depth이며 XSS 원인 제거를 대체하지 않는다. HttpOnly cookie는 JavaScript 읽기를 막지만 자동 전송과 CSRF 가능성은 별도다.

4. browser/runtime 내부

SPEC

HTML parser는 문자열이 삽입된 위치에 따라 text, attribute, script, URL token으로 해석한다. element.textContent와 React text child는 markup으로 parse하지 않지만 innerHTML은 parser를 다시 호출한다. Cookie의 Secure는 HTTPS 전송, HttpOnly는 script 접근 차단, SameSite는 cross-site 요청 전송 범위를 제어하고 Path/Domain은 기밀 경계가 아니다. localStorage 값은 같은 origin script가 읽을 수 있어 XSS에 노출된다.

5. 최소 실행 코드

SPEC

사용자 문자열은 기본 React interpolation으로 text로 render하고 URL은 허용 origin·protocol을 parse해 검증한다. Redirect는 startsWith 같은 문자열 검사 대신 URL을 정규화한 뒤 same-origin path만 허용한다. 외부 URL이 꼭 필요하면 server-side allowlist와 명시적 사용자 표시를 둔다.

safe-redirect.ts
export function safeReturnTo(raw: string | null, origin: string): string {
  if (!raw) return "/";
  const candidate = new URL(raw, origin);
  const expected = new URL(origin);
  if (candidate.origin !== expected.origin) return "/";
  if (!candidate.pathname.startsWith("/")) return "/";
  return candidate.pathname + candidate.search + candidate.hash;
}

6. production 코드

POLICY

세션은 server가 설정한 Secure; HttpOnly; SameSite cookie를 기본 후보로 하고 짧은 만료·rotation·revocation을 설계한다. Token storage는 위협 모델로 결정하며 localStorage를 ‘편해서 안전’하다고 선택하지 않는다. State-changing 요청은 CSRF token 또는 검증된 Origin/Fetch Metadata 정책, authorization, schema validation, idempotency를 통과한다. Rich HTML은 검증된 sanitizer와 고정 configuration을 거쳐 별도 component에서만 출력한다.

security-headers.ts
export const securityHeaders = {
  "Content-Security-Policy": [
    "default-src 'self'",
    "script-src 'self' 'nonce-{REQUEST_NONCE}'",
    "object-src 'none'",
    "base-uri 'none'",
    "frame-ancestors 'none'",
  ].join("; "),
  "Referrer-Policy": "strict-origin-when-cross-origin",
  "X-Content-Type-Options": "nosniff",
};

// Generate a cryptographically random nonce per response; never ship this placeholder.

7. 코드 해부

PRACTICE

dangerouslySetInnerHTML이라는 긴 이름은 trust decision을 call site에서 보이게 한다. 그것을 SafeHtml component 뒤로 모아 sanitizer version, allowed tags/attributes, link protocol policy, test corpus를 한곳에서 관리한다. CSP는 Report-Only로 violation을 관찰한 뒤 nonce/hash 기반 strict policy로 이동한다. frame-ancestors는 clickjacking을 제한하고 legacy X-Frame-Options는 필요한 호환 범위에서 보조한다.

8. click/network/render end-to-end trace

VERIFIED

저장형 XSS trace는 attacker input → API schema → database → detail response → React prop → sink → HTML parser/DOM → CSP enforcement 순서로 데이터와 control을 표시한다. CSRF trace는 malicious site → victim browser의 cookie inclusion → mutation endpoint → Origin/token/authorization decision이다. CORS preflight 유무는 CSRF 방어 성공을 뜻하지 않으므로 request가 전송됐는지와 response를 읽을 수 있는지를 분리한다.

9. 실패 주입

VERIFIED

격리된 fixture에 `<img src=x onerror=...>`와 `javascript:` link를 넣고 unsafe preview가 실행 또는 위험 DOM을 만드는 증거를 남긴다. 실제 credential이나 외부 endpoint는 사용하지 않는다. 기본 text rendering으로 바꾸거나 유지보수되는 sanitizer+protocol allowlist를 적용하고 CSP report도 확인한다. 단순 regex tag 제거는 parser 변형과 encoding을 다루지 못하므로 수정안이 아니다.

xss-fixture.lab.tsx
const fixture = '<img src=x onerror="document.body.dataset.xss=1">';

// Broken, isolated lab only:
<section dangerouslySetInnerHTML={{ __html: fixture }} />

// Safe default for plain text:
<section>{fixture}</section>

// Rich text requires a reviewed sanitizer policy before this sink.
xss-fixture장애 실습에서 재현 →

10. 테스트

PRACTICE

unit test는 safe redirect와 sanitizer corpus의 encoded·nested payload를 검사한다. Integration test는 CSRF/authorization/idempotency control을 실제 server에 요청하고, browser E2E는 CSP violation, cookie 속성, frame embedding 차단, DOM sink를 확인한다. Dependency audit 결과는 exploitability와 runtime reachability를 검토하며 단순 취약점 개수만 quality로 사용하지 않는다.

11. 성능

PRACTICE

큰 HTML을 client에서 반복 sanitize하면 interaction을 막을 수 있지만 검증을 생략할 이유가 되지 않는다. Content 생성 시 sanitize하고 policy/version을 저장한 뒤 출력 시 trust 조건을 재확인하는 경로를 검토한다. CSP report와 security telemetry는 sampling·rate limit으로 report storm을 막고 third-party script 제거가 보안과 main-thread 성능을 동시에 개선하는지 측정한다.

12. 접근성

SPEC

Sanitizer allowlist가 heading, list, table caption, language, link name 같은 의미를 불필요하게 지우지 않도록 fixture를 둔다. CAPTCHA나 추가 인증은 WCAG 2.2 Accessible Authentication을 고려하고 대안을 제공한다. Security timeout은 만료 전에 접근 가능한 경고와 연장 방법을 제공하며 화면만 가리고 session을 유지하는 가짜 logout을 만들지 않는다.

13. 보안

POLICY

Dependency는 exact version과 lockfile로 고정하고 clean install에서 integrity를 검증한다. 설치 script와 새 maintainer/package를 review하며 registry provenance/SBOM을 가능한 범위에서 보존한다. Production source map은 공개 asset으로 자동 노출하지 않고 error service에 인증 업로드한 뒤 release와 연결한다. Log에는 password, session/token, Authorization header, full query, 민감 form payload를 남기지 않는다.

14. 운영

POLICY

Threat model에는 asset, actor, entry point, trust boundary, control owner, residual risk를 기록한다. CSP report, auth anomaly, dependency advisory를 release와 연계하고 incident 시 token revocation, dependency rollback, key rotation, cache purge 순서를 runbook으로 검증한다. Source map 접근 audit와 보존 기간을 운영하고 security header 변경은 실제 배포 응답에서 확인한다.

15. 완료 조건

POLICY

완료 조건은 XSS fail/fix fixture, sink inventory, CSRF integration test, cookie·CSP·frame header browser 증거, open-redirect test, dependency/lockfile/SBOM 기록, 비공개 source map 확인, log redaction test다. 자동 scanner 통과만으로 authorization이나 운영 대응이 검증되었다고 말하지 않는다.

16. 자가시험

POLICY

두 문항을 공격 trace와 browser enforcement 지점으로 설명한다. 오답이면 XSS fixture와 cross-site mutation 요청을 격리 환경에서 다시 관찰하고 CORS·CSRF·CSP의 역할을 따로 표로 만든다.

2문제 중 0문제 응답

1. HttpOnly session cookie가 직접 막는 것은?
2. CSP의 올바른 역할은?
P11테스트와 운영빠른 피드백부터 실제 browser·canary·rollback까지 한 release evidence chain을 만든다.150
OUTCOME

테스트 경계를 의도적으로 선택하고 접근성·성능·오류 관측을 배포 제어와 연결한다.

unit testcomponent testintegration testE2Ereal browsernetwork mockingvisual regressionaccessibility testperformance budgeterror monitoringsource map uploadfeature flagcanaryrollback

1. 실체와 오해

PRACTICE

테스트 수가 많거나 coverage가 100%라는 사실은 사용자 흐름이 작동한다는 증거가 아니다. Unit은 작은 계산, component는 DOM 계약, integration은 실제 경계들의 조합, E2E는 배포에 가까운 browser 흐름을 검증하며 각 층에는 고유한 blind spot이 있다. 운영 telemetry도 test를 대체하지 않고 test가 보지 못한 분포와 release 이상을 감지한다.

2. 선수 지식

POLICY

요구사항을 observable behavior와 위험으로 바꾸고 각 test가 사용하는 runtime(browser, DOM emulator, Node, worker)을 표시한다. Deterministic fixture, clock, random ID, network control 지점을 정한다. 실패 시 owner가 재현할 artifact—screenshot, trace, console, request/response, seed, release SHA—를 test 설계의 일부로 취급한다.

3. 15분 개념

PRACTICE

가장 작은 신뢰 가능한 경계에서 실패를 잡는다. Pure reducer와 parser는 unit, form의 accessible behavior는 component, API schema·DB·auth 조합은 integration, login→검색→상세→optimistic 수정→오류 복구는 실제 browser E2E가 적합하다. Visual regression은 pixel/DOM 의미를 보완하지만 interaction을 대신하지 않고, accessibility automation도 keyboard와 screen reader task를 대신하지 않는다.

4. browser/runtime 내부

VERIFIED

Playwright 같은 browser runner는 실제 browser process와 automation protocol로 page를 조작하며 actionability와 web-first assertion을 기다린다. DOM emulator는 layout, paint, real navigation, cookie policy, browser cache를 완전히 구현하지 않는다. Service Worker·proxy·route interception의 network mock은 안정적 실패 주입에 유용하지만 DNS/TLS/CDN/cache/stream timing과 backend serialization bug를 생략하므로 실제 server test를 별도로 둔다.

5. 최소 실행 코드

POLICY

최소 E2E는 role/label 기반 locator로 실제 task를 수행하고 결과와 URL state를 함께 확인한다. data-testid는 의미 있는 query가 불가능한 내부 경계에 제한한다. `waitForTimeout` 대신 response, URL, heading, status처럼 사용자에게 관찰 가능한 조건을 기다린다.

operations.spec.ts
test("search state survives reload", async ({ page }) => {
  await page.goto("/operations");
  await page.getByRole("searchbox", { name: "Search incidents" }).fill("latency");
  await expect(page).toHaveURL(/query=latency/);
  await expect(page.getByRole("heading", { name: /latency/i })).toBeVisible();

  await page.reload();
  await expect(page.getByRole("searchbox")).toHaveValue("latency");
});

6. production 코드

POLICY

CI gate는 format → lint → typecheck → unit/component/integration → production build → browser E2E → accessibility → 대표 performance/failure test → source validation 순서와 artifact를 고정한다. Browser project는 최소 Chromium과 제품 지원 범위의 Firefox/WebKit을 포함하고 mobile viewport는 320px를 별도 실행한다. Flaky test는 무한 retry로 숨기지 않고 owner·격리 기한·원인 issue를 가진다.

playwright.config.ts
export default defineConfig({
  use: { trace: "retain-on-failure", screenshot: "only-on-failure" },
  projects: [
    { name: "chromium", use: devices["Desktop Chrome"] },
    { name: "firefox", use: devices["Desktop Firefox"] },
    { name: "webkit", use: devices["Desktop Safari"] },
    { name: "mobile-320", use: { viewport: { width: 320, height: 720 } } },
  ],
});

7. 코드 해부

PRACTICE

Arrange는 최소 fixture와 권한, Act는 사용자가 하는 한 intent, Assert는 accessible 결과와 durable side effect를 표현한다. 내부 hook state나 implementation class를 assert하면 refactor 비용이 커진다. Contract test는 frontend client와 server schema의 공유 예제를 검증하지만 generated type은 runtime response를 보증하지 않으므로 malformed fixture도 포함한다. Snapshot은 사람이 review할 작은 안정 영역에만 둔다.

8. click/network/render end-to-end trace

VERIFIED

배포 가능한 commit SHA → clean install lockfile → build asset manifest/source map → test report → immutable artifact → canary cohort → error/CWV/business guardrail → rollout 또는 rollback을 하나의 release ID로 연결한다. E2E 실패는 Playwright trace의 action, DOM snapshot, network, console을 server request ID와 연결한다. 관측 불가능한 실패는 단순 retry 전에 instrumentation 결함으로 분류한다.

9. 실패 주입

VERIFIED

실패 실습은 목록 API를 offline과 응답 지연 상태로 만들고 timeout 후 late response도 도착시킨다. UI가 영구 spinner, 중복 toast, stale commit을 만드는지 실제 browser에서 기록한다. AbortSignal과 명시적 deadline을 전달하고 offline·timeout·abort·HTTP error를 구분하며 retry button과 마지막 성공 snapshot을 제공한다. Network mock 수리 후 실제 server 지연 test도 통과해야 한다.

offline-timeout.spec.ts
test("recovers from a timed-out incident list", async ({ page }) => {
  await page.route("**/api/incidents", async route => {
    await new Promise(resolve => setTimeout(resolve, 5_000));
    await route.abort("timedout");
  });
  await page.goto("/operations");
  await expect(page.getByRole("alert")).toContainText("timed out");
  await expect(page.getByRole("button", { name: "Retry" })).toBeFocused();
});
offline-timeout장애 실습에서 재현 →

10. 테스트

POLICY

Test matrix는 auth success/expiry/concurrent refresh, list/detail/search/URL reload, form validation/double submit, optimistic success/rollback, streaming progress, offline/timeout/retry, keyboard/dialog/focus를 포함한다. Visual baseline은 OS·font·browser를 고정하고 의도된 변화는 human review로 승인한다. Accessibility scan은 주요 state마다 실행하며 production build를 대상으로 critical/serious 위반 0을 gate로 둔다.

11. 성능

POLICY

대표 route의 initial JS, image/font bytes, LCP/INP/CLS, long task, interaction trace를 budget으로 둔다. CI lab threshold는 field official threshold보다 변동성을 고려해 설계하되 느슨하게 무한 확장하지 않는다. Bundle diff와 trace를 artifact로 보존하고 p75 field regression이 canary control 대비 허용 범위를 넘으면 rollout을 중단한다. 한 synthetic 점수만 gate로 쓰지 않는다.

12. 접근성

POLICY

Component test는 role/name/state와 오류 association을, browser automation은 axe·keyboard·focus order를, 수동 test는 screen reader announcement와 이해 가능한 흐름을 담당한다. Color contrast와 reflow는 실제 computed style/viewport를 포함해 확인한다. 접근성 오류는 screenshot만이 아니라 현재 activeElement, accessibility snapshot, 입력 방식, WCAG criterion을 artifact에 남긴다.

13. 보안

POLICY

Test fixture와 trace에 production token·PII를 복사하지 않고 synthetic account와 redaction을 사용한다. CI secret은 fork PR에 노출하지 않으며 least privilege와 짧은 수명을 적용한다. Source map은 build 후 monitoring service에 인증 업로드되고 public asset 요청은 실패하는지 검사한다. E2E가 인증을 우회하는 test-only endpoint는 production bundle과 route에 포함되지 않음을 build artifact에서 검증한다.

14. 운영

PRACTICE

Error monitoring은 handled/unhandled, route, release, browser를 기록하고 message가 아닌 stable fingerprint와 source map으로 원본 위치를 복원한다. Feature flag에는 owner, 목적, default, kill switch, cohort, 만료일이 있다. Canary는 작은 대표 cohort에 동일 immutable artifact를 노출하고 technical·product guardrail을 비교한다. Rollback은 code, schema compatibility, cache, flag 상태를 포함해 목표 시간 안에 rehearsal한다.

15. 완료 조건

POLICY

완료 조건은 clean install부터 production build까지의 명령 log, unit/component/integration 결과, 실제 3-engine browser E2E, accessibility report, visual diff, performance budget, offline-timeout fail/fix, source map 비공개 업로드, canary/rollback rehearsal이다. Function·Quality 증거를 분리하며 실제 학습자가 더 빠르고 정확히 capstone을 운영한다는 Product 검증은 학습자 test 전까지 unverified다.

16. 자가시험

POLICY

두 문항을 test runtime의 능력과 release artifact chain으로 설명한다. 틀리면 같은 offline 오류를 route interception과 실제 지연 server에서 각각 실행해 무엇이 검증되고 생략됐는지 비교한다.

2문제 중 0문제 응답

1. route interception 기반 network mock이 직접 검증하지 못하는 것은?
2. 안전한 canary의 핵심은?
FAULT INJECTION / 안전장치 포함

브라우저를 망가뜨리지 않는 장애 실습

실패를 재현한 뒤 같은 입력으로 불변식을 회복한다.

SYMPTOM두 비동기 증가가 한 번만 반영된다.

INVARIANT이전 state에 의존하면 함수형 업데이트를 쓴다.

> visible counter = 0

> 실행 대기

REAL APPLICATION / CAPSTONE

Capstone · 실시간 운영 대시보드

인증 경계, URL 상태, race 취소, 낙관적 업데이트, stream, 복구 UX를 한 화면에서 검증한다.

CAPSTONE · 실제 실행 흐름

실시간 운영 대시보드

검색 → URL → 취소 가능한 요청 → render/commit → 낙관적 변경 → 서버 조정까지 한 화면에서 추적합니다.

PROJECT POLICY모든 변경 요청에 idempotency key를 붙이고, UI의 낙관적 상태를 서버 결과로 조정합니다.

교육용 인증 시뮬레이션

이 상태는 메모리에만 있습니다. 실제 세션, 쿠키, 권한 검사를 대신하지 않습니다.

데모 운영자로 로그인됨: Min Kim (demo)

네트워크 장애 실습

모드는 결정적 fake transport에만 적용됩니다. 실제 네트워크 상태를 바꾸지 않습니다.

01 · URL STATE + ABORT

인시던트 탐색

5개 인시던트
검색어는 ?q=에 저장됩니다. 빠르게 다시 검색하면 앞선 요청을 abort하고 늦은 응답을 무시합니다.

02 · LIST

인시던트 목록

03 · DETAIL + STREAM

결제 승인 지연

INC-2048

북미 결제 승인 p95가 목표치 800ms를 초과했습니다.

심각도
치명적
상태
원인 확인
서비스
payments-api
담당 팀
Checkout
시작
버전
7

스트리밍 상태

연결 중

첫 상태 이벤트를 기다립니다.

타임라인

  1. 지연 시간 예산 경보가 열렸습니다.

  2. 원인을 외부 승인 제공자의 연결 풀로 좁혔습니다.

인시던트 확인

아직 확인하지 않음
최소 5자, 최대 240자. 민감 정보는 입력하지 마세요.0/240자

GLOSSARY / SEARCHABLE

실행 흐름 용어집

정의가 모호해지는 순간 다시 실행 단계에 고정한다.

실행 컨텍스트
JavaScript 코드 평가에 필요한 현재 함수, lexical environment, this binding 등의 실행 상태 묶음.
렉시컬 환경
식별자와 binding을 저장한 environment record 및 바깥 환경 참조로 이루어진 구조.
호출 스택
현재 실행 중인 함수 frame이 후입선출로 쌓이는 모델. 깊은 동기 재귀는 stack overflow를 만들 수 있다.
객체·closure 등이 할당되고 garbage collector가 도달 가능성을 기준으로 회수하는 메모리 영역의 일반적 설명.
클로저
함수가 생성될 때의 lexical environment binding에 계속 접근할 수 있는 함수와 환경의 결합.
호이스팅
선언이 물리적으로 이동하는 현상이 아니라 환경 생성·선언 instantiation 단계에서 binding이 먼저 만들어져 보이는 결과.
프로토타입
객체의 property lookup이 자기 자신에서 실패할 때 이어서 탐색하는 다른 객체와의 내부 연결.
this 바인딩
호출 형태, strict mode, constructor, 명시적 bind/call/apply, arrow 함수의 lexical capture에 따라 정해지는 값.
태스크
Event loop가 한 번 선택해 실행하는 script, event, timer callback 같은 작업 단위.
마이크로태스크
현재 task가 끝난 뒤 다음 task 전에 checkpoint에서 비워지는 Promise reaction 등의 queue 작업.
AbortController
AbortSignal을 통해 fetch 등 협력하는 비동기 작업에 취소 의도를 전파하는 browser API.
처리되지 않은 Promise 거부
Promise rejection에 적시에 handler가 연결되지 않아 host가 보고할 수 있는 상태.
DNS
사람이 읽는 host name을 연결에 사용할 IP address 등으로 해석하는 분산 naming system.
TCP
순서 있고 신뢰 가능한 byte stream을 제공하는 transport protocol. HTTP/3는 대신 QUIC 위에서 동작한다.
TLS
연결 상대 인증, 기밀성, 무결성을 제공하는 cryptographic transport protocol.
HTTP 캐시
Freshness와 validation header에 따라 response 재사용 여부를 결정하는 HTTP 계층 저장소.
DOM
문서를 node tree와 조작 API로 표현하는 platform model. HTML source 문자열 자체와 동일하지 않다.
CSSOM
CSS rule과 style 정보를 객체 model로 표현하는 browser API와 구조.
렌더 트리
DOM과 계산된 style로부터 실제 layout/paint에 참여하는 시각 객체를 구성하는 개념 모델.
레이아웃
box의 크기와 위치를 계산하는 rendering 단계. Geometry를 읽고 쓰는 방식에 따라 반복될 수 있다.
페인트
배경, text, border 등 시각적 draw command를 생성하는 단계.
컴포지트
이미 paint된 layer를 변환·결합해 최종 frame을 만드는 rendering 단계.
CORS
서버의 HTTP header로 다른 origin의 script가 response를 읽을 수 있는 조건을 표현하는 browser protocol.
웹 워커
DOM에 직접 접근하지 않는 별도 worker 실행 환경에서 JavaScript를 수행하는 browser primitive.
구조적 타이핑
명시적 선언 이름보다 필요한 member 구조의 호환성으로 assignability를 판단하는 TypeScript 방식.
타입 추론
명시 annotation 없이 initializer, control flow, usage 문맥에서 compiler가 type을 계산하는 과정.
유니언과 인터섹션
Union은 여러 가능성 중 하나, intersection은 여러 type 요구를 동시에 만족하는 값을 나타낸다.
내로잉
typeof, discriminant, predicate 등의 runtime check와 control flow로 넓은 type을 더 구체화하는 분석.
판별 유니언
공통 literal field로 각 variant를 안전하게 구별하고 exhaustive 처리를 가능하게 하는 union.
제네릭
구체 type을 나중에 매개변수로 받아 관계를 보존하는 재사용 type/function 선언.
조건부·매핑 타입
Conditional type은 type 관계에 따라 결과를 선택하고 mapped type은 key 집합을 순회해 새 property type을 만든다.
변성
generic type의 type argument 관계가 전체 type의 assignability에 어떤 방향으로 전달되는지 설명하는 성질.
unknown·never·any
unknown은 검사 전 사용을 제한하고, never는 가능한 값이 없음을, any는 type checking을 광범위하게 우회함을 나타낸다.
타입 소거
대부분의 TypeScript type annotation이 JavaScript emit에 runtime value로 남지 않는 성질.
런타임 검증
network·storage 등 외부 값을 실행 중 schema로 검사해 안전한 domain value로 변환하는 과정.
생성 타입
schema나 API 정의에서 도구가 만든 compile-time 표현. 실제 배포 응답과 drift하거나 runtime 검증을 생략할 수 있다.
React 엘리먼트
component type, props, key 등 UI description을 담는 불변에 가까운 일반 객체이며 DOM node 자체가 아니다.
렌더
React가 component를 호출하고 다음 UI description을 계산하는 단계. DOM mutation을 의미하지 않는다.
재조정
이전과 다음 element tree를 비교해 유지·삽입·삭제·갱신할 identity와 작업을 정하는 과정.
Fiber
React가 component 작업과 관계를 추적하는 내부 구현 구조. 공개 API 계약처럼 field 세부에 의존하면 안 된다.
커밋
React가 계산된 변경을 host DOM에 적용하고 관련 lifecycle/effect 단계를 진행하는 phase.
같은 부모 아래 sibling의 identity를 React가 대응시키는 데 쓰는 안정된 값.
상태 스냅샷
한 render의 handler와 closure가 그 render 시점의 state 값을 읽는 React model.
업데이트 큐
State setter로 등록된 update들이 다음 render 계산에 적용되도록 보관되는 내부 개념.
배칭
여러 state update를 묶어 불필요한 중간 render/commit을 줄이는 React 처리 방식.
오래된 클로저
이전 render에서 생성된 closure가 당시의 state/prop snapshot을 계속 읽어 최신 의도와 어긋나는 상태.
이펙트
React tree를 network, timer, event target 같은 외부 system과 동기화하는 setup/cleanup lifecycle.
ref·context·error boundary
ref는 render 밖의 mutable reference, context는 하위 tree에 값 전달, error boundary는 render 오류 fallback을 담당하는 서로 다른 도구다.
로컬 상태
한 component subtree의 interaction에 소유권이 있고 URL이나 server와 공유할 필요가 없는 state.
서버 상태
서버가 authority이며 client가 비동기로 복제·cache하는 원격 resource 상태.
URL 상태
검색어, filter, sort, page처럼 bookmark·share·navigation history에 포함해야 하는 state.
폼 상태
입력 값, dirty, validation, pending, server error처럼 제출 lifecycle에 속한 상태.
파생 상태
기존 source of truth에서 계산 가능해 별도 동기화 저장을 피해야 하는 값.
정규화 상태
entity를 ID별로 한 번 저장하고 관계는 ID reference로 나타내 중복 업데이트를 줄이는 구조.
리듀서
현재 state와 action을 받아 다음 state를 계산하는 순수 전이 함수 모델.
캐시 무효화
Cache entry가 더 이상 사용 가능하지 않음을 marking·removal·revalidation으로 반영하는 정책.
낙관적 업데이트
서버 성공 전에 예상 결과를 UI에 적용하고 확정 또는 rollback하는 기법.
경쟁과 취소
여러 비동기 결과의 완료 순서가 의도와 다를 수 있는 상황과 더 이상 필요한 작업에 중단 의도를 전달하는 처리.
fetch
Request를 보내고 HTTP status와 무관하게 response 도착 시 Promise를 resolve하는 browser network API.
HTTP 오류
4xx/5xx처럼 protocol상 응답은 도착했지만 application이 실패로 처리해야 하는 status. fetch rejection과 다르다.
타임아웃
정해진 deadline 안에 작업이 끝나지 않아 호출자가 중단·실패로 전환하는 application policy.
재시도
실패 후 같은 intent를 다시 시도하는 정책. Backoff, jitter, 최대 횟수, idempotency가 필요하다.
중복 제거
동일 resource나 mutation intent의 동시·반복 요청을 하나로 합치거나 결과를 재사용하는 처리.
페이지네이션·프리페치
큰 결과를 cursor/page로 나누고 다음에 필요할 가능성이 높은 데이터·code를 의도 전에 가져오는 기법.
스트리밍
전체 payload 완료를 기다리지 않고 도착하는 chunk를 점진적으로 소비·표시하는 전달 방식.
오프라인
Network reachability가 없거나 요청이 전달되지 않는 상태. HTTP error와 별도 복구 경로가 필요하다.
동시 인증 갱신
여러 401 응답이 동시에 하나의 token refresh를 요구할 때 refresh를 single-flight로 공유해야 하는 상황.
멱등성
같은 intent를 반복해도 durable effect가 한 번 적용된 것과 같은 결과가 되게 하는 성질·protocol.
클라이언트 사이드 렌더링
초기 shell 이후 browser JavaScript가 UI의 주된 HTML/DOM을 만드는 전달 방식.
서버 사이드 렌더링
요청 처리 시 server environment가 React tree 등에서 HTML을 생성해 보내는 방식.
정적 생성
배포 전 또는 재검증 시점에 route HTML/artifact를 미리 만들어 재사용하는 계열.
하이드레이션
서버가 만든 기존 DOM에 React가 상태와 event 처리를 연결하는 과정.
하이드레이션 불일치
서버 HTML과 client 첫 render 결과가 달라 hydration의 동일성 가정이 깨진 상태.
서버/클라이언트 경계
Server에서만 실행되는 graph와 browser에 전달되어 실행되는 graph 사이의 직렬화·bundle 경계.
스트리밍 HTML
Server가 완성된 문서 전체를 기다리지 않고 shell과 후속 HTML segment를 점진적으로 보내는 방식.
번들·데이터 워터폴
앞선 code/data가 끝나야 다음 dependency가 발견되어 요청이 직렬화되는 지연 구조.
네이티브 폼
Browser의 submit, validation, FormData, keyboard, autofill 동작을 제공하는 HTML form 계약.
제어 입력
현재 value가 React state에서 내려오고 change handler가 다음 값을 갱신하는 input.
비제어 입력
현재 value를 DOM이 소유하며 제출 시 FormData나 ref로 읽는 input.
제약 조건 검증
required, pattern, min/max 등 HTML constraint를 browser가 검사하는 API와 algorithm.
대기 상태
제출 intent가 시작됐지만 최종 성공·실패가 아직 확정되지 않은 interaction state.
중복 제출
같은 사용자 intent가 두 개 이상의 mutation으로 처리될 수 있는 race/운영 장애.
더티 상태
현재 form value가 저장 또는 초기 canonical value와 달라 미저장 변경이 있는 상태.
탐색 중단
미저장 form을 떠나려는 navigation을 감지해 보존·폐기 선택을 제공하는 흐름.
시맨틱 HTML
내용의 역할과 구조에 맞는 native element를 사용해 browser와 보조 기술에 의미를 전달하는 markup.
접근 가능한 이름
보조 기술이 control을 식별할 때 사용하는 programmatically 계산된 이름.
포커스 순서
Keyboard focus가 interactive element를 이동하는 논리적 순서로 대체로 DOM order를 따른다.
포커스 복원
Dialog·panel·route transition 뒤 사용자의 이전 맥락 또는 논리적 다음 지점으로 focus를 되돌리는 처리.
다이얼로그
배경 interaction을 제한하고 label, initial focus, close, focus return 계약을 갖는 집중 interaction surface.
라이브 리전
Focus를 이동하지 않고 동적으로 바뀐 상태를 보조 기술에 announcement하도록 표시한 영역.
동작 감소
사용자의 prefers-reduced-motion 선호에 따라 불필요한 animation 이동·duration을 줄이는 대응.
WCAG AA
Web Content Accessibility Guidelines의 A와 AA success criteria를 만족하는 목표 수준. 자동 scan 점수와 동일하지 않다.
최대 콘텐츠풀 페인트
Viewport 안의 가장 큰 적격 content element가 표시되는 loading 지표. Good 기준은 75번째 백분위수에서 2.5초 이하.
Interaction to Next Paint
Page interaction의 input delay, processing, 다음 paint까지를 대표하는 responsiveness 지표. Good은 200ms 이하.
누적 레이아웃 이동
예상치 못한 layout shift의 session-window 점수를 나타내는 visual stability 지표. Good은 0.1 이하.
롱 태스크
Main UI thread를 50ms 이상 점유해 input과 rendering을 지연시킬 수 있는 task.
메모이제이션
같은 입력의 계산·component 결과를 재사용하는 최적화로 비교·memory·복잡성 비용도 가진다.
트리 셰이킹
Static module graph 분석으로 사용되지 않는 export를 production bundle에서 제거하는 build optimization.
가상화
긴 collection 중 보이는 범위 주변만 DOM으로 render해 work와 memory를 제한하는 기법.
메모리 누수
더 이상 필요 없는 객체·listener·timer가 reachable하게 남아 반복 사용 중 memory와 작업이 증가하는 장애.
교차 사이트 스크립팅
신뢰하지 않은 데이터가 executable browser 문맥으로 해석되어 피해 origin 권한으로 동작하는 injection 취약점.
DOM 삽입
신뢰하지 않은 문자열을 innerHTML, URL, script 같은 위험 sink에 넣어 parser/실행 의미를 만드는 행위.
사이트 간 요청 위조
Browser가 인증 cookie를 자동 전송하는 성질을 악용해 사용자가 의도하지 않은 mutation을 보내는 공격.
쿠키 속성
Secure, HttpOnly, SameSite, Domain, Path, Max-Age 등 cookie 전송·접근·수명을 제어하는 지시자.
콘텐츠 보안 정책
허용된 resource origin과 script 실행 방식을 response policy로 제한하는 browser defense-in-depth.
오픈 리다이렉트
공격자가 목적지를 제어해 신뢰되는 origin의 link가 외부 phishing 위치로 사용될 수 있는 취약점.
클릭재킹
공격자 frame/UI가 대상을 시각적으로 속여 피해자가 의도하지 않은 control을 누르게 하는 공격.
공급망 보안
Dependency, registry, maintainer, build·publish 과정의 손상이 application에 전달되는 위험과 방어.
소스 맵
변환·minify된 code 위치를 원본 source 위치에 대응시키는 metadata로 debugging에 유용하지만 source 노출 정책이 필요하다.
로그 마스킹
Token, password, 개인정보 등 금지된 값을 telemetry에 저장하기 전 제거하거나 비식별화하는 처리.
단위 테스트
작은 함수·module을 빠르고 격리된 조건에서 검증하는 test.
컴포넌트 테스트
Component를 DOM 환경에 render해 사용자 관찰 가능한 role, event, state를 검증하는 test.
통합 테스트
Schema, database, auth, component 등 둘 이상의 실제 경계를 함께 검증하는 test.
엔드투엔드 테스트
실제 또는 배포에 가까운 browser와 server를 통해 전체 사용자 task 경로를 검증하는 test.
네트워크 모킹
Client가 받는 response·failure를 제어해 결정적 상태를 만들지만 실제 network/server 일부를 생략하는 test 기법.
시각 회귀
고정 환경의 screenshot을 승인된 baseline과 비교해 예상하지 못한 pixel 차이를 찾는 test.
성능 예산
Byte, timing, metric, task duration 등에 설정한 허용 한계와 release gate.
오류 모니터링
Runtime error를 release, route, browser, stack/source map과 연결해 수집·grouping·alert하는 운영 체계.
기능 플래그
Code 배포와 기능 노출을 분리해 cohort, default, kill switch로 behavior를 제어하는 운영 장치.
카나리 배포
동일 release artifact를 작은 대표 cohort에 먼저 노출해 control과 guardrail을 비교하는 점진 배포 방식.
롤백
Release 영향이 허용 범위를 벗어날 때 code·config·flag·cache를 이전 호환 상태로 되돌리는 운영 절차.
검증 수준
Function은 실행, Quality는 측정 기준, Product/workflow는 실제 사용자 결과를 각각 따로 증명한다는 프로젝트 분류.

122개 용어