Today I Learned

날짜별 업무 및 학습 회고입니다. 매일 무엇을 했고, 무엇을 배웠는지 기록합니다.

20268

2026-08-23

재사용 가능한 표 컴포넌트를 타입으로 잠그기

컬럼 배열에서 정렬 키 union을 파생하고, 도메인마다 다른 행 타입을 합성 컴포넌트 조각들까지 전달하면서 배운 것 — 그리고 그 과정에서 계속 튀어나온 변성 이야기

  • 값에서 타입을 파생하는 문법과 const 타입 파라미터는 세트다. 하나가 빠지면 나머지도 에러 없이 조용히 무의미해진다
  • const 선언에는 타입 파라미터를 못 붙이므로, 제네릭 context는 함수 클로저 안에서 만들어야 한다
2026-08-14

구현보다 정답 기준을 먼저 잠그기

코드 생성 도구에게 작업을 맡길 때 판정 기준을 문서로 먼저 못 박고, 코드·화면·리뷰 의견을 증거로 강등하며, 확신이 부족하면 멈추게 만드는 설계를 정리했다

  • 판정 기준의 출처를 사람과 승인된 명세로 제한하면 구현을 스펙으로 오해하는 실패가 사라진다
  • 완료의 정의를 테스트 통과 뒤로 옮기면 assertion을 느슨하게 만들 이유가 없어진다
2026-08-09

브라우저 확장의 요청 응답과 변경 알림을 분리해 추적하기

분리된 확장 실행 공간에서 메시지 기반 RPC로 저장을 요청하고, 변경 신호와 쿼리 무효화로 다른 화면이 단일 원본을 다시 읽게 한 흐름을 정리했다

  • Chrome 메시지 API는 전송 수단이고 요청 검증·라우팅·오류 응답은 애플리케이션이 정의한다
  • 명령 응답과 다른 화면의 변경 알림은 목적이 다르므로 분리한다
2026-08-07

중간 계층은 무엇을 통과시키고 무엇을 추측하는가 — 프록시·입력 확정·이미지 비율

rewrite가 업스트림 3xx를 그대로 흘려 만든 무한 리다이렉트, 조합이 끝나기 전 확정된 한글 입력, 16:9로 추측하던 이미지 비율 — 중간 계층의 통과와 추측을 점검하며 배운 것

  • rewrite는 리다이렉트가 아니라 프록시라 업스트림의 3xx를 그대로 브라우저에 전달한다
  • 한글 입력 버그는 값 정규화가 아니라 커밋 경계 문제로 먼저 분리한다

20267

2026-07-31

구형 브라우저 호환은 babel 하나가 아니다 — 문법·폴리필·조건 로드·스모크의 다층 계약

5년 넘은 구형 브라우저에서 앱이 통째로 죽는 버그를 고치며 배운 것 — 여러 기능이 동시에 죽으면 하이드레이션을 의심하고, 문법 다운레벨과 런타임 폴리필을 분리하고, 폴리필은 feature-detect로 옛 브라우저에만 로드하고, 스모크는 실제 인터랙션을 실행한다

  • 여러 UI 기능이 동시에 죽으면 개별 버그가 아니라 하이드레이션/번들 파싱 같은 공통 근본원인을 먼저 의심한다
  • 문법 다운레벨(babel)과 런타임 API(폴리필)는 별개 층이다 — 트랜스파일러는 문법은 바꿔도 없는 함수는 만들지 않는다
2026-07-25

첫 페인트 스플래시와 오프라인 퍼스트 — ISR을 지키며 즉시 반영하기

네이티브 앱 같은 로딩 스플래시를 첫 페인트부터 띄우되 봇엔 숨기는 인라인 스크립트 기법, 그리고 오프라인 퍼스트에서 '내 쓰기 즉시 반영'을 staleTime이 아니라 invalidate와 durable 큐로 만드는 법

  • 첫 페인트 전 노출 + 봇 은닉은 인라인 블로킹 스크립트 주입 — UA 게이트는 headers()로 ISR을 dynamic으로 깨뜨린다
  • react-query에서 '내 쓰기 즉시 반영'은 staleTime을 낮추는 게 아니라 쓰기 후 invalidateQueries로 만든다(invalidate는 staleTime을 무시)
2026-07-24

LCP 병목이 이미지가 아니라 hydration일 때, 그리고 절제된 스크롤 모션

랜딩 성능을 개선하다 배운 것 — LCP가 느린데 이미지가 작으면 render delay(hydration)를 의심하고 클라이언트 JS를 빼는 것, 화려한 스크롤 모션보다 진입 스태거 리빌, 구조화 데이터는 화면과 1:1

  • LCP가 느린데 TBT가 0이고 이미지가 작으면 element render delay(=클라 경계 hydration)를 의심하고 그 서피스의 클라이언트 JS를 뺀다
  • 화려한 스크롤 종속 모션(scroll-jacking)보다 짧은 뷰포트 진입 스태거 리빌이 범용 정답인 경우가 많다
2026-07-23

간헐적으로 깨지는 이미지 — objectURL 레이스, WebP 폴백, 서비스워커 셸

새로고침마다 보였다 안 보였다 하는 이미지 버그를 추적하다 배운 세 가지 — objectURL 수명 레이스, 브라우저 전용 lib의 테스트 hang 방지, 서비스워커 셸 폴백을 navigation 요청으로 한정하기

  • 서버가 URL로 주는 이미지는 objectURL blob으로 감싸지 말고 직접 렌더한다 — createObjectURL 수명 레이스를 원천 제거
  • 브라우저 전용(canvas) lib를 테스트 경로에 넣을 땐 실행 전 동기 capability 게이트로 jsdom hang을 막는다
2026-07-22

무한 리스트 기능을 코드 없이 옮기기 — 클린룸 계약과 keyset 페이지네이션

가상화 무한 리스트+스크롤 복원 기능을 다른 스택으로 옮기며 배운 것 — 구현 코드가 아니라 겉보기 행동을 계약으로 적는 클린룸 방식, 그리고 무한 스크롤의 안정성을 좌우하는 offset vs keyset 페이지네이션

  • 기능을 옮기는 문서는 "우리가 쓴 라이브러리"가 아니라 "겉으로 어떻게 행동하고 실패 시 무엇을 보장하나"를 계약으로 적는다
  • 무한 스크롤 리스트의 안정성은 대부분 페이지네이션 방식에서 온다 — offset은 삽입/삭제 시 중복·누락, keyset은 값 기준이라 안정
2026-07-20

공개 표면·분석 파사드·모션 게이트에서 배운 것

구조화 데이터와 가시 FAQ 일치, auth 해소 후 fetch 게이트, 분석 이벤트 팬아웃 파사드, prefers-reduced-motion JS 전환 게이트를 범용 개념으로 정리한 TIL

  • 구조화 데이터(FAQPage 등)는 화면에 보이는 텍스트와 같은 소스에서 나와야 한다
  • 보호 API는 세션 로딩이 끝난 뒤에만 켜고, 로딩 중 !user만으로 익명·fetch를 결정하지 않는다
2026-07-14

개발 환경 정비에서 만난 범용 개념들: 셸 인용·모노레포 배포·CI 러너·가드 설계

도구·인프라를 정비하며 마주친 셸 단어 분리, workspace 배포 재작성, CI 러너 불일치, fail-open 가드를 범용 개념으로 정리한 TIL

  • 셸 변수에 담긴 경로는 공백이 있으면 인용 없이 쓸 때 여러 인자로 쪼개져 명령이 조용히 깨진다
  • 모노레포 내부 의존은 workspace 프로토콜로 링크하고 배포 시 실제 버전으로 치환하는 매니저로 올려야 한다
2026-07-13

별빛 그래프·SW 캐시·Playwright triage·ESLint guardrail

Canvas 별 노드, Service Worker SWR·generation 보안, CI flaky 분류, 디자인시스템 ESLint guardrail을 하루에 정리한 TIL

  • Canvas 노드 장식은 ID 해시로 안정화하고 globalAlpha·shadowBlur는 그리기 직후 반드시 복구한다
  • Playwright CI 빨간색은 retry 결과로 flaky와 deterministic failed를 먼저 갈라야 한다
2026-07-12

JSON-LD·3국어 i18n·문서 그래프 — 하루에 맞춘 콘텐츠 파이프라인

검색 노출용 JSON-LD 리치화, .{locale}.md 파이프라인, next-intl Link 수정, BFS 로컬 그래프, knowledge publish까지 한 축으로 정리한 TIL

  • 다국어는 번역뿐 아니라 id=`type:slug:locale` 포맷을 DB·검색·sitemap 전 구간에서 grep해야 한다
  • localePrefix always에서는 내부 이동에 plain next/link를 쓰면 bare href가 /ko로 301된다 — 예외 없이 i18n Link
2026-07-11

홈 히어로 scroll-driven 내러티브 연출

GSAP 없이 sticky scrolly·CSS keyframes·view-timeline으로 TIL→Knowledge 승격 데모와 파이프라인 섹션을 구현한 하루

  • sticky scrolly는 래퍼 높이만큼 스크롤 시간축을 만들고, phase는 진행도에서 파생하면 JS가 얇아진다
  • 히어로 라이브 데모는 clip-path steps 와이프 + animation-delay만으로 루프를 만들 수 있다
2026-07-10

Modal peek 스택과 CSS layer 패키지

단일 dialog 안 document push와 @layer 기반 editor CSS 분리로 peek UX·테마 커스터마이징을 맞춘 하루

  • gallery→peek는 dialog를 새로 열기보다 같은 shell에서 documentId만 바꾸는 편이 연속적이다
  • async resolve는 (newId, oldContent) 중간 프레임을 render-time reset으로 막아야 한다
2026-07-09

계층형 문서 네비와 viewer 번들 분리

page push/peek navigator와 read-only viewer entry 분리로 포트폴리오 데모 깊이와 번들을 동시에 맞춘 하루

  • 문서 그래프는 page 블록 + push/peek navigator 계약으로 flat editor와 분리하는 편이 낫다
  • peek·embed는 editor와 DOM은 같아도 import graph가 달라야 viewer 번들이 작아진다
2026-07-08

모바일 포인터 제스처와 서버 이미지 프록시

터치 드래그·리사이즈 안정화와 unoptimized 환경에서 이미지 프록시로 전송 바이트를 줄인 하루

  • 터치로 끌어도 페이지가 같이 스크롤되지 않게 드래그 동작을 손봤다
  • 이미지는 원본 통째가 아니라 필요한 크기만 보내도록 프록시를 붙였다
2026-07-07

중첩 UI 리렌더를 O(1)로 줄이기 — read-model과 렌더 카운트 테스트

불변 트리 에디터에서 한 줄만 고쳐도 전체가 다시 그려지는 문제를, 테스트로 먼저 고정한 뒤 정규화 projection과 id별 구독으로 행·컨테이너 리렌더를 줄인 과정

  • 불변 트리 patch는 조상도 새 ref를 받아 props 내리면 O(depth) 리렌더 — 정규화 entry + id별 구독으로 O(1)로 줄일 수 있다
  • entry 캐시는 입력 ref가 아니라 출력 값-동등으로 안정화해야 하고, 외부 store sync는 setState 직전 choke point에서 동기 실행한다
2026-07-06

번들 shared vendor, 크로스블록 선택, 플로팅 UI 좌표

webpack splitChunks 함정, 하이브리드 에디터 크로스블록 범위 연산, selectionchange·scroll anchor 패턴, Context 구독 분리

  • catch-all vendor에 chunks:all과 고정 name을 함께 쓰면 dynamic import가 shared 청크로 끌려 올라간다
  • 크로스블록 텍스트 연산은 범위 state 대신 getSelection 무상태 파생 + 경계 split 단일화가 안전하다
2026-07-05

블록 에디터 순서·협업·키보드 계약

fractional ordering, optimistic rebase, IME defer, keyboard undo 위임, scoped popover, Jest ESM shim 등 범용 패턴 정리

  • sibling 순서는 integer index보다 fractional position key + neighbor anchor + tie-break가 concurrent insert에 유리하다
  • optimistic UI는 inverse patch 없이는 rebase할 수 없고, remote seq가 merge 권위여야 한다