FRONTEND PORTFOLIO / 2026

코드보다 먼저
흐름을 봅니다.

운영 화면에서 입력, 데이터, 오류가 만나는 지점을 나누고 검증합니다.

01 / CASES

문제의 이름이 아니라,
바뀐 흐름을 보여줍니다.

제품 화면이나 기술 나열 대신, 실제 판단이 드러나는 세 가지 장면을 정리했습니다.

CASE 01 / CMP TABLE

컬럼을 다시 잡을 때
갑자기 튀지 않게

업무 장면테이블 컬럼을 드래그로 줄였다 늘릴 때, 다음 리사이즈에서 폭이 갑자기 커지거나 작아지던 공통 컴포넌트

CmpTable 컬럼 리사이즈 보정 흐름 BEFORE / DECLARED WIDTH IS NOT THE RENDERED WIDTH state.column.width 선언값 220px 브라우저 렌더링 실제 240px 다음 드래그 폭이 갑자기 커지거나 작아짐 AFTER / MEASURE THE RENDERED HEADER, THEN SYNC 마지막 th 헤더 엘리먼트 getBoundingClientRect() 실측 240px min / max 보정 실측값 입력 colgroup + state 동기화 다음 드래그 보이는 폭에서 시작 state 값이 아니라 DOM이 실제로 그린 헤더 폭(getBoundingClientRect)을 다음 리사이즈의 기준값으로 동기화합니다.
문제는 리사이즈 기능이 없는 것이 아니라, 상태의 너비와 브라우저가 최종 렌더링한 너비가 달라진 상태에서 다음 드래그가 시작된 점이었습니다.

재현 조건

테이블의 가용 폭, 고정·선택·액션 컬럼, 최소·최대 폭 제약이 함께 적용된 뒤 컬럼을 줄이거나 늘리면 선언된 폭과 실제 헤더 폭이 달라졌습니다.

원인과 선택

상태값만 다시 계산하면 이미 브라우저가 분배한 실제 폭을 알 수 없습니다. 마지막 헤더 행의 th.getBoundingClientRect().width를 기준으로 삼았습니다.

동기화

실측 폭을 최소·최대값으로 보정한 뒤 colgroup과 컬럼 상태에 함께 반영해, 다음 드래그가 화면에 보이는 폭에서 시작되게 했습니다.

       
// 렌더링 결과를 다음 리사이즈의 기준값으로 사용
const widths = headerCells.map((th) =>
th.getBoundingClientRect().width
);
applyDomMeasuredWidthsToAllCols(columns, colgroup, widths, state);
Vue 3TypeScriptAnt Design VueDOM MeasurementgetBoundingClientRectcolgroup

CASE 02 / DATA LIFECYCLE

화면은 남기고,
보이지 않는 요청만 멈추기

업무 장면KeepAlive로 보관된 로그 화면이 창 포커스 복귀 뒤에도 자동 요청을 이어가던 상황

KeepAlive 요청 제어 — 시도, 되돌림, 최종 해결 BEFORE / A KEEPALIVE PAGE KEEPS REQUESTING WHILE HIDDEN 로그 화면 진입 KeepAlive로 보존 인시던트로 이동 Query 구독자는 유지 창 재포커스 숨은 로그 API도 재요청 TRIED & REVERTED / A GLOBAL SWITCH ALSO FREEZES THE SCREEN YOU'RE LOOKING AT refetchOnWindowFocus : false (전역 설정) 지금 보고 있는 로그 화면도 멈춤 ✕ 되돌림 AFTER / REQUEST PERMISSION FOLLOWS "IS THIS PAGE VISIBLE NOW", NOT A GLOBAL FLAG 로그 화면 진입 onActivated · enabled:true 다른 화면 이동 onDeactivated · enabled:false 숨은 요청만 차단 보이는 화면은 계속 최신화 로그 화면 복귀 캐시 표시 후 stale 재조회 화면이 지금 사용자에게 보이는지를 onActivated/onDeactivated로 판단하고, 그 값을 로그 관련 Query들의 enabled 조건에 그대로 연결해 숨은 화면의 요청만 차단했습니다.
전역 스위치 하나로 덮지 않았습니다. 화면이 지금 보이는지에 따라 그 Query만 요청 권한을 갖도록 나눴습니다.

문제

KeepAlive는 로그 화면을 메모리에 그대로 둡니다. 인시던트로 이동해도 로그 Query의 구독자는 남아 있었고, 창 포커스가 돌아오면 지금 보이지도 않는 로그 API가 다시 호출됐습니다.

대안과 선택

처음에는 refetchOnWindowFocus: false를 전역으로 껐습니다. 숨겨진 로그 요청은 막혔지만, 실제로 로그 화면을 보고 있을 때도 포커스 복귀 최신화를 받지 못해 되돌렸습니다. 대신 "캐시된 화면인가"가 아니라 "지금 보이는 화면인가"로 기준을 바꿔, 활성 상태를 로그 관련 Query들의 enabled 조건에만 연결했습니다.

검증

인시던트 화면에서는 숨은 로그 요청이 멈추고, 그 화면 자체의 포커스 재조회는 그대로 살아 있습니다. 로그로 돌아오면 남아 있던 캐시를 먼저 보여주고, stale 상태면 다시 조회합니다.

// 시도했지만 되돌린 방식 — 전역 옵션은 지금 보고 있는 화면까지 멈춥니다
// defaultOptions: { queries: { refetchOnWindowFocus: false } }

// 최종 — 페이지 생명주기 → 해당 Query의 요청 권한
onActivated(() => logStore.setLogPageActive(true));
onDeactivated(() => logStore.setLogPageActive(false));
useQuery({ enabled: computed(() => logStore.isLogPageActive) });
Vue 3TypeScriptTanStack QueryPiniaVue RouterKeepAlive

CASE 03 / ERROR CONTAINMENT

한 화면의 오류가
앱 전체를 멈추지 않게

업무 장면관리자 콘솔에서 하위 화면의 렌더링 오류가 상위 레이아웃까지 전파될 수 있는 구조

Vue Error Boundary — 하위 오류의 상위 전파 차단 onErrorCaptured / 오류는 여기서 멈추고, 나머지 화면은 그대로 남습니다 하위 컴포넌트 렌더링 오류 onErrorCaptured (err, instance, info) return false 상위로 전파 안 함 RootLayout 정상 Header·LNB·다른 라우트 유지 console.error KST 타임스탬프 onError 콜백 props 실행 logErrorToService APM 전송(stub) hasError = true "다시 시도" 폴백 UI (showFallback=true) 기존 화면 유지 (showFallback=false, 현재 설정) route.path가 바뀌면 fallback 여부와 무관하게 resetError()가 실행돼, 이전 화면의 오류 상태가 다음 화면까지 남지 않습니다.
Vue가 부모 방향으로 전달한 오류를 Boundary가 받습니다.

포착

하위 컴포넌트가 낸 오류와 발생 맥락을 Boundary가 오류 상태로 보관합니다.

격리

오류 전파를 멈춰 한 페이지의 실패가 RootLayout과 공통 네비게이션까지 번지는 경로를 차단합니다.

회복

경로가 바뀌면 오류 상태를 초기화해 이전 화면의 실패가 다음 화면에 남지 않게 합니다.

Vue 3Composition APIVue RouterKeepAliveError Handling

03 / TOOLKIT

필요하다면,
깊게 파고듭니다.

업무에서 사용한 기술과, 구조를 이해하기 위해 직접 학습·실험한 영역을 함께 기록합니다.

Core

JavaScriptTypeScriptHTML5CSS3SCSSREST API

Framework & State

Vue 3ReactNext.jsPiniaTanStack QueryVue RouterKeepAliveRedux

UI & Rendering

Ant Design VueCmpTableDOM MeasurementgetBoundingClientRectcolgroupDynamic FormError Boundary

Quality & Structure

PlaywrightStorybookVitestModule FederationMonorepoNxShared UI

Delivery & Collaboration

GitGitHubGitLabBitbucketGitLab CI/CDDockerKubernetesViteWebpack

Experiments

WebSocketPollingChart.jsCodeMirrorNode.jsExpressNestJSFastAPIMCP

복잡한 운영 화면을
검증 가능한 흐름으로 만듭니다.