콘텐츠로 이동

웹 및 프런트엔드 내부: 브라우저 엔진, JavaScript 런타임 및 React 조정

내부 내용: 브라우저가 HTML을 렌더링 트리로 구문 분석하는 방법, JavaScript 이벤트 루프가 마이크로태스크와 매크로태스크를 처리하는 방법, React의 조정자가 가상 DOM 트리를 비교하는 방법, V8 JIT 컴파일 핫 기능(최신 웹 개발의 이면에 있는 정확한 파이프라인, 데이터 구조 및 스케줄링 메커니즘).


1. 브라우저 렌더링 파이프라인: 중요 경로

flowchart LR
    subgraph "Critical Rendering Path"
        HTML["HTML bytes\n(network)"]
        DOM["DOM Tree\n(tokenizer → parser\n→ element nodes)"]
        CSSOM["CSSOM Tree\n(parallel CSS parse\n→ style rules)"]
        RENDER["Render Tree\n(DOM + CSSOM merged\ninvisible nodes excluded)"]
        LAYOUT["Layout (Reflow)\n(box model compute:\nwidth, height, position\nCPU-intensive)"]
        PAINT["Paint\n(draw to layers:\nbackgrounds, borders, text\nrasterize to pixels)"]
        COMPOSITE["Composite\n(GPU: merge layers\nwith transforms/opacity\nGPU-accelerated)"]
        HTML --> DOM
        HTML --> CSSOM
        DOM --> RENDER
        CSSOM --> RENDER
        RENDER --> LAYOUT --> PAINT --> COMPOSITE
    end

HTML 토크나이저 상태 머신

HTML 토크나이저는 ~80개의 상태를 갖는 상태 머신입니다. 상황에 맞는 규칙으로 인해 HTML을 정규식으로 구문 분석할 수는 없습니다.

stateDiagram-v2
    [*] --> Data: Initial state
    Data --> TagOpen: < character
    TagOpen --> StartTagName: [a-z]
    TagOpen --> EndTagOpen: /
    StartTagName --> BeforeAttributeName: whitespace
    StartTagName --> Data: >
    BeforeAttributeName --> AttributeName: [a-z]
    AttributeName --> BeforeAttributeValue: =
    BeforeAttributeValue --> AttributeValueDoubleQuoted: "
    AttributeValueDoubleQuoted --> AfterAttributeValue: "
    AfterAttributeValue --> Data: >
    Data --> RCDATA: title/textarea start tag
    RCDATA --> Data: matching end tag

스크립트 차단: 파서가 <script> 태그(async/defer 없음)를 발견하면 **HTML 구문 분석을 일시 중지**하고 스크립트를 실행한 다음(DOM을 수정할 수 있음) 다시 시작합니다. 이것이 바로 <body> 끝에 있는 <script>이 성능에 중요한 이유입니다.


2. 자바스크립트 이벤트 루프: 마이크로태스크 vs 매크로태스크

flowchart TD
    subgraph "V8 Event Loop Phases"
        CALL["Call Stack\n(synchronous execution)"]
        MICRO["Microtask Queue\nPromise.then, queueMicrotask,\nMutationObserver callbacks"]
        MACRO["Macrotask Queue\nsetTimeout, setInterval,\nI/O callbacks, UI events"]
        RAF["requestAnimationFrame\n(before next paint)"]
        RENDER["Render Pipeline\n(layout + paint + composite)"]

        CALL -->|stack empty| MICRO
        MICRO -->|drain ALL microtasks| MICRO
        MICRO -->|queue empty| RAF
        RAF --> RENDER
        RENDER --> MACRO
        MACRO -->|pick one| CALL
    end

마이크로태스크 기아의 예

// This BLOCKS rendering indefinitely:
function infiniteMicrotasks() {
    Promise.resolve().then(infiniteMicrotasks);
    // Microtask queue never empties → RAF never runs → page freezes
}

// Correct: yield to macrotask queue
function yieldToRender(callback) {
    setTimeout(callback, 0);  // or: scheduler.postTask()
}

내부 상태 머신 약속

stateDiagram-v2
    [*] --> Pending: Promise created
    Pending --> Fulfilled: resolve(value) called
    Pending --> Rejected: reject(reason) called
    Fulfilled --> [*]: .then(onFulfilled) queues microtask
    Rejected --> [*]: .catch(onRejected) queues microtask
    note right of Fulfilled: State is immutable\nonce settled

3. V8 JIT 컴파일 파이프라인

flowchart LR
    subgraph "V8 Compiler Tiers"
        SRC["JavaScript source"]
        PARSE["Parser → AST\n(Abstract Syntax Tree)"]
        IGN["Ignition Interpreter\n(bytecode — executes immediately)\nCollects type feedback"]
        SPARK["Sparkplug Compiler\n(fast baseline JIT\nbytecode→machine code\nno optimization)\n~10ms warm-up"]
        TURBO["TurboFan Optimizing JIT\n(triggered when function 'hot')\nSpeculative optimization\nbased on type feedback"]
        DEOPT["Deoptimization\n(if assumption violated:\ne.g., type changes)\nFall back to Ignition"]
        SRC --> PARSE --> IGN --> SPARK --> TURBO
        TURBO --> DEOPT --> IGN
    end

TurboFan 추측 최적화

sequenceDiagram
    participant JS as Hot Function: add(a, b) = a + b
    participant TF as TurboFan
    participant IC as Inline Cache

    Note over IC: Called 10000× with integers
    IC->>TF: Type feedback: a=Smi, b=Smi (small ints)
    Note over TF: Speculate: always integers
    Note over TF: Emit MOV rax,[a], ADD rax,[b], RET
    Note over TF: Insert guard: CHECK type(a)==Smi
    TF-->>JS: Optimized machine code

    Note over JS: Called with add("hello", 5)
    Note over JS: Type guard FAILS (a is String!)
    JS->>TF: DEOPTIMIZE
    TF-->>JS: Back to Ignition bytecode
    Note over IC: Type feedback now: String|Smi\nRe-optimize with union type (slower)

숨겨진 클래스(모양/지도)

V8은 동일한 속성 레이아웃을 가진 객체에 숨겨진 클래스(모양)를 할당하여 속성 액세스를 최적화합니다.

flowchart LR
    subgraph "Object Shape Transitions"
        C0["Shape C0: {}"]
        C1["Shape C1: {x: offset=0}"]
        C2["Shape C2: {x: offset=0, y: offset=8}"]

        C0 -->|obj.x = 5| C1
        C1 -->|obj.y = 10| C2
    end
    subgraph "Shape Sharing (Fast)"
        P1["point1 = {x:1, y:2}\n→ Shape C2"]
        P2["point2 = {x:3, y:4}\n→ Shape C2 (same!)"]
        FAST["Property read point1.x:\n  lookup offset[C2.x] = 0\n  read memory[ptr+0]\n  O(1) — no hash table!"]
        P1 --> FAST
        P2 --> FAST
    end
    subgraph "Shape Miss (Slow)"
        P3["point3 = {y:2, x:1}\n→ different shape C3!\n(different insertion order)"]
        SLOW["Cannot share shape with C2\nSeparate shape chain"]
    end

4. React 조정: 파이버 아키텍처

flowchart TD
    subgraph "React Fiber Tree"
        WIP["Work-In-Progress Tree\n(being built/updated)"]
        CURR["Current Tree\n(on screen)"]
        ALT["alternate pointer:\nFiber nodes recycled\nbetween current and WIP"]
        WIP <--> ALT
        CURR <--> ALT
    end
    subgraph "Fiber Node Structure"
        FN["Fiber {\n  type: 'div' | ComponentFn\n  key: string\n  stateNode: DOM node | class instance\n  child: → first child fiber\n  sibling: → next sibling fiber\n  return: → parent fiber\n  pendingProps: {}\n  memoizedProps: {}\n  memoizedState: Hook list\n  effectTag: UPDATE|PLACEMENT|DELETION\n  updateQueue: linked list of updates\n}"]
    end

조정: 차이 알고리즘

sequenceDiagram
    participant App as State Update: setCount(5)
    participant Sched as React Scheduler
    participant Render as Render Phase (pure)
    participant Commit as Commit Phase (DOM)

    App->>Sched: scheduleUpdateOnFiber()
    Note over Sched: Assign priority (lane)\nScheduler: postMessage for async work\nor synchronous for urgent updates

    Sched->>Render: beginWork(fiber)\nTop-down tree traversal\n(can be paused/resumed!)
    Note over Render: Compare new element type + key:\n  same type → update props\n  different type → unmount + remount\n  list: key matching for minimal DOM ops

    Render->>Render: completeWork(fiber)\nCollect effectList\n(mutations needed)

    Render->>Commit: Synchronous (cannot pause)\ncommitMutationEffects: apply DOM changes\ncommitLayoutEffects: run useLayoutEffect\ncommitPassiveEffects: run useEffect (async)

동시 모드: 시간 분할

React 18 동시 모드는 **스케줄러**를 사용하여 렌더링 작업을 5ms 조각으로 나눕니다.

flowchart TD
    WORK["Rendering 1000 components\n~50ms total work"]
    SLICE1["Work slice 1: 5ms\n→ yield to browser"]
    INPUT["Browser: handle user input\n(0.5ms — stays responsive!)"]
    SLICE2["Work slice 2: 5ms"]
    PAINT["Browser: paint frame\n(16ms budget kept!)"]
    CONT["Continue until complete\n(10 slices × 5ms)"]
    WORK --> SLICE1 --> INPUT --> SLICE2 --> PAINT --> CONT

5. 가상 DOM 차이점: 주요 알고리즘

flowchart TD
    subgraph "Tree Diff O(N) Heuristics"
        H1["Heuristic 1: Different root type\n→ tear down entire subtree\n→ don't recurse into it"]
        H2["Heuristic 2: Same type element\n→ update attributes only\n→ recurse into children"]
        H3["Heuristic 3: key prop on lists\n→ match by key across renders\n→ minimal moves/inserts/deletes"]
    end
    subgraph "List Reconciliation with Keys"
        OLD["Old: [A(key=1), B(key=2), C(key=3)]"]
        NEW["New: [C(key=3), A(key=1), B(key=2)]"]
        DIFF["Without keys: 3 updates (wrong)
With keys:\n  C: move to position 0\n  A: move to position 1\n  B: move to position 2\n= 2 DOM moves (efficient)"]
        OLD --> DIFF
        NEW --> DIFF
    end

6. CSS 캐스케이드 및 특이성 계산

flowchart TD
    subgraph "Cascade Order (later wins at same specificity)"
        C1["User-agent stylesheet\n(browser defaults)"]
        C2["User stylesheet\n(accessibility overrides)"]
        C3["Author stylesheets\n(your CSS files)"]
        C4["Author !important"]
        C5["User !important"]
        C6["User-agent !important"]
        C1 --> C2 --> C3 --> C4 --> C5 --> C6
    end
    subgraph "Specificity Calculation (a,b,c,d)"
        S1["(1,0,0,0) — inline style"]
        S2["(0,1,0,0) per ID selector\n#header → (0,1,0,0)"]
        S3["(0,0,1,0) per class/attr/pseudo-class\n.active → (0,0,1,0)\n[type='text'] → (0,0,1,0)"]
        S4["(0,0,0,1) per element/pseudo-element\ndiv → (0,0,0,1)\np::first-line → (0,0,0,2)"]
        EXAMPLE["#nav .item:hover span\n= (0,1,0,0)+(0,0,1,0)+(0,0,1,0)+(0,0,0,1)\n= (0,1,2,1)"]
        S1 --> EXAMPLE
        S2 --> EXAMPLE
        S3 --> EXAMPLE
        S4 --> EXAMPLE
    end

7. 웹 성능: 중요한 리소스 로딩

sequenceDiagram
    participant Browser as Browser
    participant Server as Server

    Browser->>Server: GET / (HTML)
    Server-->>Browser: HTML (first byte ~50ms)
    Note over Browser: Parse HTML → discover resources

    par Parallel resource loading
        Browser->>Server: GET /style.css (render-blocking!)
        Browser->>Server: GET /bundle.js (defer)
        Browser->>Server: GET /hero.jpg (preload)
    end

    Server-->>Browser: style.css
    Note over Browser: CSSOM built → unblock render
    Server-->>Browser: First chunk of bundle.js
    Note over Browser: FCP (First Contentful Paint) possible now
    Server-->>Browser: hero.jpg
    Note over Browser: LCP (Largest Contentful Paint)
    Server-->>Browser: bundle.js complete
    Note over Browser: TTI (Time to Interactive)\nJS parsed + executed

핵심 웹 바이탈 내부 트리거

미터법 트리거 측정
LCP 가장 큰 이미지/텍스트 블록이 그려짐 PerformanceObserver 유형 largest-contentful-paint
FID/INP 입력 이벤트 → 브라우저 응답 지연 PerformanceEventTiming.processingStart - startTime
CLS 레이아웃 변경: 사용자 상호 작용 없이 요소가 이동합니다 LayoutShift.value = impact_fraction × distance_fraction

8. 서비스 워커: 가로채기 내부 요소 가져오기

sequenceDiagram
    participant Page as Web Page
    participant SW as Service Worker\n(separate thread)
    participant Cache as Cache Storage API
    participant Net as Network

    Page->>SW: fetch('/api/data') [intercepted]
    Note over SW: self.addEventListener('fetch', event)
    SW->>Cache: caches.match(request)
    Cache-->>SW: Cache HIT → cached response
    SW-->>Page: Serve from cache (offline works!)

    Note over SW: Cache MISS scenario:
    SW->>Net: fetch(request) [network request]
    Net-->>SW: Network response
    SW->>Cache: cache.put(request, response.clone())
    SW-->>Page: Network response

서비스 워커 수명 주기 — 페이지와 별도로 페이지 로드 시 지속됩니다.

Install → Activate → Idle → Fetch/Message
(new SW waits for old clients to close before activating)


9. WebAssembly: 실행 모델

flowchart TD
    subgraph "WebAssembly Execution Pipeline"
        C["C/C++/Rust source"]
        WASM["WebAssembly binary\n(.wasm)\nstructured binary format:\nmodule, functions, tables, memory"]
        VALIDATE["Browser validates WASM\n(type-check in O(N) single pass)\nSafer than JS eval"]
        JIT["JIT compile to machine code\n(WASM types are explicit\n→ simpler/faster than JS JIT\n~5% of native speed achievable)"]
        EXEC["Execute in sandboxed linear memory\n(no pointers outside WASM.memory\ncannot access browser internals)"]
        C --> WASM --> VALIDATE --> JIT --> EXEC
    end
    subgraph "WASM Linear Memory"
        MEM["Single contiguous ArrayBuffer\n[0..n MB]\nmanually managed by WASM\n(malloc from emscripten/wasi)\nJS can read/write same buffer\n(shared memory via SharedArrayBuffer)"]
    end

10. HTTP/2 다중화 및 HOL 차단

sequenceDiagram
    participant Browser as Browser
    participant H2 as HTTP/2 Server

    Note over Browser,H2: Single TCP connection, multiple streams
    Browser->>H2: HEADERS frame (stream 1): GET /style.css\n  HEADERS frame (stream 3): GET /bundle.js\n  HEADERS frame (5): GET /image.jpg\n  (all sent in parallel, same connection!)

    H2->>Browser: DATA frame (stream 3): 16KB of bundle.js
    H2->>Browser: DATA frame (stream 1): complete style.css\n  DATA frame (stream 3): next 16KB bundle.js
    H2->>Browser: DATA frame (stream 5): image.jpg

    Note over Browser,H2: HTTP/2 Head-of-Line still present at TCP level:\n  single packet loss stalls ALL streams\nHTTP/3 (QUIC) solves this with\nindependent UDP streams

11. WebSocket: 프레임 프로토콜 내부

flowchart LR
    subgraph "WebSocket Frame Header"
        B0["Byte 0:\n  bit 7: FIN (last fragment)\n  bit 4-6: RSV1-3 (extensions)\n  bit 0-3: opcode\n  (0=continuation,1=text,2=binary\n   8=close,9=ping,A=pong)"]
        B1["Byte 1:\n  bit 7: MASK (client→server must mask)\n  bit 0-6: payload_len\n  (0-125: actual\n   126: next 2 bytes = real len\n   127: next 8 bytes = real len)"]
        MASK["Masking key (4 bytes, if MASK=1)\nXOR with payload bytes cyclically:\n  masked[i] = payload[i] XOR key[i%4]\n  (prevents proxy cache poisoning)"]
        B0 --> B1 --> MASK
    end

프론트엔드 아키텍처 요약

block-beta
    columns 2
    block:Rendering
        RTree["Render Tree\nDOM+CSSOM merged\nno hidden elements"]
        Layout["Layout/Reflow\nbox positions computed\nexpensive on % widths"]
        Composite["GPU Compositing\ntransform/opacity free\nlayer promotion: will-change"]
    end
    block:JavaScript
        EventLoop["Event Loop\nmicrotask drain first\nRAF before paint"]
        V8JIT["V8 TurboFan\nspeculative optimization\ntype guard deopt"]
        React["React Fiber\ninterruptible render\ntime-sliced Concurrent Mode"]
    end
    block:Network
        H2["HTTP/2\nmultiplexed streams\nheader compression HPACK"]
        CRP["Critical Render Path\nCSS render-blocking\nJS parser-blocking"]
        SW["Service Worker\nfetch interception\noffline caching"]
    end
    block:Security
        CSP["Content Security Policy\nscript-src restrict\nprevents XSS"]
        CORS["CORS\npreflight OPTIONS\nAccess-Control headers"]
        SameSite["SameSite Cookie\nLax/Strict/None\nCSRF prevention"]
    end

설계적 고민

구조와 모델링

프론트엔드 아키텍처의 근본적 구조 결정은 **렌더링 전략 선택**입니다. CSR(Client-Side Rendering), SSR(Server-Side Rendering), SSG(Static Site Generation), ISR(Incremental Static Regeneration)은 각각 다른 성능 프로파일과 개발 복잡도를 가집니다.

CSR은 초기 로딩이 느리지만 이후 페이지 전환이 빠르고, SSR은 TTFB(Time to First Byte)가 빠르나 서버 부하가 증가합니다. SSG는 빌드 타임에 모든 페이지를 생성하여 CDN에서 직접 제공하므로 가장 빠르지만, 데이터 변경 시 재빌드가 필요합니다. ISR은 SSG의 장점을 유지하면서 revalidate 주기로 페이지를 갱신합니다.

flowchart TD
    subgraph "CSR (Create React App)"
        CSR_REQ["브라우저 요청"] --> CSR_HTML["빈 HTML + JS 번들\n(FCP 지연)"]
        CSR_HTML --> CSR_JS["JS 다운로드/파싱/실행\n(TTI 지연)"]
        CSR_JS --> CSR_API["API 호출 → 데이터 fetch"]
        CSR_API --> CSR_RENDER["DOM 렌더링 완료\n(LCP 매우 늦음)"]
    end
    subgraph "SSR (Next.js getServerSideProps)"
        SSR_REQ["브라우저 요청"] --> SSR_SRV["서버에서 데이터 fetch\n+ HTML 생성"]
        SSR_SRV --> SSR_HTML["완성된 HTML 전송\n(FCP 빠름)"]
        SSR_HTML --> SSR_HYD["Hydration: JS 이벤트 바인딩\n(TTI = FCP + Hydration)"]
    end
    subgraph "SSG + ISR (Next.js)"
        SSG_BUILD["빌드 타임 HTML 생성"] --> SSG_CDN["CDN 캐시 배포\n(TTFB 최소)"]
        SSG_CDN --> SSG_REQ["사용자 요청 → CDN 직접 응답"]
        SSG_REQ -->|"stale-while-revalidate"| SSG_REVAL["백그라운드 재생성\n(revalidate: 60s)"]
    end

상태 관리 아키텍처 또한 핵심 구조 결정입니다. 상태를 어디에 위치시킬 것인지에 따라 컴포넌트 간 결합도, 디버깅 용이성, 성능이 크게 달라집니다. 로컬 상태(useState)는 단일 컴포넌트 내에서만 유효하고, 전역 상태(Redux/Zustand)는 앱 전체에서 공유되며, 서버 상태(React Query/SWR)는 캐시 무효화와 동기화 전략이 핵심입니다.

트레이드오프와 의사결정

**마이크로 프론트엔드 vs 모놀리식 SPA**는 조직 규모와 배포 주기에 따른 핵심 의사결정입니다. 마이크로 프론트엔드는 팀 독립성과 독립 배포를 가능하게 하지만, 공유 상태 관리, 스타일 충돌, 번들 중복, 라우팅 통합 등의 복잡도가 크게 증가합니다.

flowchart TD
    subgraph "모놀리식 SPA"
        MONO_BUILD["단일 빌드 파이프라인"] --> MONO_BUNDLE["하나의 번들\n공유 의존성 최적화"]
        MONO_BUNDLE --> MONO_DEPLOY["전체 배포\n단일 팀 조율 필요"]
        MONO_DEPLOY --> MONO_PROS["장점:\n- 코드 공유 용이\n- 일관된 UX\n- 번들 최적화"]
        MONO_DEPLOY --> MONO_CONS["단점:\n- 팀 간 배포 충돌\n- 빌드 시간 증가\n- 전체 장애 위험"]
    end
    subgraph "마이크로 프론트엔드"
        MFE_TEAM1["팀 A: 상품 검색\n독립 빌드/배포"]
        MFE_TEAM2["팀 B: 결제\n독립 빌드/배포"]
        MFE_TEAM3["팀 C: 사용자 관리\n독립 빌드/배포"]
        MFE_SHELL["App Shell\nModule Federation\n라우팅 통합"]
        MFE_TEAM1 --> MFE_SHELL
        MFE_TEAM2 --> MFE_SHELL
        MFE_TEAM3 --> MFE_SHELL
        MFE_SHELL --> MFE_PROS["장점:\n- 독립 배포\n- 기술 스택 자유\n- 장애 격리"]
        MFE_SHELL --> MFE_CONS["단점:\n- 번들 중복(React 2벌)\n- 공유 상태 어려움\n- 스타일 충돌"]
    end

**번들 최적화 전략**에서의 트레이드오프도 중요합니다. 코드 스플리팅은 초기 로딩을 줄이지만 라우트 전환 시 추가 네트워크 요청이 발생합니다. 트리 쉐이킹은 사용하지 않는 코드를 제거하지만 사이드 이펙트가 있는 모듈은 제거할 수 없습니다. 레이지 로딩은 필요한 시점에 로드하지만 사용자 경험에 지연을 줄 수 있어 prefetch 힌트와 함께 사용해야 합니다.

전략 초기 로딩 후속 탐색 개발 복잡도 적합한 케이스
코드 스플리팅 ✓ 감소 △ 추가 요청 중간 대규모 SPA
트리 쉐이킹 ✓ 감소 영향 없음 낮음 ESM 기반 라이브러리
레이지 로딩 ✓ 대폭 감소 △ 지연 가능 중간 비핵심 기능
Prefetch/Preload 영향 없음 ✓ 빠름 낮음 예측 가능한 네비게이션

리팩토링과 설계 원칙

프론트엔드 리팩토링의 핵심 원칙은 **관심사 분리(Separation of Concerns)**입니다. UI 로직, 비즈니스 로직, 데이터 접근 로직을 명확히 분리하면 테스트와 유지보수가 용이해집니다. Custom Hook 패턴은 React에서 이 원칙을 실현하는 핵심 수단입니다.

flowchart TD
    subgraph "리팩토링 전: 거대 컴포넌트"
        BEFORE["ProductPage 컴포넌트\n- API 호출 로직\n- 상태 관리 로직\n- 에러 처리 로직\n- UI 렌더링 로직\n- 이벤트 핸들러\n(500줄 이상)"]
    end
    subgraph "리팩토링 후: 관심사 분리"
        HOOK["useProduct() Hook\n- API 호출 + 캐싱\n- 로딩/에러 상태\n- 낙관적 업데이트"]
        LOGIC["useProductLogic() Hook\n- 비즈니스 규칙\n- 유효성 검증\n- 상태 변환"]
        UI["ProductPage 컴포넌트\n- 순수 UI 렌더링만\n- Props 기반\n(100줄 이하)"]
        PARTS["하위 컴포넌트들\n- ProductCard\n- PriceDisplay\n- ReviewList"]
        HOOK --> UI
        LOGIC --> UI
        UI --> PARTS
    end

**웹 컴포넌트 vs 프레임워크 컴포넌트**도 설계 원칙과 연관된 결정입니다. 웹 컴포넌트(Custom Elements + Shadow DOM)는 웹 표준이므로 프레임워크에 종속되지 않지만, 생태계와 DX(개발자 경험)가 React/Vue에 비해 부족합니다. 디자인 시스템처럼 프레임워크 간 공유가 필요한 경우 웹 컴포넌트가 적합하고, 특정 프레임워크 내 생산성이 우선이면 프레임워크 컴포넌트가 유리합니다.

**성능 최적화 리팩토링**에서는 측정 기반 접근이 필수입니다. Lighthouse, Web Vitals(LCP, FID, CLS) 메트릭을 기준으로 병목을 식별하고, 가장 임팩트가 큰 부분부터 최적화합니다. 무분별한 React.memouseMemo는 오히려 메모리 사용량을 증가시킬 수 있으므로, 프로파일링으로 실제 렌더링 병목을 확인한 후 적용해야 합니다.

디자인 패턴 적용

프론트엔드에서 가장 많이 사용되는 디자인 패턴은 Compound Component, Render Props, Higher-Order Component(HOC), Custom Hook 패턴입니다. 최근에는 Custom Hook이 HOC와 Render Props를 대체하는 추세입니다.

flowchart TD
    subgraph "프론트엔드 디자인 패턴 진화"
        MIXIN["Mixin (과거)\n이름 충돌\n암묵적 의존성"] -->|"대체"| HOC["HOC 패턴\nwithAuth(Component)\nwithRouter(Component)\n중첩 시 Props 충돌"]
        HOC -->|"대체"| RENDER["Render Props\nchildren as function\n유연하지만 콜백 지옥"]
        RENDER -->|"대체"| HOOKS["Custom Hooks\nuseAuth(), useRouter()\n조합 용이, 테스트 용이"]
    end
    subgraph "Compound Component 패턴"
        TAB["Tab.Root"] --> TAB_LIST["Tab.List"]
        TAB --> TAB_PANELS["Tab.Panels"]
        TAB_LIST --> TAB_ITEM1["Tab.Item\nactive 상태 공유"]
        TAB_LIST --> TAB_ITEM2["Tab.Item"]
        TAB_PANELS --> TAB_PANEL1["Tab.Panel\nContext로 상태 공유"]
        TAB_PANELS --> TAB_PANEL2["Tab.Panel"]
    end

**Container/Presenter 패턴**은 데이터 로직과 UI 렌더링을 분리하는 고전적 패턴입니다. Container가 데이터를 가져와 Presenter에 Props로 전달하면, Presenter는 순수하게 UI만 담당합니다. 이 패턴은 Storybook에서의 컴포넌트 격리 테스트를 매우 용이하게 만듭니다.

**Error Boundary 패턴**은 React에서 컴포넌트 트리의 에러를 격리하는 패턴입니다. 개별 위젯의 에러가 전체 페이지를 다운시키지 않도록, 기능 단위로 Error Boundary를 배치하고 Fallback UI를 제공합니다. 이는 마이크로 프론트엔드에서 특히 중요하며, 독립 배포되는 각 마이크로 앱의 장애가 호스트 앱에 전파되지 않도록 보장합니다.

연습 문제

1. 시스템 구조와 모델링

문제 1-1. 사용자가 브라우저에 URL을 입력하면, 브라우저는 HTML 파싱 → DOM 구성 → CSSOM 생성 → Render Tree 결합 → Layout → Paint → Composite 단계를 거쳐 화면을 렌더링합니다. 이 파이프라인에서 <script> 태그가 DOM 파싱을 블록하는 이유를 설명하고, asyncdefer 속성이 각각 이 블로킹 동작을 어떻게 변경하는지 비교하세요. CSS 파일의 로딩이 JavaScript 실행을 블록하는 상황(render-blocking vs parser-blocking)도 함께 분석하세요.

힌트 보기 브라우저는 `