이 글에서 다루는 것
파일 업로드, CSS 테마, 패널 리사이즈라는 겉보기 다른 작업을 하면서, 셋 모두 "단일 진실 원천과 값이 갈리는 경계" 를 명시하지 않으면 무너진다는 공통점을 정리했다.
하루 요약
| 작업 | 무엇을 하고 싶었나 | 한 일 | 결과 |
|---|---|---|---|
| 업로드 | 4단계 계약을 빠진 단계 없이 문서화 | presign → PUT → complete → create; 더미 URL이면 PUT 스킵 | complete 통지로 백엔드가 업로드 인지 |
| 테마 | @layer·@scope·light-dark() 축 분리 + 단일 CSS 생성기 | 토큰 추상화, @layer로 명시도 전쟁 제거, 런타임·빌드 한 생성기로 통일 | 런타임·빌드 CSS가 어긋나지 않음 |
| 레이아웃 | fr 비율 저장 + localStorage는 마운트 후 1회 복원 | px → fr 비율 저장; 복원을 mount 후로 미룸 | 창 크기 자동 대응 + hydration 일치 |
1. 직접 업로드 계약
배경지식 (개념)
- Presigned URL: 서버가 서명한 URL로 클라이언트가 스토리지에 직접 PUT 하는 방식.
어떤 상황이었나
큰 파일을 앱 서버로 중계하면 부하·타임아웃이 크다. 클라이언트 → 스토리지 직접 업로드가 필요했다. 4단계 계약을 빠진 단계 없이 문서화하는 게 목표였다.
핵심 작업
큰 파일을 앱 서버로 중계하는 대신, 백엔드가 서명한 presigned URL을 받아 클라이언트가 스토리지로 직접 PUT 한다. 핵심은 단계 계약: presign → PUT → complete → create. complete 통지를 빠뜨리면 백엔드가 업로드를 인지하지 못한다. 목 환경에서 PUT 스킵 여부는 전역 플래그가 아니라 URL이 더미인지로 판단한다.
교훈
- 스토리지는 PUT 성공을 백엔드에 자동 통지하지 않는다 — complete 통지가 필수다.
- "SoT가 어디고 어디서 값이 갈라지나" 를 명시하면 충돌을 구조적으로 막는다.
2. CSS 토큰 테마
배경지식 (개념)
@layer·@scope·light-dark(): 각각 우선순위·범위·모드를 담당하는 CSS 축. 서로 독립적으로 다뤄야 명시도 충돌이 없다.
어떤 상황이었나
런타임 주입 CSS와 빌드 산출 CSS가 갈라지면 토큰·우선순위가 어긋난다. 축 분리 + 단일 CSS 생성기가 목표였다.
핵심 작업
테마를 토큰(커스텀 프로퍼티)으로 추상화하고, @layer(우선순위)·@scope(범위)·light-dark()(모드)를 각각 독립 축으로 분리한다. 런타임 주입 CSS와 빌드 산출 CSS가 갈라지지 않도록 하나의 생성기를 단일 진실 원천으로 둔다. 결과적으로 런타임·빌드 CSS가 어긋나지 않는다.
교훈
- 테마는 우선순위·범위·모드를 독립 축으로 나누고, CSS 생성은 한 생성기로 단일화한다.
3. fr 레이아웃과 하이드레이션 경계
배경지식 (개념)
fr(CSS grid/flex): 남는 공간을 비율로 나누는 단위. px보다 창 크기 변화에 견고하다.- Hydration mismatch: 서버 HTML과 클라이언트 첫 렌더가 달라 React가 경고·깜빡임을 내는 현상.
어떤 상황이었나
패널 폭을 px로 저장하면 창 크기가 바뀔 때 재계산이 번거롭고, localStorage를 스토어 초기화 시점에 읽으면 SSR과 클라이언트 첫 렌더가 달라진다. fr 비율 저장 + localStorage 마운트 후 1회 복원이 목표였다.
핵심 작업
패널 드래그 리사이즈에서 폭을 px가 아니라 fr 비율로 저장하면, 컨테이너 폭이 바뀌어도 재배분이 자동이다. 저장된 레이아웃을 localStorage에서 복원할 때는 마운트 후 1회로 미뤄 서버 렌더와 첫 클라이언트 렌더가 일치하게 한다. 창 크기 자동 대응 + hydration 일치를 얻었다.
교훈
- 비율(fr) 기반 상태는 절대값(px)보다 환경 변화에 견고하다.
- localStorage·
window는 마운트 후에만 — SSR 첫 렌더와 맞춘다.