면접 준비
프론트엔드 개발자 이론 지식 총정리 (면접 + 실무)
각 주제마다 핵심 이론 → 비교 → 면접 포인트 순으로 정리했습니다.
1. 패키지 매니저 & 의존성 관리
핵심 이론
- node_modules 해석 방식: Node.js는
require/import시 현재 폴더의node_modules부터 상위로 올라가며 탐색한다. 이 구조가 모든 패키지 매니저 설계의 출발점. - 호이스팅(Hoisting): npm/yarn classic은 중복 설치를 줄이려 의존성을
node_modules최상위로 끌어올린다. → 부작용: 유령 의존성(Phantom Dependency) —package.json에 선언하지 않은 패키지를 import할 수 있게 되어, 어느 날 갑자기 빌드가 깨지는 원인이 됨. - SemVer:
^1.2.3(minor까지 허용),~1.2.3(patch만 허용). lockfile(package-lock.json,yarn.lock,pnpm-lock.yaml)이 실제 설치 버전을 고정해 재현 가능한 빌드를 보장. - peerDependencies: "내가 직접 설치하지 않고, 사용하는 쪽(호스트)이 제공해야 하는 의존성". React 플러그인 라이브러리가 React를 peer로 두는 이유 = 인스턴스 중복 방지.
npm vs yarn classic vs pnpm vs yarn berry 비교
| 구조 | 장점 | 단점 | |
|---|---|---|---|
| npm | 평탄화(flat) node_modules | 표준, 생태계 호환성 최고 | 느림, 유령 의존성, 디스크 낭비 |
| yarn classic(v1) | npm과 유사 + 병렬 설치 | npm보다 빨랐음(과거), workspaces 선구자 | 유지보수 종료, 유령 의존성 동일 |
| pnpm | 전역 저장소(content-addressable store) + 하드링크 + 심볼릭링크 | 디스크 절약(같은 버전은 1회만 저장), 빠름, 비평탄 구조라 유령 의존성 원천 차단 | 심볼릭링크를 못 다루는 일부 도구와 호환 이슈(드묾) |
| yarn berry(v2+) | PnP(Plug'n'Play): node_modules 자체를 없애고 .pnp.cjs가 "어떤 패키지가 어디 zip에 있는지" 맵을 들고 해석 | 설치 거의 즉시(zero-install 가능 — zip을 git에 커밋), 엄격한 의존성 검증 | IDE/도구가 PnP를 이해해야 함(sdk 설정 필요), 생태계 마찰이 가장 큼 |
면접 포인트
- "pnpm이 빠르고 디스크를 아끼는 원리?" → 전역 store에 파일을 한 번만 두고 하드링크로 연결, 프로젝트에는 심볼릭 링크로 실제 의존 관계만 노출.
- "유령 의존성이 왜 위험한가?" → 선언 없는 암묵적 의존이라 상위 패키지 업데이트로 호이스팅 결과가 바뀌면 런타임에 깨짐.
- "zero-install이란?" → 의존성(zip 캐시)을 저장소에 포함해 clone 직후 install 없이 실행. CI 시간 단축과 버전 불일치 제거가 목적.
모노레포
- workspaces(npm/yarn/pnpm 내장): 여러 패키지를 한 저장소에서 관리, 로컬 패키지 간 심볼릭 링크.
- Turborepo / Nx: 태스크 그래프 기반 증분 빌드 + 원격 캐시. "바뀐 패키지와 그 영향 범위만 빌드/테스트"가 핵심 가치.
- 면접 포인트: 모노레포의 장단점 — 코드 공유·원자적 변경·통일된 툴링 vs 빌드 시간·권한 관리·툴링 복잡도.
2. 빌드 & 번들러
핵심 이론
- 번들러가 하는 일: 엔트리에서 시작해 import 구문을 따라가며 모듈 의존성 그래프를 만들고 → 변환(transpile) → 하나 이상의 번들로 합침.
- 트랜스파일 vs 번들링: 트랜스파일은 문법 변환(TS→JS, 최신→구형 문법), 번들링은 파일 결합. Babel/SWC/esbuild는 트랜스파일러, Webpack/Rollup은 번들러(단, esbuild·Vite는 둘 다 수행).
- ESM vs CJS:
- CJS(
require)는 런타임에 동적으로 해석 → 정적 분석 불가. - ESM(
import)은 파싱 타임에 정적으로 구조가 확정 → 트리셰이킹의 전제 조건.
- CJS(
- 트리셰이킹: ESM의 정적 구조를 이용해 "실제로 사용된 export만" 남기는 것.
package.json의sideEffects: false선언이 있어야 안전하게 제거 가능(CSS import 같은 부수효과 파일은 예외 명시). - 코드 스플리팅:
import()동적 import 지점을 기준으로 청크를 분리. 라우트 단위 분리가 가장 흔한 패턴. - 소스맵: 압축/변환된 코드 ↔ 원본 코드 매핑. 프로덕션에서는 hidden-source-map 등으로 에러 트래킹 서비스에만 업로드하는 전략이 일반적.
- browserslist + 폴리필: 문법 변환(Babel/SWC)과 별개로, 없는 API(예:
Promise.allSettled)는 core-js 폴리필이 필요. "문법은 트랜스파일, API는 폴리필"로 구분해서 답하면 좋음.
도구 비교
| 도구 | 언어 | 포지션 | 특징 |
|---|---|---|---|
| Webpack | JS | 범용 번들러의 표준 | 로더/플러그인 생태계 최강, 설정 복잡, 상대적으로 느림 |
| Rollup | JS | 라이브러리 번들링 강자 | 깔끔한 ESM 출력, 트리셰이킹 원조 |
| esbuild | Go | 초고속 트랜스파일/번들 | 10~100배 빠름, 세밀한 플러그인 생태계는 약함 |
| SWC | Rust | Babel 대체 트랜스파일러 | Next.js 기본 컴파일러, Jest 대체(swc/jest)로도 사용 |
| Vite | JS(내부에 esbuild+Rollup) | 개발 서버 + 빌드 | 아래 별도 설명 |
| Turbopack / Rspack | Rust | Webpack 계승 시도 | Webpack 호환을 노린 Rust 재작성 |
Vite가 빠른 원리 (단골 질문)
- 개발 모드: 번들링을 아예 하지 않고 브라우저의 네이티브 ESM을 그대로 사용. 요청된 파일만 그때그때 변환해서 서빙 → 프로젝트가 커져도 서버 시작 시간이 거의 일정.
- 사전 번들링(pre-bundling): node_modules의 CJS 패키지들은 esbuild로 미리 ESM 변환 + 수백 개 파일을 하나로 합쳐 요청 수를 줄임.
- HMR: 변경된 모듈과 그 경계(boundary)만 교체. 번들 전체를 다시 만들지 않음.
- 프로덕션 빌드: 여전히 Rollup으로 번들링(수백 개의 ESM 요청은 실서비스에서 비효율적이므로).
면접 포인트
- "CJS는 왜 트리셰이킹이 안 되나?" →
require가 조건문 안에서도 호출 가능한 런타임 함수라 정적 분석 불가. - "Webpack HMR 동작 원리?" → 웹소켓으로 변경 알림 → 변경된 모듈의 해시 매니페스트 요청 →
module.hot.accept경계까지 버블링하며 모듈 교체, 경계가 없으면 전체 리로드. - "청크 분리 전략?" → vendor 분리(변경 빈도가 낮은 라이브러리를 별도 청크로 → 캐시 히트율 상승), 라우트 기반 lazy loading, 공통 청크 추출.
3. 테스트
핵심 이론
- 테스트 피라미드: 단위(많이, 빠르게) → 통합 → E2E(적게, 느리게). 프론트엔드에서는 Kent C. Dodds의 테스팅 트로피(통합 테스트에 가장 큰 비중)가 자주 인용됨 — "사용자가 쓰는 방식과 닮을수록 신뢰도가 높다".
- 단위 vs 통합 vs E2E:
- 단위: 함수/훅 하나. 빠르지만 실제 동작 보장은 약함.
- 통합: 여러 컴포넌트 + API 목킹까지 묶어 시나리오 검증.
- E2E: 실제 브라우저에서 전체 플로우. 가장 신뢰도 높지만 느리고 flaky.
- Testing Library 철학: "구현 상세(state, 내부 메서드)가 아니라 **사용자가 보는 것(role, label, text)**으로 쿼리하라". 리팩터링에 강한 테스트를 만드는 핵심 사상. (Enzyme이 밀려난 이유가 이것.)
- 목킹 계층:
jest.mock: 모듈 자체를 교체.- MSW(Mock Service Worker): 네트워크 계층에서 가로챔 → fetch/axios 등 구현과 무관하게 동작, 개발 서버·스토리북·테스트에서 동일한 핸들러 재사용 가능. 요즘 사실상 표준.
- 스냅샷 테스트: 렌더 결과를 직렬화해 저장 후 비교. 손쉽지만 "왜 깨졌는지" 안 알려주고 무지성 업데이트로 형해화되기 쉬움 → 작은 단위에만 제한적으로.
- flaky 테스트 원인: 비동기 타이밍, 실제 시간 의존(→ fake timers), 테스트 간 상태 공유, 애니메이션.
도구 비교
- Jest vs Vitest: Vitest는 Vite 설정/변환 파이프라인을 그대로 공유하고 ESM 네이티브라 설정이 가볍고 빠름. Jest는 생태계·레퍼런스가 가장 큼. API는 거의 호환.
- Cypress vs Playwright:
- Cypress: 테스트 코드가 브라우저 안에서 실행. 디버깅 경험(타임트래블)이 좋지만, 멀티 탭·멀티 도메인 등 브라우저 밖 제어에 제약이 있었음.
- Playwright: CDP(Chrome DevTools Protocol) 등으로 브라우저를 밖에서 제어. 멀티 브라우저/탭/컨텍스트, 병렬 실행에 강하고 auto-waiting 내장. 최근 대세.
- 시각적 회귀 테스트: Storybook + Chromatic/Percy — 픽셀 diff로 UI 변경 감지.
면접 포인트
- "무엇을 테스트해야 하나?" → 구현이 아니라 동작(행위). 조건부 렌더링, 사용자 인터랙션 결과, 엣지 케이스. getter/setter나 라이브러리 자체는 테스트하지 않음.
- "커버리지 100%의 함정" → 라인이 실행됐다는 것과 검증됐다는 것은 다름(assertion 없는 테스트도 커버리지는 올라감).
- "E2E를 어디까지?" → 핵심 비즈니스 플로우(로그인, 결제)만. 나머지는 통합 테스트로 내리는 게 비용 효율적.
4. 상태관리
핵심 이론
-
상태의 분류부터 답하기 (요즘 면접의 핵심 프레임):
- 클라이언트 상태: 모달 열림, 다크모드, 폼 입력 등 — 앱이 소유.
- 서버 상태: API 데이터 — 서버가 소유하고 클라이언트는 "캐시 사본"만 가짐. 낡을 수 있고(stale), 여러 컴포넌트가 공유하며, 재검증이 필요.
- URL 상태(쿼리스트링), 폼 상태 등.
- → "서버 상태를 Redux에 넣던 시대에서, React Query가 서버 상태를 분리해가면서 클라이언트 전역 상태는 매우 작아졌다"는 흐름을 설명할 수 있으면 강함.
-
Flux 아키텍처: View → Action → Dispatcher → Store → View의 단방향 데이터 흐름. 양방향 바인딩(MVC)에서 상태 변화 추적이 어려워진 문제를 해결하려는 설계.
-
Context API의 한계: Context는 상태관리 도구가 아니라 주입(DI) 도구. value가 바뀌면 구독한 모든 소비자가 리렌더되고 selector가 없음 → 자주 바뀌는 값에는 부적합.
라이브러리 비교
| 모델 | 리렌더 제어 | 특징 | |
|---|---|---|---|
| Redux (RTK) | 단일 스토어, 리듀서(불변 업데이트) | useSelector + 얕은 비교 | 예측 가능성·DevTools(타임트래블)·미들웨어 생태계. 보일러플레이트는 RTK로 크게 개선. Immer 내장 |
| Zustand | 단일 스토어(외부 스토어 + 구독) | selector 기반 구독 | Provider 불필요, useSyncExternalStore 기반, 러닝커브 최소. 현재 가장 무난한 기본값 |
| Jotai / Recoil | atom 단위 bottom-up | atom 구독 단위 | 상태를 잘게 쪼개 의존 그래프로 파생(derived) 상태 구성. 세밀한 리렌더 최적화에 유리 |
| MobX | Proxy 기반 observable | 접근 추적으로 자동 | "읽은 값만 추적해서 알아서 리렌더". 마법 같지만 흐름 추적이 어려울 수 있음 |
- Redux 미들웨어: thunk(함수를 dispatch — 간단한 비동기), saga(제너레이터로 복잡한 플로우 — 취소/경쟁 처리), 요즘은 RTK Query가 서버 상태까지 흡수.
React Query(TanStack Query) / SWR — 서버 상태
- stale-while-revalidate: 캐시된(stale) 데이터를 즉시 보여주고, 백그라운드에서 재검증 후 갱신 → 체감 속도와 최신성 동시 확보.
- 핵심 기능: 쿼리 키 기반 캐싱, 요청 중복 제거(dedupe),
staleTime/gcTime, 포커스/재연결 시 refetch, mutation 후 invalidateQueries로 관련 캐시 무효화, 낙관적 업데이트(UI 먼저 반영 → 실패 시 롤백). - 면접 포인트: "staleTime과 gcTime 차이" — staleTime은 "신선하다고 간주하는 시간"(지나야 refetch 후보), gcTime은 "구독자가 없어진 캐시를 메모리에서 지우기까지의 시간".
면접 포인트
- "Context vs 상태관리 라이브러리" → 리렌더 범위 제어(selector) 유무, 상태 로직 조직화 여부.
- "Zustand는 어떻게 리렌더를 줄이나?" → 컴포넌트가 selector로 필요한 조각만 구독, 반환값이 바뀔 때만 리렌더 (
useSyncExternalStore로 외부 스토어와 동기화, tearing 방지). - "낙관적 업데이트 구현 흐름?" → onMutate에서 이전 캐시 스냅샷 저장 + 캐시 선반영 → onError에서 롤백 → onSettled에서 invalidate.
5. 렌더링 전략 & SSR
렌더링 방식 비교
| 방식 | 언제 HTML 생성 | 장점 | 단점 |
|---|---|---|---|
| CSR | 브라우저에서 JS 실행 후 | 서버 부담 적음, 페이지 전환 빠름 | 초기 로딩 느림(빈 HTML), SEO 불리 |
| SSR | 요청마다 서버에서 | 최신 데이터 + SEO + 빠른 FCP | 서버 비용, TTFB 증가 |
| SSG | 빌드 타임에 | 가장 빠름(CDN 캐시), 서버 불필요 | 데이터 갱신 시 재빌드 필요 |
| ISR | 빌드 + 주기적/온디맨드 재생성 | SSG 속도 + 데이터 갱신 | 짧은 시간 stale 콘텐츠 노출 가능 |
핵심 이론
- 하이드레이션(Hydration): 서버가 보낸 정적 HTML에 JS가 이벤트 핸들러를 붙이고 상태를 연결해 "살아있는 앱"으로 만드는 과정. SSR의 숨은 비용 — HTML은 보이는데 클릭이 안 되는 구간(TTI 지연)이 생김. "화면은 빨리 뜨는데 인터랙션은 JS 로드+실행 후"라는 트레이드오프를 설명할 수 있어야 함.
- Hydration mismatch: 서버 렌더 결과와 클라이언트 첫 렌더 결과가 다르면 발생(예:
Date.now(),window접근, 브라우저 전용 분기). 해결:useEffect로 클라이언트 전용 로직 미루기, suppressHydrationWarning은 최후 수단. - Streaming SSR: HTML을 한 번에 다 만들지 않고 준비된 부분부터 스트리밍(
<Suspense>경계 단위). 느린 데이터가 전체 TTFB를 막지 않게 함. - RSC(React Server Components):
- 서버 컴포넌트는 번들에 포함되지 않고 서버에서만 실행 → JS 전송량 감소, DB 직접 접근 가능.
- "SSR은 HTML을 서버에서 만드는 것, RSC는 컴포넌트 자체가 서버에서만 실행되고 하이드레이션 대상이 아닌 것" — 이 구분이 단골 질문.
- 클라이언트 컴포넌트(
'use client')와의 경계 설계가 실무 핵심(서버 → 클라이언트로는 직렬화 가능한 props만).
- 대안 아키텍처:
- Islands(Astro): 페이지는 정적 HTML, 인터랙티브한 "섬"만 부분 하이드레이션.
- Resumability(Qwik): 하이드레이션 자체를 없애고 서버의 실행 상태를 "이어서 재개".
면접 포인트
- "Next.js가 왜 필요한가?" → SSR/SSG/ISR/스트리밍/라우팅/이미지·폰트 최적화를 프레임워크 레벨에서 제공. React만으로 SSR을 직접 구축할 때의 복잡도(서버 렌더, 데이터 직렬화, 라우팅, 번들 이원화)를 흡수.
- "SEO에 SSR이 유리한 이유" → 크롤러가 JS 실행 없이도 완성된 콘텐츠를 봄(구글은 JS 렌더링을 하지만 지연·불완전할 수 있고, 다른 크롤러/SNS 미리보기는 못 함).
- "App Router vs Pages Router" → RSC 기본, 레이아웃 중첩, 스트리밍 우선 설계 vs 전통적
getServerSideProps모델.
6. 배포 & 인프라
CI/CD
- CI: 푸시/PR마다 자동으로 lint → type check → test → build를 돌려 통합 문제를 조기 발견.
- CD: 검증된 아티팩트를 자동 배포. GitHub Actions 기준 워크플로/잡/스텝, 캐시(의존성·빌드 캐시)로 시간 단축, 브랜치별 preview 배포(Vercel식) 개념.
정적 자산 배포와 캐싱 (프론트 배포의 핵심)
- 전형적 구조: 빌드 산출물 → 오브젝트 스토리지(S3) → CDN(CloudFront).
- CDN 원리: 전 세계 엣지 서버에 캐시. 캐시 미스 시 오리진에서 가져와 저장.
- 캐시 전략의 정석 (면접 단골):
- JS/CSS 등은 파일명에 콘텐츠 해시(
app.a1b2c3.js)를 넣고Cache-Control: max-age=31536000, immutable→ 영원히 캐시. index.html은no-cache(또는 max-age=0) → 항상 재검증. HTML이 새 해시 파일명을 참조하므로 배포 즉시 반영.
- JS/CSS 등은 파일명에 콘텐츠 해시(
- Cache-Control 헤더:
max-age,s-maxage(공유 캐시용),no-cache(캐시하되 매번 재검증),no-store(아예 저장 금지),stale-while-revalidate. - 조건부 요청:
ETag/If-None-Match,Last-Modified/If-Modified-Since→ 304 Not Modified로 본문 전송 생략. - 캐시 무효화(invalidation): CDN 캐시 퍼지 vs 파일명 해싱 전략(후자가 정석 — 무효화 비용/전파 지연 없음).
서버/컨테이너
- nginx: 리버스 프록시(API 프록시로 CORS 회피, SSL 종료), 정적 파일 서빙, gzip/brotli 압축, SPA의
try_files ... /index.html(히스토리 라우팅 폴백). - Docker: "내 컴퓨터에선 되는데"를 없애는 환경 패키징. 멀티 스테이지 빌드(빌드 스테이지 → nginx 스테이지)로 이미지 경량화. 이미지 레이어 캐시(package.json 먼저 COPY 후 install → 소스 변경 시 install 캐시 재사용).
배포 전략
- Rolling: 서버를 순차 교체 — 무중단이지만 신구 버전 공존.
- Blue-Green: 새 환경(Green)을 통째로 띄우고 트래픽 스위치 — 롤백 즉시, 비용 2배.
- Canary: 일부 트래픽(1~5%)만 새 버전으로 → 지표 확인 후 점진 확대.
- Feature Flag: 배포와 릴리즈를 분리 — 코드는 배포하되 기능은 플래그로 켬. A/B 테스트, 점진 릴리즈, 킬스위치.
면접 포인트
- "배포했는데 사용자가 예전 화면을 봐요" → HTML 캐시 정책 문제 또는 CDN 무효화 누락. 위의 해시 전략으로 답변.
- "SPA 새로고침 시 404가 나는 이유" → 서버에
/some/route실제 파일이 없기 때문 → index.html 폴백 설정. - "환경변수 관리" → 빌드 타임 주입(번들에 박힘, 클라이언트 노출됨 — 비밀값 금지) vs 런타임 주입의 구분.
7. 모니터링 & 측정
Core Web Vitals (반드시 숙지)
- LCP (Largest Contentful Paint): 뷰포트 내 가장 큰 콘텐츠가 그려지는 시점. 목표 2.5초 이내. 개선: 이미지 최적화, 서버 응답(TTFB) 개선, 렌더 차단 리소스 제거, LCP 이미지 preload.
- INP (Interaction to Next Paint): 인터랙션 → 다음 페인트까지의 지연. FID를 대체한 지표(FID는 첫 입력만, INP는 전체 인터랙션의 최악 수준을 봄). 목표 200ms 이내. 개선: 긴 태스크(Long Task, 50ms+) 쪼개기, 메인 스레드 작업 줄이기, 무거운 리렌더 최적화.
- CLS (Cumulative Layout Shift): 레이아웃 밀림 누적 점수. 목표 0.1 이하. 개선: 이미지/광고에 크기 예약(width/height, aspect-ratio), 폰트 스왑 대응, 콘텐츠 위 동적 삽입 금지.
- 보조 지표: TTFB(첫 바이트), FCP(첫 콘텐츠 페인트), TTI, TBT.
측정 방법
- Lab(합성) vs Field(RUM):
- Lab: Lighthouse, WebPageTest — 통제된 환경, 재현 가능, 회귀 감지용(CI에 Lighthouse CI).
- RUM(Real User Monitoring): 실제 사용자 환경에서
web-vitals라이브러리, PerformanceObserver로 수집 — 실제 체감을 반영. 구글 랭킹에 쓰이는 건 필드 데이터(CrUX).
- Performance API:
performance.mark/measure, Navigation Timing, Resource Timing, Long Task API.
에러 트래킹
- Sentry 동작 이해: 전역 에러 핸들러(
window.onerror,unhandledrejection) + 프레임워크 통합(React Error Boundary)으로 수집 → 빌드 시 소스맵 업로드로 압축 코드 스택을 원본 위치로 복원. 릴리즈 버전 태깅, breadcrumb(에러 직전 행동 기록), 이슈 그루핑. - Error Boundary: 렌더 중 에러를 잡아 폴백 UI 표시. 이벤트 핸들러/비동기 에러는 못 잡는다는 점이 단골 질문.
- 로깅/분석: 사용자 행동 로그 설계(이벤트 택소노미), Sampling 개념.
면접 포인트
- "성능 개선을 어떻게 증명했나?" → "지표(LCP 등) 측정 → 병목 진단(어느 구간?) → 개선 → 재측정" 서사로 답하는 것이 정석. 숫자 없는 최적화 경험담은 약함.
- "FID가 INP로 바뀐 이유" → 첫 입력 하나만 보면 이후 상호작용의 버벅임을 못 잡기 때문.
8. 최적화
브라우저 렌더링 파이프라인 (모든 렌더 최적화의 기반)
- HTML 파싱 → DOM, CSS 파싱 → CSSOM
- 둘을 합쳐 Render Tree (display:none은 제외)
- Layout(Reflow): 각 요소의 크기·위치 계산
- Paint: 픽셀로 그리기 (색, 그림자 등)
- Composite: 레이어를 GPU에서 합성
- Reflow vs Repaint: 위치/크기 변경(width, top, font-size)은 Layout부터 다시 = 비쌈. 색상 변경은 Paint만.
transform,opacity는 Composite만 발생 → 애니메이션은 이 둘로 하라는 이유. - 레이아웃 스래싱: 읽기(offsetHeight)와 쓰기(style 변경)를 반복하면 강제 동기 레이아웃 발생 → 읽기/쓰기 배치 분리,
requestAnimationFrame활용. - CSS
will-change,contain,content-visibility존재 알아두기.
네트워크/로딩 최적화
- 코드 스플리팅 + lazy loading:
React.lazy+Suspense, 라우트 단위 분리. - 리소스 힌트:
preload(현재 페이지 필수 리소스 우선 로드 — LCP 이미지, 폰트),prefetch(다음 페이지용 유휴 시간 로드),preconnect/dns-prefetch(연결 미리 수립). - 이미지: WebP/AVIF(용량 30~50%↓),
srcset/sizes반응형,loading="lazy", LCP 이미지는 lazy 금지 +fetchpriority="high", CLS 방지용 크기 명시. - 폰트: FOIT(안 보임) vs FOUT(폴백 먼저) —
font-display: swap, 서브셋팅(한글은 특히 중요 — 수 MB → 수백 KB), woff2, preload. - 압축: gzip vs brotli(더 높은 압축률, 정적 사전 압축 가능).
- HTTP/1.1 vs 2 vs 3: HTTP/2 멀티플렉싱(한 연결에 병렬 스트림 → 도메인 샤딩/스프라이트 불필요해짐), 헤더 압축(HPACK). HTTP/3는 QUIC(UDP) 기반으로 HOL 블로킹을 전송 계층에서도 해소.
React 렌더링 최적화
- 리렌더의 진실: 부모가 리렌더되면 props 변경과 무관하게 자식도 기본적으로 리렌더. "props가 안 바뀌면 리렌더 안 된다"는 흔한 오해.
- Reconciliation(재조정): 새 엘리먼트 트리와 이전 트리를 비교(diffing) — 타입이 다르면 서브트리 폐기, 같으면 props만 갱신. 리스트에서 key가 "동일 항목 식별자" 역할(index key가 위험한 이유: 삽입/삭제 시 상태가 엉뚱한 항목에 붙음).
- 메모이제이션:
React.memo(props 얕은 비교로 리렌더 스킵) +useMemo/useCallback(참조 동일성 유지 — memo와 세트로 의미 있음). 남용 시 비교 비용만 추가된다는 균형 잡힌 시각 필요. React 19의 React Compiler가 이를 자동화하는 방향이라는 것도 언급하면 가점. - 상태 구조 최적화: 상태를 사용하는 곳 가까이로 내리기(colocation), 자식을
children으로 받아 리렌더 격리. - 긴 리스트: 가상화/윈도잉(react-window, TanStack Virtual) — 보이는 것만 렌더.
- React 18 동시성:
useTransition/useDeferredValue로 긴급(입력)과 비긴급(결과 목록) 업데이트 분리, 렌더 중단 가능(concurrent rendering)의 개념. - 번들 다이어트: 번들 분석기(webpack-bundle-analyzer / rollup-plugin-visualizer)로 큰 의존성 찾기, moment→dayjs, lodash 개별 import, 중복 의존성 정리.
면접 포인트
- "리렌더링 최적화 어떻게?" → ① 측정(React DevTools Profiler) ② 상태 내리기/구조 개선 ③ 그래도 필요하면 memo — 순서로 답하기.
- "transform으로 애니메이션하는 이유" → Reflow/Repaint 없이 Composite 단계만 타고, GPU 가속 + 메인 스레드 밖(compositor thread)에서 처리 가능.
9. 코드 품질 · 린트 · 협업
린트/포맷
- ESLint 원리: 코드를 AST로 파싱해 규칙별로 노드를 검사. TypeScript는 typescript-eslint가 파서를 교체하고, 타입 정보 기반 규칙(type-aware linting)도 가능.
- ESLint vs Prettier 역할 분리: 린트는 "코드 품질/버그 가능성", 프리티어는 "포맷팅". 충돌 규칙은
eslint-config-prettier로 꺼줌. (최근엔 Biome처럼 둘을 합친 Rust 도구도 등장.) - Flat Config(eslint.config.js): ESLint 9의 새 설정 체계 — extends 체인 대신 배열 병합.
- 자동화 파이프라인: Husky(git hook) + lint-staged(변경 파일만 검사) + commitlint(Conventional Commits:
feat:,fix:— 체인지로그/시맨틱 버저닝 자동화의 기반).
TypeScript 이론 (면접 최다 빈출)
- 구조적 타이핑(structural typing): 이름이 아니라 "모양"으로 호환 판단 (덕 타이핑의 정적 버전).
typevsinterface: interface는 선언 병합·extends, type은 유니온/교차/조건부 타입 등 표현력. 팀 컨벤션 문제이지 성능 차이는 사실상 없음.anyvsunknown: unknown은 "모르니 좁히기 전엔 못 씀"(안전), any는 타입 시스템 이탈.- 제네릭, 타입 좁히기(narrowing): typeof/in/instanceof/판별 유니온(discriminated union) + 타입 가드(
is). - 유틸리티 타입(Partial, Pick, Omit, Record),
strict옵션의 의미(특히 strictNullChecks). - enum vs union type: enum은 런타임 코드 생성(const enum/트리셰이킹 이슈), 유니온 리터럴 선호 흐름.
아키텍처/설계
- 폴더 구조 사상: 기능 기반 vs 타입 기반, FSD(Feature-Sliced Design), Atomic Design의 장단(추상 계층 판단 비용).
- 관심사 분리: UI(표현) / 비즈니스 로직(훅, 서비스) / 데이터 접근(API 레이어) 분리 — "컴포넌트에서 fetch를 직접 안 하는 이유"를 설명할 수 있어야 함.
- 선언적 프로그래밍, 합성(composition) > 상속, 의존성 역전의 프론트엔드식 적용(컴포넌트 주입).
10. 웹 접근성 (Accessibility, a11y)
접근성 컴포넌트를 직접 구현해 보면 "버튼 하나, 모달 하나"에도 브라우저별 차이, 포커스 관리, ARIA 처리 등 생각보다 깊은 공수가 든다는 걸 체감하게 됩니다. 그래서 접근성은 "경험해 본 사람"과 아닌 사람이 답변 깊이에서 크게 갈리는 주제이고, 면접에서 실무 역량을 어필하기 좋은 영역입니다.
기본 개념과 표준
- WCAG(Web Content Accessibility Guidelines): 접근성의 국제 표준. 4대 원칙 POUR — 인식 가능(Perceivable), 운용 가능(Operable), 이해 가능(Understandable), 견고함(Robust). 준수 레벨 A / AA / AAA (실무 목표는 보통 AA). 한국은 KWCAG(한국형 지침) + 장애인차별금지법이 법적 근거.
- 접근성 트리(Accessibility Tree): 브라우저가 DOM에서 파생해 만드는 별도의 트리. 스크린 리더 등 보조기술(AT)은 DOM이 아니라 이 트리를 읽는다. 각 노드는 **역할(role) · 이름(name) · 상태(state)**를 가짐 — "이 요소의 접근 가능한 이름(accessible name)이 무엇인가"가 모든 논의의 출발점.
- Accessible Name 계산 우선순위(단골 심화 질문):
aria-labelledby>aria-label> 연결된<label>/ 콘텐츠 텍스트 >title. 아이콘 버튼에 이름이 없으면 스크린 리더는 그냥 "버튼"이라고만 읽는다.
시맨틱 HTML — 제1원칙
- ARIA 사용의 제1규칙: "가능하면 ARIA를 쓰지 마라" — 네이티브 요소를 쓰면 역할·상태·키보드 동작·포커스 가능성이 공짜로 따라온다.
<div onClick>대신<button>: 버튼은 Tab 포커스, Enter/Space 활성화, role=button, 폼 submit 동작까지 내장. div로 이걸 재현하려면tabindex="0"+role="button"+ keydown(Enter/Space) 핸들러 + 시각적 포커스까지 전부 수동 구현해야 함 — "공수가 든다"는 체감의 전형적 사례.<a>vs<button>: 이동(내비게이션)은 a, 동작(액션)은 button. 스크린 리더 사용자는 "링크 목록"으로 페이지를 훑기 때문에 역할이 섞이면 탐색이 무너짐.
- 랜드마크 요소(
<header> <nav> <main> <footer>), 제목 계층(h1~h6 건너뛰지 않기),<label>–<input>연결, 이미지alt(장식 이미지는alt=""로 명시적 무시).
ARIA 속성 처리
- Role / State / Property 3분류:
- Role:
role="dialog",role="tablist",role="listbox"— 요소의 정체. - State(동적으로 변함):
aria-expanded,aria-selected,aria-checked,aria-disabled,aria-hidden. - Property(비교적 정적):
aria-label,aria-labelledby,aria-describedby,aria-controls,aria-haspopup.
- Role:
- "ARIA는 약속일 뿐 동작을 주지 않는다":
role="button"을 붙여도 키보드 동작은 생기지 않음. 역할을 선언했으면 그 역할의 키보드 인터랙션까지 구현할 책임이 생긴다 (잘못 쓴 ARIA는 없는 것보다 나쁘다). - 라이브 리전(Live Region): 포커스가 없는 곳의 동적 변화(토스트, 검색 결과 수, 폼 에러)를 스크린 리더에 알림.
aria-live="polite"(하던 말 끝나고) vs"assertive"(즉시 끊고),role="status"/role="alert". 리전은 미리 DOM에 존재해야 내용 변경이 감지된다는 함정이 실무 포인트. aria-hidden="true": 접근성 트리에서만 제거(시각적으론 보임). 반대로 시각적으로만 숨기고 스크린 리더엔 읽히게 하는 sr-only(visually hidden) CSS 패턴도 세트로 알아두기.display:none은 둘 다에서 제거.
키보드 포커스 관리 — 컴포넌트 구현의 핵심 난도
- 기본 규칙: 마우스로 되는 모든 것은 키보드로도 되어야 한다(WCAG 운용성). Tab/Shift+Tab = 위젯 사이 이동, 방향키 = 위젯 내부 이동이 관례.
- tabindex 3종:
0(자연스러운 순서에 편입),-1(Tab으론 못 가지만focus()로는 이동 가능 — 프로그래밍 포커스용), 양수(순서 강제 — 사용 금지에 가까움, 유지보수 재앙). - Roving tabindex: 탭 목록·라디오 그룹·메뉴 등에서 "현재 활성 항목만
tabindex=0, 나머지는-1"로 두고 방향키로 활성 항목을 옮기는 패턴. 위젯 전체가 Tab 한 번으로 통과되게 만드는 표준 기법. (대안:aria-activedescendant— 포커스는 컨테이너에 두고 활성 자식만 가리키는 방식. 콤보박스에서 주로 사용.) - 포커스 트랩(Focus Trap): 모달이 열리면 Tab 순환이 모달 내부에 갇혀야 함. 구현 요소 — 열릴 때 모달(또는 첫 포커스 요소)로 포커스 이동, 마지막 요소에서 Tab 시 첫 요소로 순환, 닫히면 열었던 트리거로 포커스 복귀, Esc로 닫기, 배경 콘텐츠
inert또는aria-hidden처리.- 네이티브
<dialog>요소 +showModal()이 트랩·Esc·backdrop을 상당 부분 내장하고,inert속성도 표준화됨 — "직접 구현 vs 플랫폼 기능" 트레이드오프로 언급하면 좋음.
- 네이티브
- :focus-visible: 마우스 클릭엔 포커스 링을 숨기고 키보드 사용자에게만 표시.
outline: none을 무조건 지우는 것이 대표적 안티패턴인 이유. - 포커스 손실 버그: 포커스된 요소가 DOM에서 사라지면(목록 삭제, 조건부 렌더) 포커스가 body로 튕김 → 스크린 리더 사용자는 길을 잃음. 삭제 후 "다음 항목 또는 목록 제목으로 포커스 이동" 같은 명시적 관리가 필요 — React에선 ref +
useEffect타이밍 문제와 얽혀 은근히 까다로운 부분.
브라우저·스크린 리더별 차이 (직접 구현 시 체감하는 지점)
- 스크린 리더는 브라우저와 짝으로 테스트한다: VoiceOver+Safari(macOS/iOS), NVDA+Chrome/Firefox, JAWS+Chrome(Windows), TalkBack+Chrome(Android). 같은 마크업도 조합마다 읽는 방식이 다를 수 있음.
- 대표적 차이 사례:
- Safari/VoiceOver: macOS Safari는 기본 설정에서 링크·버튼이 Tab 순서에 포함되지 않는 옵션(시스템/브라우저 설정 의존)이 있어 "Safari에서 Tab이 안 먹어요" 이슈의 단골 원인.
display: contents, 일부 role 조합에서 접근성 트리 버그가 있었던 역사도 유명. <details>/<summary>,dateinput, 커스텀 셀렉트 등 네이티브 위젯의 렌더링·키보드 동작 자체가 브라우저마다 다름 → 이것이 디자인 시스템들이 커스텀 구현으로 가는 이유이자, 커스텀 구현의 공수가 폭증하는 이유.- 포커스 링 기본 스타일,
scroll into view동작, IME(한글 입력) 조합 중 keydown 이벤트 처리(isComposing— Enter가 두 번 발화하는 버그) 등도 브라우저별 차이의 실무 사례.
- Safari/VoiceOver: macOS Safari는 기본 설정에서 링크·버튼이 Tab 순서에 포함되지 않는 옵션(시스템/브라우저 설정 의존)이 있어 "Safari에서 Tab이 안 먹어요" 이슈의 단골 원인.
- 그래서 실무 결론: 헤드리스 UI 라이브러리 활용 — Radix UI, React Aria(Adobe), Headless UI, Ark UI. 스타일 없이 역할·키보드·포커스·스크린 리더 대응 로직만 제공. "WAI-ARIA APG 패턴을 브라우저별 차이까지 검증해서 캡슐화한 것"이라고 설명할 수 있으면 강함. 트레이드오프: 의존성 추가·커스터마이징 한계 vs 직접 구현의 방대한 QA 비용.
대표 컴포넌트 패턴 (WAI-ARIA Authoring Practices Guide 기준)
- 모달 다이얼로그:
role="dialog"+aria-modal="true"+aria-labelledby(제목 연결) + 포커스 트랩 + 복귀. - 탭:
tablist/tab/tabpanel+aria-selected+ roving tabindex + 좌우 방향키. - 드롭다운 메뉴:
aria-haspopup+aria-expanded+ 방향키 이동 + Esc 닫기 + 문자 입력 시 해당 항목 점프(typeahead). - 콤보박스(자동완성):
role="combobox"+aria-expanded+aria-activedescendant+aria-autocomplete— 접근성 구현 난도 최상급으로 꼽히는 패턴. - 토스트/알림:
role="status"라이브 리전 + 충분한 표시 시간 + 포커스를 뺏지 않기.
그 외 필수 지식
- 색 대비(Contrast): 일반 텍스트 4.5:1, 큰 텍스트 3:1 (AA 기준). "색만으로 정보 전달 금지"(에러를 빨간색만으로 표시 X — 아이콘/텍스트 병행).
- prefers-reduced-motion: 전정 장애 사용자를 위한 애니메이션 축소 미디어 쿼리.
prefers-color-scheme, 터치 타깃 크기(최소 44×44px 권장)도 세트로. - 폼 접근성: label 연결, 에러를
aria-describedby로 입력에 연결 +aria-invalid, 필수 표시, placeholder는 label 대체가 될 수 없음.
테스트와 자동화
- 자동화 도구: axe-core(Lighthouse 접근성 감사의 엔진),
eslint-plugin-jsx-a11y(정적 검사), jest-axe / Playwright + axe(테스트 통합), Storybook a11y 애드온. - 자동화의 한계 (중요한 균형 감각): 자동 도구는 접근성 이슈의 일부(통상 30~50% 수준)만 잡는다 — "포커스 순서가 논리적인가", "스크린 리더로 실제 플로우가 이해되는가"는 수동 테스트 필수. 최소한의 수동 체크: 키보드만으로 전체 플로우 완주 + 스크린 리더 1개 조합으로 핵심 플로우 청취.
- Testing Library의
getByRole쿼리가 사실상 접근성 테스트를 겸한다는 점(role/name으로 못 찾으면 접근성에 문제가 있을 확률이 높음)도 연결해서 답하면 좋음.
면접 포인트
- "div 대신 button을 써야 하는 이유를 구체적으로" → 위의 시맨틱 항목처럼 재현해야 할 동작 목록(포커스, Enter/Space, role, 폼 동작)을 나열하며 답하면 경험자 티가 남.
- "모달 접근성 구현 시 고려사항" → 포커스 이동/트랩/복귀 + aria-modal + 배경 inert + Esc — 이 5종 세트를 순서대로.
- "접근성을 챙기면 뭐가 좋아지나" → 법적 리스크 외에도 SEO(시맨틱 구조), 키보드 파워유저 UX, 자동화 테스트 용이성(role 기반 쿼리), 일시적 장애 상황(밝은 야외, 한 손 사용) 커버 — "장애인 전용 기능이 아니라 견고한 UI의 기본 품질"이라는 관점.
- "접근성 컴포넌트를 직접 만들 때 어려웠던 점" → 브라우저·스크린 리더 조합별 차이, 포커스 타이밍(React 렌더 사이클과의 충돌), APG 스펙과 실제 AT 동작의 괴리 → 그래서 헤드리스 라이브러리/네이티브 dialog로 절충했다는 서사가 실전형 답변.
11. 브라우저 & 네트워크 기초 (모든 면접의 뿌리)
이 파트는 어느 주제를 파고들어도 결국 만나게 되는 공통 기반입니다.
자바스크립트 런타임
- 이벤트 루프: 콜 스택 비면 → 마이크로태스크 큐(Promise.then, queueMicrotask) 전부 소진 → 매크로태스크(setTimeout, I/O) 하나 → 렌더링 기회 → 반복. "setTimeout(0) vs Promise.then 실행 순서"는 단골.
- 싱글 스레드인데 비동기가 되는 이유(Web API가 브라우저 스레드에서 처리), Web Worker(메인 스레드 밖 연산).
네트워크/보안
- 주소창에 URL 입력 → 화면 표시까지: DNS 조회 → TCP(+TLS) 핸드셰이크 → HTTP 요청/응답 → 파싱/렌더링 파이프라인. 각 단계에 어떤 최적화가 대응되는지(DNS prefetch, preconnect, CDN, 캐시, 렌더 차단 제거)를 매핑해서 답하면 최상급.
- CORS: 브라우저의 동일 출처 정책(SOP) 완화 메커니즘. 서버가
Access-Control-Allow-Origin으로 허용 — "서버를 지키는 게 아니라 사용자(브라우저)를 지키는 정책", preflight(OPTIONS)가 언제 발생하는지(단순 요청 조건). - 인증: 쿠키 세션 vs JWT — 상태 저장 위치, 만료/폐기, refresh token 순환. 쿠키 속성(HttpOnly, Secure, SameSite)이 보안 질문과 직결.
- XSS(스크립트 주입 — 이스케이프, CSP, HttpOnly 쿠키로 완화) vs CSRF(인증된 사용자의 요청 위조 — SameSite 쿠키, CSRF 토큰). 둘의 차이 설명은 필수.
- REST vs GraphQL: over/under-fetching, 캐싱 용이성(HTTP 캐시 vs 별도 계층), 스키마/타입 안전성.
- WebSocket vs SSE vs 폴링(실시간 통신 선택 기준).
학습 우선순위 제안
- 최우선(모든 면접에서 나옴): 브라우저 렌더링 파이프라인 + 이벤트 루프 + Core Web Vitals + React 리렌더링/재조정 + CSR/SSR/하이드레이션.
- 차순위(기술 선택 질문): 번들러 원리(ESM/트리셰이킹/Vite), 패키지 매니저 비교, 서버 상태 vs 클라이언트 상태(React Query), 캐시 전략(해시 파일명 + Cache-Control).
- 경력/실무 어필용: CI/CD·배포 전략, 모니터링(Sentry/RUM), 테스트 전략(트로피, MSW), 모노레포.
답변 공식: "무슨 문제를 풀려고 등장했는가 → 원리는 무엇인가 → 트레이드오프는 무엇인가" 3단으로 말하면 어떤 주제든 구조화된 답이 됩니다. 예: "pnpm은 (문제) 중복 설치와 유령 의존성을, (원리) 전역 store + 링크 구조로 해결하고, (트레이드오프) 심볼릭 링크 비호환 도구가 드물게 있다."