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) | 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 방식으로 바꾸고 빌드 결과물의 네트워크 탭을 비교해보면 된다.
참고
- Next.js Docs, Link Component https://nextjs.org/docs/app/api-reference/components/link
- Next.js Docs, Prefetching https://nextjs.org/docs/app/guides/prefetching
- Next.js Docs, Linking and Navigating https://nextjs.org/docs/app/getting-started/linking-and-navigating
- Next.js Docs, staleTimes https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
- Next.js Docs, partialPrefetching https://nextjs.org/docs/app/api-reference/config/next-config-js/partialPrefetching
'개발 > 프론트엔드' 카테고리의 다른 글
| 🛡️ Next.js 16.4.1 보안 업데이트 · 내 앱 해당 조건표 (0) | 2026.10.28 |
|---|---|
| 🟦 TypeScript 7.1 베타 · es2026 타깃과 달라진 점 (0) | 2026.10.27 |
| 🧯 Sentry 에러 로그 자세히 남기기 · v11은 기본 수집이 늘었다 (0) | 2026.10.23 |
| ⚛️ React 19.3 출시, ViewTransition canary 없이 쓴다 (0) | 2026.09.16 |
| 📱 Shopify가 React Native 버리고 네이티브로 돌아간 이유 (0) | 2026.09.11 |