배경 화면 파일이 API 경로를 직접 조립하고(40여 곳) 변경 요청을 직접 보냈으며(20여 곳), 정렬 · 판정 · 변환 같은 규칙이 컴포넌트 안에 섞여 있었다. 조회 Hook 은 경로가 바뀌어도 이전 응답을 돌려줘 새 이름표 옆에 옛 숫자가 보였고(문제 140), 화면 여럿이 조회 오류를 그리지 않아 무한 로딩이 됐고(141), 응답 순서 경합 · 처리되지 않은 거부 · 남는 오류가 있었다(142).
네 층 (의존은 위에서 아래로만)
| 층 | 하는 일 |
|---|---|
pages · components |
그리기와 화면 상태(탭 · 펼침 · 입력값) |
lib/hooks |
상태 · 부수효과 — useApi(조회) · useAction(변경 요청) · usePolling(주기 호출) · useEventNames · useSiteConfig(화면 설정) |
lib/api |
기능별 모듈(company · region · alert · insight · notify · ops · session)이 경로 · 메서드 · 응답 타입을 한 곳에, client 가 fetch · CSRF · 경로 검사. config 는 Next 의 /api/config(런타임 화면 설정) |
lib/domain |
React 없는 순수 함수 — 표기 · 색 기준 · 변환 · 판정 (단위 테스트) |
lib/api/client · fetch 금지(기능 모듈만). lib/domain 은 React · next · 화면 · 네트워크 · 브라우저 전역
금지, API 는 응답 타입만 import type.data 는 지금 경로의 값만, 이전 조건의 값은 stale 로 따로(성적표처럼 흐리게 남길 곳만). 경로가 바뀌거나
화면을 떠나면 요청 취소. 상태 전이는 순수 함수(requestState)라 React 없이 시험.useAction(시작할 때 이전 오류를 지우고 실패는 오류로 담음, 진행 중 잠금), 주기 호출은 usePolling(탭이 보일 때만).react-hooks/set-state-in-effect 켬) — 브라우저에만 있는 값은 useSyncExternalStore, 나머지는 상태에서 파생.queries · commands 한 모듈로 모았는데 모든 화면의 첫 로드 JS 가 약 2.4 KB 늘었다. 웹팩은
모듈마다 앱 어딘가에서 쓰는 export 를 모두 남기므로 공통 묶음이 쓰는 모듈은 통째로 들어간다 — 네임스페이스 import · 이름 import ·
sideEffects 로는 줄지 않음을 빌드로 확인했다. 백엔드 기능 모듈처럼 나누고 머리말이 쓰는 로그인 · 알림 수만 session 으로 두었다.TZ=UTC), E2E 27 → 41개(새 14개는 수정 전 실패를 확인), 린트 경고 0(규칙 모두 켬).덧붙임 (2026-10-09, 독립 검토 뒤 — 문제 160~167) 목록 화면도 상세 화면과 같은 표시 순서(값이 없을 때의 오류 → 불러오는 중 → 없음 → 내용)로 맞췄고 — 같은 조건을 다시 부르다 실패하면 보이던 값은 남김(163 · 167), 값을 스스로 들고 있는 지도 컴포넌트는 새 값을 기다리는 동안 툴팁을 띄우지 않는다(162 — 140 과 같은 종류가 ‘경로에 묶인 응답’ 밖에 남아 있었음). 지도 E2E 는 카카오 SDK 대역(폴리곤 이벤트 · 툴팁 상태를 기록하는 작은 스크립트)으로 외부 망 · 키 없이 돈다. 단위 테스트 39 · E2E 47개.
조회 캐시 · 재시도 · 낙관적 갱신이 필요해지면 그때 라이브러리를 검토한다. 기능 모듈 하나가 여러 화면의 공통 묶음을 키우면 다시 나눈다 (같은 측정으로 확인).
덧붙임 (2026-10-11, VERIFICATION 203) — 경계 규칙의 빈틈
lib/domain 규칙이 @/ 경로만 봐서 ../api/client 같은 상대 경로가 통과했고, 화면의 fetch 금지가 이름 fetch 만 봐서 window.fetch · globalThis.fetch 가 통과했다.
lib/domain 은 상위 상대 경로를 금지하고, 세 객체(window · globalThis · self)의 fetch 속성을 금지한다. 우회 모양은 lib/boundaries.test.ts 가 ESLint 로 직접 검사해
고정한다(백엔드 ArchitectureTest 의 ‘우회하기 쉬운 모양’과 같은 역할).