투자 포트폴리오 분석 멤버십 플랫폼.
DEF. 원인을 추정하지 않고 진단부터: @next/bundle-analyzer로 본 결과, 첫 번들에 CMS와 차트 라이브러리가 통째로 들어가 있었습니다. 동시에 포트폴리오 탭/필터를 빠르게 전환하면 늦게 도착한 응답이 최신 화면을 덮어쓰는 race condition도 재현되었습니다.
기능이 늘어나도 폴더가 무너지지 않도록 Feature-Sliced Design을 채택. 5계층으로 의존성을 단방향으로 강제.
탭/필터 전환 race condition을 closure + Map + Symbol 토큰 기반 캐싱 훅으로 해결. 늦은 응답은 자동으로 무시.
bundle-analyzer로 진단 → 미사용 패키지 동적 임포트, 폰트 서브셋, GSAP context().revert() 패턴으로 SPA 메모리 누수 차단.
Admin에서 반복되는 필터 store를 제네릭 팩토리로 추상화. 도메인은 타입 인자로만.
결제 진입부터 완료까지의 상태를 단계별로 모델링. 취소·실패·중단·중복 경로를 명시적으로 처리.
키별로 in-flight 요청을 closure에 저장. 같은 키로 새 요청이 들어오면 이전 요청은 폐기 표시 — 늦게 도착해도 setState를 무시합니다.
function useRequestCache<T>(fetcher: (key: string) => Promise<T>) {
const cache = useRef(new Map<string, T>());
const inflight = useRef(new Map<string, symbol>());
const [state, setState] = useState<{key?: string; data?: T}>({});
const get = useCallback(async (key: string) => {
if (cache.current.has(key)) {
setState({ key, data: cache.current.get(key) });
return;
}
const token = Symbol(key);
inflight.current.set(key, token);
const data = await fetcher(key);
if (inflight.current.get(key) !== token) return; // stale
cache.current.set(key, data);
setState({ key, data });
}, [fetcher]);
return [state, get] as const;
}
Awarelab Home
Portfolio Page
Subscription Page
i18n / Multilingual
Admin Dashboard
Admin Member Detail