모듈 상단 상수에 쿠키를 읽으면 왜 안 되는가

JS Module Evaluation Timing

JavaScriptReactRuntime

난이도: ★★☆☆☆
연관 노트: router를 useMemo 의존성에 넣으면 라우트가 바뀌어도 재계산이 안 되는 이유


핵심 요약

모듈 최상위 상수는 JS 번들 모듈이 로드될 때 한 번만 실행되고, useMemo컴포넌트가 렌더될 때 실행되기 때문에 쿠키 같은 런타임 값은 useMemo 안에서만 제때 읽힌다.


왜 헷갈렸나

buildMenuUrl이든 buildMenuUrlWithParams든 상수 초기화 시점에 평가되니까 어차피 의미없다”는 설명을 들었는데, 함수를 두 개로 나누거나 이름을 바꾸는 것과 무슨 차이인지 바로 연결이 안 됐다.
문제가 “어떤 함수를 쓰느냐”가 아니라 “어느 위치에서 호출하느냐”임을 몰랐다.


메커니즘

JS 모듈은 처음 import될 때 모듈 스코프 전체가 위에서 아래로 1회 실행된다. 이후 같은 모듈이 다시 import돼도 이미 캐시된 결과를 반환할 뿐 재실행되지 않는다.

번들 로드
  └─ Header.tsx 모듈 평가
       └─ const NAV_MENUS = [{ to: buildMenuUrl("/") }]
            └─ buildMenuUrl() 실행 → 이 시점 쿠키를 읽음
                 ※ 이후 쿠키가 바뀌어도 NAV_MENUS는 고정

반면 useMemo는 컴포넌트 렌더 사이클에 속한다:

사용자가 페이지 이동 (리렌더)
  └─ Navigation 컴포넌트 실행
       └─ useMemo(() => [{ to: buildMenuUrl("/") }], [isActive])
            └─ buildMenuUrl() 실행 → 이 시점 쿠키를 읽음
                 ※ 렌더마다 최신 쿠키 반영 가능

해결 패턴

// ❌ 모듈 최상위 — 로드 시점에 고정
const MENUS = [
  { text: "홈", to: buildMenuUrl("/") },
];

// ✅ useMemo — 렌더 시점에 평가
const menus = useMemo(() => [
  { text: "홈", to: buildMenuUrl("/") },  // 렌더마다 최신 쿠키 읽음
], []);

dependency 배열이 []여도 괜찮은 경우: 쿠키는 페이지 로드 시 한 번 세팅되고 세션 중 변경되지 않는 구조라면, 컴포넌트 마운트 시점(= 첫 렌더)에 읽어도 충분하다.


다음에 이 상황을 만나면

“상수에 함수 호출 결과를 담았는데 값이 안 바뀐다” → 즉시 위치 확인:

  • 모듈 최상위인가? → 렌더 사이클(useMemo, 컴포넌트 body)로 이동
  • useMemo의 dependency가 비어있는가? → 런타임 값이 렌더 간에 바뀌는지 확인
  • 읽으려는 값이 모듈 로드 이후에 세팅되는가? (쿠키, 토큰, localStorage)

커넥팅 닷

← 선행 개념 (이걸 알아야 이해된다)

→ 확장 개념 (여기서 더 나아가면)

  • React 렌더 사이클 전체 — useMemo, useCallback, useEffect가 각각 어느 시점에 실행되는지 정확히 구분하는 것으로 연결
  • SSR/SSG에서의 모듈 평가 — 서버에서 번들이 로드될 때도 동일한 1회 평가 원칙이 적용됨. typeof window === "undefined" 가드가 필요한 이유

↔ 같은 원리가 적용되는 곳


참고