개발/프론트엔드

⚡ Next.js prefetch false 언제 쓰나 · 기본값과 서버 부하

개발만빼고다잘함 2026. 10. 23. 08:17
반응형

Next.js <Link>는 화면에 보이기만 해도 링크 너머 페이지를 미리 받아 온다. 링크가 50개 깔린 목록을 만들다 보면 이게 서버를 50번 두드리는 건 아닌지 고민이 생긴다. 끄면 클릭이 느려질 것 같고 켜 두면 트래픽이 걱정이다. 기준은 2026년 10월 15일(UTC) 릴리스된 v16.4.2 문서다.

특히 유의해야 할 점은 prefetch가 프로덕션에서만 돈다는 것이다. next dev에서 네트워크 탭을 보면 prefetch 요청이 안 보여 고장인 줄 알기 쉽다. <Link>는 클라이언트 컴포넌트라 하이드레이션이 끝나야 prefetch를 시작한다.

기본값이 미리 받는 범위

App Router에서 prefetch를 안 쓰면 "auto"(또는 null)다. Cache Components를 켜지 않은 기본 설정에서는 정적 라우트와 동적 라우트를 다르게 다룬다.

라우트생략(auto)truefalse
라우트 생략(auto) true false
정적 전체 라우트 전체 라우트 안 함. 클릭할 때 받는다
동적, loading.js 있음 레이아웃부터 첫 loading 경계까지 전체 라우트 안 함. 서버 렌더를 기다린다
동적, loading.js 없음 건너뛴다 전체 라우트 안 함. 서버 렌더를 기다린다

미리 받은 결과는 메모리에 이만큼 남는다.

  • 정적 페이지 : 기본 5분
  • 동적 페이지 : 기본 0초. 단 loading 경계는 5분 동안 재사용한다

동적 라우트를 건너뛰는 건 안 갈 수도 있는 라우트에 서버 작업을 쓰지 않으려는 설계라고 문서가 밝힌다. 기본값이 이미 서버 부하를 아끼는 쪽이다.

링크가 많은 페이지의 네트워크

링크가 많아도 한꺼번에 쏟아붓지 않는다. 작은 작업 큐가 이 순서로 처리한다.

  • 1순위 : 뷰포트 안 링크
  • 2순위 : hover나 터치로 의도를 보인 링크
  • 교체 : 새 링크가 오래된 링크를 밀어낸다
  • 폐기 : 화면 밖으로 스크롤된 링크는 버린다

마트 시식 코너가 손님 동선에 맞춰 접시를 내고 지나간 통로 접시는 치우는 것과 비슷하다. 무한 스크롤 표처럼 링크가 끝없이 늘어나는 곳은 문서도 끄는 걸 예로 든다. 끄기 아깝다면 hover 때만 켠다. 문서 예제의 JS판에 주석을 달았다.

'use client'
import Link from 'next/link'
import { useState } from 'react'

export function HoverPrefetchLink({ href, children }) {
  const [active, setActive] = useState(false)
  // hover 전에는 false로 막는다. 올리면 null이 되어 기본 prefetch가 살아난다
  return (
    <Link href={href} prefetch={active ? null : false} onMouseEnter={() => setActive(true)}>
      {children}
    </Link>
  )
}

Partial Prefetching을 켠 앱은 active ? true : 'auto'로 쓰라고 문서가 따로 적었다. 라우터 차이도 잊지 말자. App Router의 false는 hover 때도 받지 않는다. Pages Router의 false는 뷰포트 진입만 막고 hover 때는 받는다.

동적 페이지 클릭이 멈칫할 때

동적 페이지 링크가 한 박자 멈췄다 넘어간다면 공식 권장은 loading.js다. 이 파일이 있으면 레이아웃과 로딩 화면까지 미리 받는다. prefetch가 끝나 있으면 클릭 즉시 넘어간다. 다만 레이아웃이 cookies()나 캐시 안 된 fetch를 쓰면 그 렌더가 끝날 때까지 이동이 막힌다.

// app/blog/[slug]/loading.tsx
// 이 파일 하나로 동적 라우트가 '건너뜀'에서 '부분 prefetch'로 바뀐다
export default function Loading() {
  return <LoadingSkeleton /> // 클릭 직후 보일 뼈대 화면
}

true를 동적 링크에 일괄로 붙이면 기본값이 피하려던 서버 작업을 다시 떠안는다. 웬만하면 loading.js부터 두는 편이 낫다.

캐시 시간은 staleTimes로

동적 페이지의 0초가 아쉬우면 staleTimes로 늘린다. 아직 Experimental 기능이다.

// next.config.js
module.exports = {
  experimental: {
    staleTimes: {
      dynamic: 30, // 동적 페이지(초). 기본 0
      static: 180, // 정적 페이지·prefetch={true}·router.prefetch(초). 기본 5분
    },
  },
}

언제 끄고 언제 켜나

상황선택
상황 선택
무한 스크롤 표, 긴 링크 목록 false 또는 hover 때만
푸터처럼 미리 받을 필요 없는 링크 false
동적 페이지 클릭 후 멈칫함 loading.js 추가
화면 밖이지만 곧 갈 페이지 router.prefetch()
레이아웃에서 조회수 기록 기록 코드를 useEffect로 이동

레이아웃·페이지의 분석 호출 같은 부수효과는 방문이 아니라 prefetch 때 실행될 수 있다. 조회수가 부풀었다면 여기부터 의심해보자.

16.3에 들어온 Partial Prefetching

16.3.0에 생긴 partialPrefetching(cacheComponents 필요)을 켜면 라우트당 App Shell 하나를 링크끼리 나눠 쓴다. 대신 이 모드의 prefetch={true}는 링크마다 서버 호출 비용이 든다. 전부 정적인 페이지만 정적 캐시에서 나간다. 다음 메이저에서 두 기능이 기본이 되고 옵션은 없어진다고 문서가 예고했다.

<Link>의 transitionTypes가 기대는 ViewTransition은 React 19.3 정리 글에 따로 적어 뒀다.

손댈 순서는 단순하다. 동적 라우트에 loading.js가 있는지부터 본다. 그다음 링크가 수십 개 깔리는 목록 하나만 hover 방식으로 바꾸고 빌드 결과물의 네트워크 탭을 비교해보면 된다.

참고

반응형