- 발행일
같은 표준데이터를 두 화면이 198건과 134건으로 세고 있었다 — 집계 모집단 일원화
홈 "데이터 현황" 타일은 198건, 그 타일을 눌러 도착하는 목록 화면은 134건이었습니다. 닷새 전에 "타일이 목록으로 링크하니까 목록에 맞춘다"고 결정하고 주석까지 박아뒀는데도 회귀가 샜습니다. 원인은 결정이 아니라 그 결정을 지킨다던 테스트였어요. 기대값을 상수로 박아둔 정합 테스트는 목록 뷰가 조건을 하나 더 붙여도 계속 초록이었습니다.
홈 "데이터 현황" 타일은 198건, 그 타일을 눌러 도착하는 목록 화면은 134건이었습니다. 닷새 전에 "타일이 목록으로 링크하니까 목록에 맞춘다"고 결정하고 주석까지 박아뒀는데도 회귀가 샜습니다. 원인은 결정이 아니라 그 결정을 지킨다던 테스트였어요. 기대값을 상수로 박아둔 정합 테스트는 목록 뷰가 조건을 하나 더 붙여도 계속 초록이었습니다.
FAQ와 1:1 문의를 공지사항 Post 모델 위에 얹었습니다. 게시판 종류를 Category 행으로 뒀더니 게시판 추가가 데이터 한 줄이 됐는데, 대신 "문의"라는 한글 문자열이 코드에 박혔어요. 그리고 넉 달 전 제가 "타인 글은 403보다 404가 안전하다"고 써놓고, 정작 이번 구현은 403으로 냈습니다. 왜 그랬는지 따져봤습니다.
홈 "데이터 현황"에 물리 아키텍처만 "준비 중"으로 있었습니다. 데이터를 붙이려고 기존 집계 함수를 열어보니 ASN.1 데이터셋을 세고 있었어요. 화면이 말하는 것과 다른 것을 세고 있었던 겁니다. 같이 정리한 퀵메뉴에서는 카드 폭을 flex-1로 두면 안 되는 이유를 만났습니다.
분류체계는 목록 → 상세 두 단계였는데, 목록이 하는 일이 "트리 하나 고르기"뿐이었습니다. 트리 하나로 합치면서 자식 개념을 정방향 링크로 찾다가 트리와 다른 집합이 나왔어요. 저장된 트리플은 자식이 선언한 skos:broader 하나뿐이라, 자식을 찾으려면 역방향으로 가야 했습니다. 이어서 같은 역관계 때문에 관계도 라벨이 포개지던 것도 잡았습니다.
인스턴스를 편집하다 왼쪽 트리의 다른 노드를 누르면 입력한 게 전부 날아갔습니다. confirm으로 되묻는 것까지는 쉬웠는데, "취소"를 눌러 선택을 되돌리는 그 동작이 다시 select 이벤트를 쏴서 결국 화면이 지워졌어요. 이어서 같은 화면의 e2e가 간헐 실패하던 것도 jstree refresh()가 비동기라는 같은 뿌리였습니다.
표준데이터 목록에 분류 칩을 붙이는 데 나흘간 네 번 갈아엎었습니다. 3분류 → 4분류 → 문자 코드를 숫자로 → 미분류 칩 추가. 마지막에 "미분류"의 저장 코드가 0인데 "전체"도 0을 쓰고 있어서, 미분류를 고르면 전체가 나오는 문제를 만났어요. falsy 하나 때문에 URL 값과 저장 코드를 갈라야 했습니다.
서비스 소개 페이지가 "상세검색·기관·유형·태그 필터"를 설명하고 있었는데, 그 필터는 진작 없어졌습니다. 도입 배경은 4차 산업혁명·D.N.A·ICBM 같은 몇 년 지난 문구였고요. RFP와 연차보고서를 근거로 다시 썼습니다. 같은 축에서 읽기 전용 화면의 "필수 *" 표기도 걷어냈는데, 그건 이틀 전 제가 적은 규칙을 뒤집는 일이었습니다.
엑셀 재적재 한 번에 dcat:Catalog의 pk가 35에서 5로 밀렸고, 그 번호를 URL에 박아둔 스펙 11개가 동시에 빨개졌습니다. pk를 URI 조회로 바꾸고, 무엇이 밀리고 무엇이 안 밀리는지 선을 그었습니다. 덤으로 타임아웃 때 원복이 같이 죽어 dev DB가 며칠간 어긋나 있던 것도 잡았어요.
잠긴 버튼에 사유를 title로 달아뒀는데 아무리 호버해도 안 떴습니다. disabled 요소는 마우스·키보드 이벤트를 통째로 죽여서 네이티브 title조차 안 뜬다는 걸 그때 알았어요. aria-disabled로 바꿔 의미만 남기고 클릭은 핸들러에서 막았습니다. 그 다음엔 말풍선이 스크롤 패널에 잘리고, 휠 관성 때문에 떴다 사라지는 문제가 이어졌습니다.
구성 속성의 "필수 여부"를 선택·권장·필수 3단계로 바꿨습니다. 서버 필드는 아직 불리언이라 권장은 화면에만 남는 반쪽짜리인데, 그걸 알면서 먼저 한 이유를 적었습니다. 이어서 표시 순서를 드래그로 바꾸게 만들고, 그 과정에서 이틀 전에 넣은 정렬 버튼을 스스로 지웠습니다.
여행 일정의 순서를 드래그로 바꾸는 기능을 react-dnd로 만들며 겪은 것들입니다. drop이 아니라 hover 시점에 정렬하는 이유, 드래그 아이템의 index를 직접 갱신하지 않으면 hover마다 이동이 재실행되어 순서가 튀는 고전 함정, 그리고 이 글을 쓰며 지금 코드에서 새로 찾은 버그 — 삭제 드롭존이 한 번 빨개지면 안 돌아오는 문제 — 까지 정리했습니다.
로그인 풀림, alert 두 번 뜸, 무한 토큰 갱신 루프 — 증상은 달랐지만 전부 한 뿌리였습니다. 401 처리가 화면마다 흩어져 있었거든요. 서버 에러를 enum 4종으로 분류하는 공통 fetch 래퍼로 모은 과정과, 인증 상태를 boolean이 아니라 3-state로 두는 이유, 그리고 지금 코드에 남아 있는 결함까지 정리했습니다.
표준 메타데이터가 개정될 때마다 마이그레이션을 찍어야 했던 고정 컬럼 모델을, 메타클래스 인스턴스 기반으로 전환한 설계 기록입니다. JSONField·고정 컬럼 유지를 포함한 세 가지 안을 놓고 왜 EAV에 가까운 구조를 골랐는지, 리터럴 값과 객체 참조를 왜 다른 테이블로 나눴는지, 그리고 두 모델을 한동안 동시에 살려둔 전환 전략을 정리했어요.
속성에 달려 있던 is_mandatory를 클래스-속성 관계 행으로 옮긴 작업입니다. 같은 속성이 클래스마다 필수/선택으로 갈리는데 전역 플래그로는 표현할 수 없었어요. 이관 마이그레이션을 짜다 PostgreSQL의 pending trigger events에 걸렸고, 제약을 백필보다 먼저 만드는 순서로 풀었습니다.
클래스 3개짜리 화면이 18개 노드로 그려지던 관계도를, 백엔드를 안 건드리고 중심 이동 탐색으로 바꿨습니다. 배치를 힘 시뮬레이션에서 고정 방사 좌표로 뺐더니 이번엔 노드 두 개가 땅콩처럼 붙었어요. 범인은 forceCenter였고, 고정 노드가 있는 그래프에서 왜 그게 겹침을 만드는지 정리했습니다.
yarn 프로젝트에 package-lock.json이 남아 있어 dependabot 보안 경보가 두 배로 잡히고 있었습니다. Jest는 moduleNameMapper 순서와 tsconfig 때문에 애초에 못 돌고 있었고, Cypress의 chai 타입이 jest-dom 매처 타입을 덮고 있었어요. Next 14→15 메이저 업그레이드까지 한 번에 처리한 기록입니다.
엑셀 3개를 읽던 로더 3개를 워크북 1개를 읽는 로더 1개로 합쳤습니다. pandas가 못 읽는 2단 병합 헤더를 openpyxl로 직접 평탄화하고, 시트 순서 의존을 없애려고 유형별 4패스로 나눴어요. 853행에서 중복 6건이 나왔는데 그 승자 규칙에 아직 구멍이 남아 있다는 얘기까지 적었습니다.
ASN.1 노드 단어를 세어 서버에서 PNG로 그려주던 워드클라우드를, 통합검색 검색어를 세어 클라이언트에서 그리는 화면으로 바꿨습니다. 화면의 성격을 "무엇이 등록됐나"에서 "무엇을 찾았나"로 옮긴 결정과, 같은 집계를 두 군데 두는 바람에 같은 검색어가 화면마다 다른 비율로 보이게 된 이야기입니다.
납품 문서의 기능목록 엑셀을 JSON으로 뽑아 메뉴 136종을 전부 열어보는 Playwright 스윕을 만들었습니다. 설정을 용도별 3종으로 나눈 이유, SQLite 때문에 워커를 1로 고정한 이유, 그리고 확인 못 한 항목을 스킵으로 남겨둔 게 옳은 선택이었는지까지 적었습니다.
카탈로그 PNG 다운로드가 500을 뱉어서 들어갔는데, 원인은 성능 최적화라고 믿고 있던 prefetch_related의 인자 하나였습니다. Dataset에 없는 관계명이라 QuerySet 평가 시점에 AttributeError가 났고, 지연 평가 때문에 선언부가 아니라 버튼 클릭에서 터졌어요. 같은 파일 안에서 따옴표 표기가 갈려 grep이 한쪽만 잡은 이야기와, AST로 인자 478건을 전수 대조한 기록입니다.
값 유형 토글을 누를 때마다 화면이 튀었습니다. 하나 고쳤는데 증상이 남아서 다시 팠더니 원인이 둘이었어요. sr-only로 숨긴 체크박스가 일으키는 focus scroll, 그리고 tbody 전면 재렌더로 scroll anchoring 기준이 사라지는 문제. 각각 어떤 대안들을 놓고 무엇을 골랐는지, 그리고 "안 튀는 것 같다"를 숫자로 바꾼 검증까지 정리합니다.
편집 화면에서 아무것도 건드리지 않고 저장만 눌렀는데, 이미 선택돼 있던 값이 빈 값으로 저장되는 버그가 있었습니다. 세 가지 해결책을 놓고 고민한 끝에 "값은 동기, 라벨만 비동기"로 나누는 씨앗 패턴을 골랐어요. 에러 로그가 남지 않는 조용한 데이터 소실을 어떻게 재현하고 잡았는지 정리합니다.
인스턴스를 수정해도 목록·다운로드·RDF는 옛 값을 보여줬습니다. 링크 시점에 한 번 복사하고 끝이었거든요. 캐시 제거·조회 시 재계산·쓰기 시 시그널 세 안을 놓고 마지막을 고른 이유와, 감사 로그가 폭발하지 않게 bulk_update로 일부러 시그널을 우회한 트레이드오프를 정리합니다.
로컬(SQLite)에선 멀쩡히 적재되던 메타데이터 로더가 운영(PostgreSQL)에서만 value too long으로 죽었습니다. 프런트 쪽을 주로 맡다가 모델·마이그레이션을 조금씩 만지기 시작한 입장에서, 원인을 어떻게 좁혀갔고 네 가지 선택지 중 왜 TextField를 골랐는지 정리합니다.
속성의 range가 CharField 하나였습니다. xsd:string 같은 데이터 유형과 클래스 이름이 같은 칸에 문자열로 들어가 있었어요. FK 두 개 + 상호배타 제약으로 쪼개면서, 왜 단일 FK나 Generic FK로 가지 않았는지, 그리고 데이터 마이그레이션 스크립트 없이 로더 재적재만으로 과거 오적재를 정정한 방법을 정리합니다.
표준데이터·OID·아키텍처·Open API 4개 이용안내 탭을 아코디언에서 캐러셀로 통일한 작업입니다. 네 가지 UI 후보를 놓고 캐러셀을 고른 근거, 감수하기로 한 트레이드오프(페이지 내 검색 불가), 그리고 탭마다 복붙돼 있던 인라인 매크로를 공유 파티얼 하나로 합친 이야기예요.
클래스 참조 필드처럼 옵션이 수백 개인 select를 스크롤로만 찾던 걸 입력 검색으로 좁히는 검색형 select로 되살린 작업입니다. 라이브러리를 다시 들일지 직접 얹을지부터 정하고, placeholder 잘림·× 해제 안 되는 버그, 그리고 Font Awesome svg 모드가 참조를 유령으로 만드는 함정까지 하나씩 잡았어요.
토큰 신청 검토 화면에서 승인/거절을 우측 정렬 버튼 그룹으로 묶고, 거절 사유를 적어둔 채 승인을 눌러버리는 오클릭에 확인 가드를 넣은 작업입니다. 네 가지 마크업 방식 중 form 속성을 고른 이유와, 커스텀 모달 대신 네이티브 confirm을 남겨둔 판단을 정리해요.
관리자가 오퍼레이션을 토글했는데 TTL이 지나야 반영되던 캐시 갭, 그리고 만료된 토큰의 신청 상태가 "승인됨"으로 남던 stale 문제를 정리합니다. 캐시 즉시 무효화와 진입 시점 sync_expired로 "보이는 상태 = 실제 상태"를 맞춘 이야기예요.
데이터 관계도·워드클라우드·트리맵마다 복붙돼 있던 줌 설정을 공용 모듈 d3ZoomControls.js로 추출하고, 확대/축소/맞춤 버튼과 "최초 1회 fit-to-view"를 표준화한 작업입니다. SVG가 100% 크기일 때 뷰포트 크기를 어떻게 구하느냐가 핵심 함정이었어요.
FAQ 게시판을 새 모델을 추가하지 않고 기존 Post 모델 + "FAQ" 카테고리로 구현한 회고입니다. 공지사항과 한 모델을 공유하면서 카테고리로 분리하고, 큐레이션 순서 노출·읽기 전용 본문·데이터 적재 커맨드까지 묶은 이야기예요.
Open API의 데이터 계층을 처음부터 provider 인터페이스로 추상화해 mock으로 화면·계약을 먼저 굳히고, 실데이터 전환은 OrmDataProvider 골격 + 한 줄 스위치로 분리한 과정을 정리합니다. 미완 활성화가 곧 비공개 데이터 유출이라 fail-closed로 설계한 이야기도 담았어요.
총 등록 수·상태 분포만 보여주던 통계 대시보드를, 생애주기·완성도·최근 업데이트·인기 항목 중심으로 다시 짠 작업입니다. "ACT 개수"가 아니라 "완성형(고품질) 데이터가 얼마나 되나"를 묻도록 지표를 바꾼 이야기예요.
항목 수가 늘면서 점점 느려지던 메타클래스 리스트 페이지의 쿼리를 분석하고, select_related/prefetch_related로 정리하면서 의식한 점들을 기록합니다.
백엔드 개발 전 mockup 데이터가 49개라서 페이지네이션·검색·필터 검증이 빈약했던 문제를, 엑셀 파일을 입력으로 받는 시드 스크립트를 만들어 478개로 늘린 과정을 정리합니다.
"거의 안 바뀔 것"이라는 가정으로 정적 JSON에 두었던 네임스페이스 옵션이 실제로는 자주 바뀌고 있었고, 결국 DB 조회로 옮긴 결정과 그 교훈을 정리합니다.
메타클래스 속성을 편집할 때마다 컨텍스트를 가리던 모달을 들어내고, 트리/리스트 옆 인라인 폼으로 옮긴 결정과 그 결과를 정리합니다.
두 페이지가 따로 들고 있던 jstree 옵션과 이벤트 핸들러를 비교해 차이가 거의 없음을 확인하고, 공통 헬퍼 4종으로 추출한 리팩토링 회고입니다.
Tailwind 메이저 업그레이드를 앞두고 시각 회귀를 잡기 위해 Playwright 기반 페이지 캡쳐 스크립트를 만든 경위와, 그 과정에서 만난 작은 어려움들을 정리합니다.
약관/방침 페이지와 소개·가이드 페이지에 흩어져 있던 마크업 중복을 base 템플릿 + 공용 매크로 + 외부 스크립트로 정리한 회고입니다.
디자인과 화면이 조금씩 다른데 뭐가 틀린 건지 모르겠다는 상태를 정리하려고, Figma 44프레임을 코드와 전수 대조해 리포트를 만들었습니다. 핵심은 차이를 찾는 게 아니라 "의도적 조정"과 "결함"을 가르는 규칙이었어요. 그리고 2.5개월 뒤에 확인해보니 HIGH 4건 중 2건이 아직 미반영입니다.
tailwind.config.js 기반 v3에서 CSS 안에서 @theme로 토큰을 정의하는 v4로 전환하면서 발생한 회귀와 패치 과정을 정리한 회고입니다.
FontAwesome 6.x에서 7.x로 메이저 버전 업그레이드를 하면서 검토한 호환성 항목과 실제로 깨졌던 부분, 그리고 회고를 정리합니다.
드롭다운 네비에서 상위 메뉴에서 하위 메뉴로 마우스를 옮길 때 끊기는 현상의 원인을 추적하고, 모든 sub-list height를 h-28로 통일해 해결한 짧은 기록입니다.
"있는데 도움 안 되는 정보"가 안내 화면을 어떻게 무겁게 만드는지, 표준데이터/OID 등록 프로세스 안내에서 중복 단계 카드 흐름을 제거하며 깨달은 점을 정리합니다.
프론트엔드가 먼저 만들어둔 두 작업이 각각 "심사 전 무분별 취소"와 "OID 트리 방향 역전 필요"라는 도메인 문제를 드러낸 과정과, 어떻게 결정·정리했는지에 대한 회고입니다.
Django REST API는 Render에, React 19 + Vite SPA는 Vercel에 나눠 배포하면서 마주친 CORS, 환경변수, 정적 파일, 빌드 파이프라인 이슈를 정리합니다.
본인과 관리자만 열람할 수 있는 1:1 비공개 문의 게시판을 Django로 구현한 과정을 정리합니다. 열람 권한, 답변 권한, 첨부파일 재사용 패턴을 다룹니다.
편집자가 직접 쓴 글과 RSS에서 수집한 글을 하나의 Post 테이블에 두되, source_type 필드로 분기하는 설계의 장단점과 어드민/쿼리 패턴을 정리합니다.
서버에서 graphviz로 PNG를 그려 내려주던 ASN.1 트리를 D3.js 기반 Collapsible Tree로 전환한 과정과 그 이유를 정리합니다. 서버 의존성 제거, 인터랙션, JSON 스키마 설계까지.
jQuery 기반 Django 프로젝트에 D3.js 시각화를 도입한 경험을 정리합니다. 공통 그래프 유틸 설계, 실데이터 부재 시 샘플 폴백, 줌/중앙정렬 개선 포인트를 다룹니다.
로그인 없이 이메일 한 줄로 구독하고, 발송 메일의 고유 링크로 해지할 수 있는 뉴스레터 시스템을 Django + DRF로 설계한 기록입니다.
일회성 스크립트 대신 Django management command로 crawl_news, send_newsletter, seed_editorial, backfill_urls 네 가지를 설계하며 얻은 기준과 패턴을 정리합니다.
한국어 5개 + 영어 4개 RSS 소스에서 Python/Django 관련 글만 골라 수집하고, HTML 정제와 자동 태깅까지 거치는 파이프라인을 Django management command로 정리한 기록입니다. 나중에 다시 읽으면서 키워드 부분 문자열 매칭과 한글 slugify에서 결함 세 개를 찾았고, 재현 스크립트까지 같이 적었습니다.
같은 Django 크롤링 작업을 로컬 crontab(setup_cron.sh)과 Render Cron Job으로 각각 돌려보고 비교한 기록입니다. 다시 읽으면서 "중복 등록 방지"가 실은 "갱신 불가"였다는 것, 로케일 때문에 안 터지는 게 운이었다는 것 등 스크립트 결함 다섯 개를 찾아 같이 적었습니다.
8개 카테고리 커뮤니티 게시판을 단일 Post 모델로 갈지, 카테고리별로 앱을 나눌지. 이번 프로젝트에서 전자를 택한 이유와 트레이드오프를 정리합니다.
헤더에 "쪽지 N개"를 띄우기 위해 모든 뷰에서 카운트를 넘기는 대신, context processor로 전역 주입한 경험과 그 함정을 정리합니다.
Bootstrap 기본 페이지네이션이 파란색 테마 위에서 활성 상태가 묻히는 문제를 카드형 버튼 + 아이콘으로 해결한 회고입니다.
Render의 render.yaml 하나로 Django API(web)와 주간 크롤러(cron)를 각각 다른 서비스로 띄우고, fromDatabase로 DB를 공유한 기록입니다. 다시 읽으면서 SECRET_KEY가 두 서비스에서 서로 다르게 생성되는 것, CORS 환경변수 파싱, dj-database-url 기본값 등 설정 결함 네 개를 찾았습니다.
포트폴리오용 커뮤니티 서비스에서 "비어 보이지 않게, 가짜 같지 않게" 더미 데이터를 만든 과정을 정리합니다. 페이지네이션 검증과 작성자 분산이 핵심이었습니다.
업무상 결제해둔 ITS/V2X 관련 표준 14종(ISO 14817-2, SAE J2735, KS R 1600 시리즈, KS X ISO 14827/15784, ISO TS 19091)을 실제로 읽어보며, 각 표준이 무엇을 규정하고 서로 어떻게 물려 있는지 한 글에 정리합니다.
표준데이터 등록 시스템(SDMS)에 ISO 14817-2의 데이터 개념 라이프사이클을 적용해 기록→제출→사전검증→검증→사전인증→인증→배포의 7단계 상태 머신을 구성한 과정을 정리합니다.
시드 데이터를 data migration에 넣을지, 커스텀 management command로 뺄지. 두 방법의 차이와 실제로 언제 무엇을 택해야 하는지 정리합니다.
AbstractUser를 확장한 커스텀 User 모델에 카카오 OAuth 2.0을 얹어, 일반 이메일 가입과 소셜 로그인을 한 테이블에서 공존시키는 과정을 정리합니다.
심사 프로세스에 승인/반려 외 제3의 축(보완 요청)을 추가했다가, 상태 머신 복잡도와 UX 혼란을 이유로 철회한 의사결정 과정을 정리합니다.
App Router로 만들었다고 믿었던 프로젝트의 루트 레이아웃에 use client가 붙어 있었습니다. 그 한 줄 때문에 트리 전체가 클라이언트 컴포넌트였고, next/head는 무동작이었으며, QueryClient는 모듈 최상단에서 만들어지고 있었어요. 점수를 올리러 갔다가 경계를 다시 그은 기록입니다.
기존 프로젝트의 웹팩 설정을 변경할 때 자주 마주치는 문제들과 체크리스트를 정리합니다.
common 앱에 섞여 있던 설문조사 모델을 독립 앱으로 분리하고 3-tier 아키텍처로 재설계한 과정, 그리고 weasyprint 기반 PDF 다운로드와 개인정보 마스킹 구현을 정리합니다.
JSTree의 주요 플러그인 설정 방법과 contextmenu 플러그인으로 사용자 정의 우클릭 메뉴(추가/편집/삭제/복사)를 구현하는 방법을 정리합니다.
다단계 입력이 필요한 등록 폼에 Creation Wizard 모드와 일반 편집 모드를 분리해서 구현한 과정을 정리합니다. 상태 관리, 트리 연동, "완전 종료" 버그까지.
커밋 메시지 컨벤션(타입, 50자 제목, 명령형)을 정리한 글입니다. 그런데 규칙 정리보다 값진 게 생겼어요 — 제 사이드 프로젝트 2년치 커밋 500여 개를 이력서 재료로 캐면서, 이 글의 "나쁜 예"를 전부 제가 저질렀다는 걸 확인했습니다. 나쁜 커밋의 비용이 언제 어떻게 청구되는지를 뒤에 붙였습니다.
django-treebeard의 MP_Node(Materialized Path)를 활용해 계층형 데이터를 DB에 저장하고 효율적으로 조회하는 방법을 정리합니다.
Django ORM에서 자주 발생하는 N+1 쿼리 문제를 진단하고 select_related, prefetch_related로 해결하는 방법을 정리합니다.
Django 마이그레이션에서 RunPython을 사용해 필드 추가 후 데이터를 채우는 방법과 수동 마이그레이션 시 주의할 점을 정리합니다.
Django에서 여러 폼을 한 페이지에서 처리하는 Formset과 부모-자식 관계를 함께 처리하는 Inline Formset의 차이와 사용법을 정리합니다.
Django의 두 가지 뷰 작성 방식인 FBV(함수형 뷰)와 CBV(클래스형 뷰)의 차이점, 장단점, 언제 무엇을 쓸지 정리합니다.
여행지 목록에서 상세로 들어가면 북마크가 가끔 빈 상태로 보였습니다. isAuthenticated가 null에서 확정되기 전에 요청이 나갔고, 같은 데이터가 캐시 키 두 개로 갈려 있었어요. 고치긴 했는데 지금 다시 보니 원인 진단과 처방이 어긋나 있고 캐시를 통째로 끄는 코드가 남았습니다.
Django ORM의 핵심 원리와 자주 쓰는 메서드를 표로 정리한 레퍼런스입니다. 뒤에 실무에서 실제로 걸린 지점들을 덧붙였습니다 — 지연 평가가 에러 위치를 속이는 문제, update()가 시그널과 auto_now를 건너뛰는 문제, only()가 오히려 쿼리를 늘리는 경우까지. 정리할 때는 몰랐고 운영하면서 배운 것들입니다.
트리 구조 시각화를 위해 jsTree와 VTree를 조사하고 Django Jinja 템플릿에 붙였던 기록입니다. 1년 넘게 운영한 지금 답을 알고 있습니다 — jsTree는 살아남아 트리 화면의 기본이 됐고, VTree는 걷어내고 D3 직접 구현으로 갔어요. 갈린 기준과 함께, 당시 코드에서 지금 보면 걸리는 지점들(텍스트 기반 라우팅, 템플릿 리터럴 JSON 주입)을 정리했습니다.
Exception Not Found의 워크플로우 엔진 DB 설계 8부작을 정리하고, 그 설계를 실제 표준데이터 심사 워크플로우로 구현하면서 무엇을 그대로 따랐고 무엇을 바꿨는지 대조했습니다. 참조 설계가 스스로 지적한 한계 하나는 구현 단계에서 피해갈 수 있었고, 하나는 그대로 남았어요.
Django는 MVT 패턴이라는데 MVC와 뭐가 다른지 정리했습니다. 결론은 이름 매핑 하나로 끝나는데(Django의 View = MVC의 Controller), 실무에서 정작 어려웠던 건 그 다음 — "그래서 이 로직은 View, Model, Template 중 어디에 두나"였습니다. 개념 정리 뒤에 그 경계에서 실제로 겪은 것들을 붙였습니다.
jQuery 기반 트리 플러그인 jsTree의 플러그인·API·이벤트를 표로 정리한 레퍼런스입니다. 실무 트리 화면에 붙이면서 걸린 지점들을 뒤에 덧붙였습니다 — check_callback의 진짜 의미, 루트 노드 id가 #이라는 함정, 기본 contextmenu를 버리고 모달로 간 이유까지.
RDF, 온톨로지, DCAT, 링크드 데이터 — 표준데이터 관리 시스템을 맡게 되며 급하게 공부했던 시맨틱 웹 개념 정리입니다. 특이한 건 이 공부가 교양으로 끝나지 않았다는 것 — 온톨로지의 클래스·인스턴스·속성·관계가 그대로 시스템의 데이터 모델이 됐고, 그 과정에서 개념과 RDB 사이의 간극도 몸으로 배웠습니다. 정리 뒤에 그 이야기를 붙였습니다.
자주 쓰는 깃 명령어를 목적별 표로 정리한 치트시트입니다. 뒤에 실무에서 걸린 지점을 붙였습니다 — checkout 하나가 브랜치 이동과 파일 복원이라는 전혀 다른 두 일을 한다는 것(switch/restore가 나온 이유), reset --hard의 안전망인 reflog, 그리고 표에는 없지만 커밋 고고학에 제일 많이 쓴 log -S까지.
Django 폼 렌더링을 돕는 라이브러리 5종(crispy-forms, widget-tweaks, formify, bootstrap5, dynamic-formset)을 비교 조사했던 글입니다. 결말을 붙였어요 — 실제 프로젝트는 이 중 하나가 아니라 둘(crispy + widget-tweaks)을 역할을 나눠 같이 씁니다. 비교표를 만들 때는 후보들이 경쟁 관계인 줄 알았는데, 절반은 보완 관계였습니다. (원래 2022-08로 게시돼 있던 날짜는 본문이 Django 5.2 문서를 참조해 불가능한 날짜라 조사 시점 기준으로 교정했습니다.)
Tailwind의 단위·색상·레이아웃·반응형을 정리한 레퍼런스입니다. 개고하며 원문을 검증하다 큰 오류를 발견했어요 — "같은 속성은 마지막 클래스가 우선"이라는 전제로 우선순위 절 전체를 썼는데, CSS는 class 속성 안의 순서를 아예 보지 않습니다. 왜 그 오해가 생기는지, 진짜 규칙과 tailwind-merge가 존재하는 이유까지 정정했습니다.
jQuery 기반 로딩 애니메이션 플러그인 waitMe의 사용법과 실제 적용 사례를 정리합니다.
Jinja 템플릿의 문법·필터·상속·매크로를 표로 정리한 레퍼런스입니다. Django 프로젝트에서 Jinja2를 실제로 쓰며 배운 것들을 뒤에 붙였습니다 — url·static이 공짜가 아니라는 것, safe 필터가 사고의 지름길이라는 것, 그리고 원문 필터 표의 예시가 문법부터 틀려 있었다는 고백까지.
목록↔상세를 오갈 때 스크롤 위치를 기억했다가 복원하는 훅을 만들었던 글입니다. 개고하며 코드를 다시 보니 결함이 다섯 개 보여요 — 전역 단일 키, 저장과 복원의 책임 분열, 스로틀 없는 매 이벤트 저장, "맨 위로 돌아간 것"을 기억 못 하는 조건, 그리고 비동기 목록에서는 복원 타이밍 자체가 문제라는 것까지. 원래 코드와 고친 코드를 나란히 실었습니다.
인프런 강의 정리 : 프론트엔드 개발 웹팩
인프런 "실무에 바로 적용하는 스토리북과 UI 테스트"를 듣고 meta/story 문법, Atomic 컴포넌트 조합, Decorator, 네 가지 UI 테스트를 정리했습니다. 강의 자료에서 발견한 구식 API와 안 도는 코드도 함께 적어 두었고, 마지막에는 이걸 다 배우고도 내 프로젝트에는 Storybook을 도입하지 않은 이유를 정리했습니다.
인프런 클린코드 강의를 들으며 정리한 입문 수준의 핵심 개념과 실무 적용 포인트.
TDD, 스냅샷 테스트, 스토리북 기반 시각적 회귀 테스트, Cypress E2E, 테스트 더블까지 2부에서 다룬 내용을 주제별로 압축했습니다. 그리고 1년 반이 지난 지금, 이 중 실제로 실무와 사이드 프로젝트에 남은 것은 무엇이고 끝내 쓰지 못한 것은 무엇인지 정직하게 결산했습니다.
로컬에서 잘 되던 STOMP 채팅이 배포하니까 연결이 안 됐습니다. 원인 후보가 서버 상태부터 인증서까지 열 개쯤 되는 상황에서, 하루 8번의 배포 끝에 mixed content — https 페이지에서 ws:// 차단 — 하나로 좁혔습니다. 후보를 어떤 순서로 제거했는지와, 다음에 같은 일을 겪으면 뭘 먼저 볼지를 정리했습니다.
첫 메시지는 잘 가는데 두 번째부터 화면이 안 갱신되고, 새로고침하면 다 보이는 이상한 증상이었습니다. 저장은 되는데 렌더만 안 되는 것 — React key로 쓰던 messageId가 서버 응답에 없어서 프런트가 난수로 채우고 있었어요. 원인을 좁힌 과정과, 당시 정리에서 스스로 놓쳤던 것까지 다시 봤습니다.
wss 연결 실패를 디버깅하던 중 백엔드 로그에 "Invalid character found in method name [0x16, 0x03, 0x01…]"이 찍혔습니다. 악성 트래픽인가 싶었던 이 바이트들은 사실 TLS 핸드셰이크의 첫 패킷이었어요. HTTP만 받는 포트에 wss를 쏘면 왜 이런 로그가 남는지, 프로토콜 불일치를 로그만 보고 판별하는 법을 정리했습니다.
Vitest와 Testing Library로 단위 테스트부터 통합 테스트까지 다루는 강의를 들으며 정리한 내용을, 챕터 나열 대신 주제별 핵심으로 압축했습니다. 뒤이어 이 중 어떤 개념이 실제 프로젝트(Jest 테스트 복구, Playwright QA 스윕)에서 살아남았고 어떤 개념은 유지되지 못했는지 정직하게 결산했습니다. 강의 예제 자체에서 발견한 낡은 API와 깨지기 쉬운 코드도 함께 짚었습니다.
useInfiniteQuery, useQuery, useSWR로 무한 스크롤을 각각 구현해 비교했습니다. 그런데 실제 프로젝트에 넣어보니 예제 코드 세 개가 전부 문제를 갖고 있었어요. 종료 조건, stale closure, 등호 비교로 바닥을 판정하는 것까지 — 실무에서 걸린 지점을 뒤에 덧붙였습니다.
여행 루트 목록에서 과거 데이터 병합 오류·삭제 데이터 미반영이 반복돼 병합 로직을 재작성했던 기록입니다. 당시엔 해결이라고 썼는데, 이 방식은 결국 뒤집혔어요 — 병합 자체가 문제의 근원이라 react-query를 이 화면에서 걷어내는 것으로 끝났습니다. 중간 기록으로서의 가치가 있어 결말과 함께 남깁니다. (원래 2023-09로 잘못 적혀 있던 날짜를 커밋 기준 2025-01로 교정했습니다.)
useState를 바닐라 JS로 흉내 내봤던 스터디 글입니다. 다시 보니 두 가지가 보여요 — 내가 만든 건 사실 useState가 아니라 구독 기반 외부 스토어(zustand 쪽)였다는 것, 그리고 진짜 useState의 핵심(훅 호출 순서로 상태 슬롯을 찾는 커서)은 하나도 구현하지 않았다는 것. 원 코드를 두고 그 간극을 정리했습니다.
useEffect를 바닐라 JS로 흉내 냈던 스터디 글입니다. 개고하며 돌려보니 버그가 둘 있었어요 — deps 비교가 "배열 전체 vs 항목 하나"를 비교해 항상 변경으로 판정되고, 정작 첫 호출에는 effect가 실행되지 않습니다. 원 코드와 버그 분석, 그리고 커서 모델로 고친 버전을 실었습니다.
무한 스크롤을 붙인 화면마다 이슈가 났는데, 돌아보니 라이브러리 문제가 아니라 전부 "누적된 목록의 주인이 누구인가"가 흐릿해서 생긴 문제였습니다. react-query 캐시와 zustand 스토어가 같은 목록을 두고 싸우다 한쪽을 걷어낸 이야기, 페이지 번호를 컴포넌트가 들고 있다가 캐시에 넘긴 이야기를 정리했습니다.
무한 스크롤 라이브러리를 비교하기 전에 "무한 스크롤을 쓸 것인가"부터 정해야 했습니다. 페이지네이션과의 트레이드오프, 그리고 useInfiniteQuery·useQuery·useSWRInfinite 중 무엇을 고를지의 기준을 정리했습니다. 구현 코드는 별도 글로 뺐습니다.
로그아웃 API를 설계하며 PATCH(상태 전환)와 DELETE(세션 삭제)를 비교했던 글입니다. 실제 프로젝트에서는 PATCH를 채택했는데, 지금 다시 보면 이 비교에는 빠진 선택지(실무 사실상 표준인 POST)와 더 중요한 질문("무엇의 상태인가")이 있었어요. 그리고 예시 코드의 결함이 실제 코드에도 그대로 들어갔다는 것까지 확인했습니다.
면접 대비로 정리했던 이벤트 루프·V8·마이크로태스크 문답입니다. 당시엔 "면접용 지식"이라고 생각했는데, 이후 실무에서 정확히 이 지식으로 풀린 버그들이 있었어요 — 렌더 직후 getBBox가 0을 주던 문제, 구독 콜백의 stale closure. 문답 정리 뒤에 그 순간들을 붙였습니다.
fetch와 axios의 차이를 정리했던 글입니다. 내장 API·번들 크기·SSR 호환이라는 당시 결론은 지금도 유효하지만, 다시 보니 진짜 이유를 놓치고 있었어요 — Next.js App Router는 fetch 자체를 확장해 캐싱·중복 제거를 얹습니다. 실제 프로젝트에서 fetch를 직접 채택하고 래퍼를 만들며 치른 비용까지 붙여서 업데이트했습니다.