코드 레벨로 보는 Next.js <Link/>

질문Bot·2025년 11월 26일

Next.js

목록 보기
10/13
post-thumbnail

Next.js 개발하다 보면 <a/> 태그 대신 <Link/> 태그 많이 쓰시죠?

다들 명확한 이유를 아시나요?
구글링을 하다보면 “새로고침이 안 되니까”, “내부 라우팅이라서” 이렇게만 알고 넘어가는 경우가 많죠...그게 사실 저입니다..ㅎㅎ

그러다 회사에서 사이드바 리팩토링을 하면서 정말 코드 레벨로 정리할 필요가 생겼고,
Next.js 공식 GitHub로 들어가 Link 컴포넌트에 대해서 한번 정리해 봤습니다.


⚙️ 기본적인 차이

✔️ 페이지 새로고침 방지

a태그 클릭 → 전체 페이지를 다시 요청 → React 상태 모두 초기화
Link 태그 클릭 → 클라이언트 라우팅 → 새로고침 없음 → 상태 유지

✔️ 내부 라우팅

a 태그 → 외부 링크용
Link 태그 → Next.js 내부 페이지 이동용

✔️ 브라우저 히스토리

Link 태그 → router.push/replace로 History API를 직접 다룸 → 즉, 앞/뒤 모두 정상 동작
a 태그 → 전체 리로드라 히스토리 구조도 달라진다.


제가 하는 프로젝트 사이드바는 2중 구조에 조건 처리, 열리고/닫히는 UI 로직이 많은 상황이었습니다.
그래서 리스트마다 단순하게 Link 태그만 씌우기가 애매했고,

아래처럼 버튼 + router.push() 방식으로 처리하고 있었죠.

<button onClick={() => router.push(child.href)}>
  {child.title}
</button>

문제는…

🤦‍♂️ dev 환경에서는 너무 느림…으…느려도 너무 느려,,,,

Next.js는 dev 모드에서 페이지 이동할 때마다
전부 새로 컴파일 → 새로 빌드 → 트랜스폼이 일어나니까
router.push 로직으로는 페이지 전환이 너무 답답했어요.

그래서 “미리 관련 파일을 받아오자!” 라는 생각으로 리팩토링을 진행했습니다.


Vercel - Next.js github

1. 전역 prefetch 관리: Set

const prefetched = new Set<string>()
  • prefetch는 한 번만 하면 되기 때문에
  • href + as + locale 조합으로 key를 만들고
  • 이미 가지고 있다면 다시 prefetch 하지 않음

즉, 중복 요청 자동 방지 구조를 가지고 있습니다~


2. PrefetchOptions

type PrefetchOptions = RouterPrefetchOptions & {
  bypassPrefetchedCheck?: boolean
}

bypassPrefetchedCheck: true의 의미는
→ 기존 캐시와 무관하게 강제로 prefetch
→ hover 시 항상 최신 데이터를 위해 사용


3. prefetch 함수

function prefetch(router, href, as, options) {
  if (typeof window === 'undefined') return
  if (!isLocalURL(href)) return
  
  router.prefetch(href, as, options).catch(err => {
    if (process.env.NODE_ENV !== 'production') {
      throw err
    }
  })
}
  • window 환경에서만 동작 (SSR에서는 의미 없음)
  • 내부 URL만 prefetch
  • prefetch 실패해도 prod에선 조용히 무시

🤔 실제로 prefetch는 언제 실행될까요?

이 파트가 조금 재미있는 코드였습니다…(개인적인 생각)

정확히 두가지의 타이밍입니다.

1. 화면 안으로 들어왔을 때

const [setRef, visible] = useIntersection({ rootMargin: '200px' })

useEffect(() => {
  if (visible && prefetch !== false) {
    prefetch(...)
  }
}, [visible])

화면에 해당 Link가 보이면 → 사용자가 곧 클릭할 가능성이 높기 때문
미리 JS 번들, SSG JSON을 받음

즉, 스크롤만 해도 필요한 페이지를 미리 받아 놓음

2. 마우스 hover

prefetch(router, href, as, {
  priority: true,
  bypassPrefetchedCheck: true
})

hover 시에는 무조건 작동할수있게 priority를 올려서 급하게 받습니다.


🙃 prefetch는 SSR 데이터까지 미리 땡길까요?

아닙니다!!!

prfetch는 아래와 같이

항목prefetch 유무
JS 번들✔️
SSG(getStaticProps)✔️
SSR(getServerSideProps)

SSR 데이터는 달라지는 요소가 많아 prefetch를 하지않는것 같습니다.


➕ 추가적인 로직

isModifiedEvent

(event.metaKey || event.ctrlKey || event.shiftKey || ...)
(target !== '_self')

위에 상황은 아래와 같은 경우입니다.

  • 유저는 새탭 띄우고 싶음
  • 하지만!! 브라우저 기본 동작이 더 중요한 상황

이러면 next는 가로채지 않습니다. 그냥 a태그처럼 동작을 진행함


결론 : Link란? 클릭 가로채서 router로 바꿔주는 컴포넌트

const handleClick = (e) => {
  if (isModifiedEvent(e)) return
  if (!isLocalURL(...)) return

  e.preventDefault()
  router.push(...)
}

쉽게 말해, browser navigation → Next.js 클라이언트 navigation으로 변환


Link태그는 단순 새로고침이 안일어나는 컴포넌트다~가 아닙니다!

  • 내부 라우팅을 완전히 SPA 방식으로 유지 -> 상태 유지 및 전체 리로드가 없습니다
  • prefetch를 이용한 라우팅 속도 향상 -> 뷰포트 혹은 hover일때 미리 로드함
  • 브라우저 UX를 방해하지 않음 -> 새 탭 등 기본 동작 유지
  • 예측 가능한 라우팅 보장 -> 클릭 이벤트를 가로채서 router.push로 동작
profile
유용한 정보를 전달하는 사람이 되고자 노력합니다.

0개의 댓글