prefetch는 내려받기만 하고, prerender는 내려받은 뒤 렌더링까지 시작한다.

Tachyon은 <a>에 커서가 50ms 이상 머물면 <link rel="prerender">를 넣어주는 라이브러리.

#268

대용량 리스트에서 selected 아이템 조회 최적화

O(n×m) → O(n+m)로 개선: Map으로 인덱싱

// 기존: 매번 find (느림)
selected.map((key) => items.find((item) => item.key === key))

// 개선: Map 인덱싱 (빠름)
const itemsMap = new Map(items.map((item) => [item.key, item]))
selected.map((key) => itemsMap.get(key)).filter(Boolean)

React에서 백그라운드 인덱싱:

function useItemsIndex(items: Item[]) {
  const [map, setMap] = useState(new Map())
  const [ready, setReady] = useState(false)

  useEffect(() => {
    // 청크 단위로 처리하여 UI 블로킹 방지
    const newMap = new Map(items.map((item) => [item.key, item]))
    setMap(newMap)
    setReady(true)
  }, [items])

  return { map, ready }
}

10만개 이상이면 Web Worker 고려.

#508

WeakMap의 키를 “입력 배열의 참조 자체”로 쓰면 캐시 무효화 로직이 0줄이 된다 — 무효화를 참조 동등성에 위임.

const indexCache = new WeakMap<Item[], Map<string, string>>()

// name → id 역인덱스를 1회 빌드 (O(n))
const buildIndex = (items: Item[]) =>
  new Map(items.map((it) => [it.name, it.id]))

function lookup(items: Item[], name: string): string | null {
  let index = indexCache.get(items)
  if (!index) {
    index = buildIndex(items) // 캐시 미스일 때만 빌드
    indexCache.set(items, index)
  }
  return index.get(name) ?? null
}
  • 같은 참조로 재호출 → cache hit, O(1)
  • 데이터가 새 참조로 교체(refetch 등) → 자동 miss → 새 index 빌드. 옛 index는 옛 items와 함께 GC 대상
  • 키가 weak reference라 items가 어디서도 안 잡히면 entry도 같이 사라짐. 일반 Map이면 참조가 영구히 붙들려 메모리 누수

전제: 호출 측 items 참조가 stable해야 작동. 매번 [...data]·data.filter()로 새 배열을 넘기면 항상 miss라 무의미. (TanStack Query의 data/select 결과는 참조 안정성을 보장하므로 그대로 넘기면 OK)

참고

핵심: parser 진행과 first paint는 분리된다. 깃발(first paint)이 보라 사각형(JS 실행)보다 왼쪽이면 — JS가 손대기 전에 그려지면 — 사용자가 잘못된 상태를 본다.

세 로딩 전략의 타임라인 — first paint(깃발)와 JS 실행(보라 사각형)의 선후로 flash window 발생 여부 비교
  • non-blocking (async/defer): paint가 JS보다 먼저 → flash window(빨간 구간) 발생.
  • parser-blocking (동기 <script>): parser는 멈추지만 그 위 청크는 먼저 그려질 수 있어, default 상태가 한 프레임 노출될지가 브라우저 재량 = 보장 없음.
  • blocking=render (<head> module): paint가 JS 뒤로 → 유일하게 깨끗.

트레이드오프: “1프레임 flash/CLS”를 막는 대가로 “FCP를 JS 실행시간만큼 미루는” 거래라, 스크립트가 작고 인라인일 때만 성립한다.

blocking=render를 써도 되는 세 조건 — 작고 인라인인 스크립트, head 안 배치, 무시돼도 안전한 폴백

성립 조건 셋:

  1. 스크립트가 작고 인라인 (FCP 지연을 감당할 만큼).
  2. <head> 안에서만 동작 — 컴포넌트 마크업 뒤가 아니라 head에 type="module" blocking="render"로 올려야 보장이 생긴다 (원문이 놓친 부분).
  3. attribute가 무시돼도 옛 동작으로 떨어질 뿐 깨지지 않는다 → 라이브러리가 권장 패턴으로 제공하려면 이 안전한 폴백이 핵심.
#567