Link 컴포넌트
Link
는 React의 a
태그를 확장한 Next.js 컴포넌트로, 페이지 간 이동 시 클라이언트 사이드 네비게이션과 사전 로딩(prefetching)을 지원해요. 덕분에 사용자는 더 빠르고 부드러운 페이지 전환을 경험할 수 있답니다. Next.js에서 라우트 간 이동하는 가장 기본적이고 권장되는 방법이에요.
기본 사용법은 이렇게 간단해요:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
위 코드에서 href
속성에 이동할 경로를 넣으면 되고, 내부 텍스트나 컴포넌트가 링크로 표시됩니다.
좀 더 알아두면 좋은 팁!
Link
내부에<a>
태그를 써서 스타일을 직접 주거나 추가 속성을 넣을 수도 있어요:
<Link href="/about">
<a style={{ color: 'red' }}>About Us</a>
</Link>
-
최신 Next.js 버전에서는 내부에
<a>
태그를 생략하고 바로 텍스트나 컴포넌트를 넣어도 자동으로 처리해줘서 더 편해졌습니다. -
prefetch
속성으로 미리 데이터를 불러오게 할 수 있는데, 기본값은true
예요. 페이지가 백그라운드에서 빠르게 로드되도록 도와줍니다. -
Link
를 사용할 때는 외부 링크에는 적합하지 않아요. 외부 주소를 연결할 땐 일반a
태그나next/head
내의meta
태그를 쓰는 게 맞습니다.
이렇게 Link
컴포넌트를 활용하면 Next.js 앱에서 라우팅이 훨씬 깔끔하고 빠르게 진행돼, 사용자 경험도 크게 향상되니 꼭 기억해두세요!
참고자료
Link
컴포넌트에 전달할 수 있는 주요 props를 정리해봤어요. 이걸 알면 링크를 어떻게 제어할 수 있는지 감이 딱 옵니다!
Prop | 예시 | 타입 | 필수 여부 |
---|---|---|---|
href | href="/dashboard" | String 또는 Object | 필수 |
replace | replace={false} | Boolean | 선택 |
scroll | scroll={false} | Boolean | 선택 |
prefetch | prefetch={false} | Boolean 또는 null | 선택 |
onNavigate | onNavigate={(e) => {}} | Function | 선택 |
팁!
Link
에className
이나target="_blank"
같은a
태그 속성도 props로 넣으면 내부a
태그에 똑같이 전달돼요.
예를 들어 새 탭에서 열고 싶으면 다음처럼 쓰면 됩니다:
<Link href="/page" target="_blank" className="my-link">Go to Page</Link>
href
href
는 어디로 연결할지 경로나 URL을 지정하는 데 꼭 필요해요. 문자형 문자열이나 URL을 나타내는 객체 둘 다 써도 됩니다.
replace
replace
가 true
면 링크를 이동할 때 브라우저 히스토리를 새로 만들지 않고 현재 기록을 덮어쓰게 돼요. SPA에서 뒤로 가기 버튼을 눌렀을 때 이전 페이지로 돌아가지 않게 할 때 유용하죠.
scroll
기본적으로 Link
를 클릭하면 페이지가 최상단으로 스크롤 돼요. scroll={false}
를 주면 그 동작을 막을 수 있어요. 스크롤 위치를 유지하고 싶을 때 기억해두면 좋아요.
prefetch
prefetch
속성은 해당 링크의 페이지 데이터를 미리 불러올지 결정합니다. 네트워크 비용을 약간 부담하더라도 미리 로딩해두면 사용자 경험이 훨씬 부드러워져서 개인적으로 자주 사용해요.
false
를 주면 미리 로딩하지 않습니다.
onNavigate
링크 이동 시 실행할 커스텀 함수도 넣을 수 있어요. 예를 들어, 링크 클릭 전 알림 창을 띄우거나, 이동을 조건부로 막을 때 활용할 수 있죠.
저도 이 링크 컴포넌트 쓸 때, 어떻게 하면 사용자 경험이 더 좋아질까 하면서 이 props들을 잘 활용하는 편입니다. 애플리케이션 특성에 맞게 적절히 조합해서 써보세요! 혹시 생소한 props 있으면 댓글이나 질문 주세요 :)
href (필수)
href
는 이동하고자 하는 경로나 URL을 지정하는 속성이에요.
import Link from 'next/link'
// /about?name=test 경로로 이동하는 예제
export default function Page() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
위 코드처럼 href
에는 문자열 경로뿐만 아니라 pathname
과 query
객체 형태로도 전달할 수 있어요. 이렇게 하면 URL 쿼리 스트링을 깔끔하게 관리할 수 있죠.
replace
replace
는 링크를 클릭했을 때 현재 페이지를 대체해서 히스토리에 쌓이지 않도록 하는 옵션이에요.
예를 들어:
import Link from 'next/link'
export default function Page() {
return (
<Link href="/about" replace>
About (Replace)
</Link>
)
}
이렇게 하면 사용자가 링크를 누르면 /about
으로 이동하지만, 브라우저 뒤로 가기 버튼을 눌러도 이전 페이지로 돌아가지 않아요. 기존의 기록(history)을 새로운 페이지로 덮어쓰는 방식이라고 생각하면 이해가 쉬워요.
추가로 알려드리는 팁!
-
prefetch: Next.js의
Link
컴포넌트에는 자동으로 링크된 페이지를 미리 불러오는prefetch
기능이 기본으로 켜져 있어요. 이는 페이지 전환 속도를 크게 올려주지만, 네트워크 비용이 부담될 수 있는 점 참고하세요. -
passHref:
Link
가 자식 컴포넌트로 감싸고 있는 태그가<a>
가 아닌 다른 컴포넌트일 때,passHref
속성을 사용하면href
가 자식 컴포넌트에게 제대로 전달돼요.
이렇게 Next.js의 Link
컴포넌트를 활용하면 SPA(싱글 페이지 어플리케이션)처럼 빠르고 부드러운 화면 전환을 손쉽게 만들 수 있으니 꼭 익혀두시면 좋아요!
Next.js의 next/link
에서 자주 쓰이는 replace
와 scroll
속성에 대해 쉽게 설명해볼게요.
replace 속성
기본값은 false
입니다.
replace
를 true
로 설정하면, 새로운 URL을 브라우저의 히스토리에 추가하는 대신 현재 히스토리 상태를 대체해버려요. 쉽게 말해, 뒤로 가기 버튼을 눌렀을 때 이전 페이지가 아니라 그 전 페이지로 바로 가도록 하는 거죠.
예시 코드를 보면,
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" replace>
Dashboard
</Link>
)
}
'Dashboard' 링크를 클릭했을 때 새로운 히스토리를 쌓는 게 아니라 기존 상태를 바꾸기에, 브라우저 히스토리가 깔끔해져요. 이 기능은 로그인 리디렉션 같은 상황에서 유용하게 쓰입니다.
scroll 속성
기본값은 true
예요.
Link로 페이지를 이동할 때 스크롤 위치를 어떻게 처리할지 결정해주는데요,
scroll={true}
면, 백/포워드 내비게이션처럼 브라우저가 스크롤 위치를 유지해줘요.- 물론, 새로운 페이지가 보기에 없거나 새롭게 랜더링되면 자동으로 페이지 맨 위로 스크롤이 이동합니다.
만약 페이지 전환할 때마다 항상 맨 위로 스크롤시키고 싶다면(특별한 경우), scroll
을 true
로 둬야 하고, 특정 스크롤 위치 유지를 원한다면 false
로 설정하세요.
추가 팁!
- 히스토리 관리가 복잡한 싱글 페이지 앱(SPA)에서
replace
옵션을 적절히 활용하면 사용자 경험이 더 자연스러워집니다. - 스크롤 조작은 모바일에서 특히 UX에 큰 영향을 줄 수 있으니 필요에 따라 세밀하게 조절하는 걸 추천해요.
간단하지만 꼭 알아둬야 하는 Next.js의 핵심 속성들이니까, 실제 프로젝트에서 한번씩 설정해보면서 감을 잡아보세요!
Next.js에서 scroll = false
로 설정하면, 첫 번째 페이지 요소로 자동 스크롤을 시도하지 않는다는 점 기억해두면 좋아요.
팁: Next.js는 네비게이션 시
scroll: false
인지 먼저 확인하고 스크롤 동작을 제어하는데요. 만약 스크롤이 활성화되어 있다면, DOM에서 관련 요소를 찾아내어 최상위 요소들을 하나씩 체크합니다. 여기서 스크롤이 불가능하거나 HTML이 렌더링되지 않은 요소들(예를 들어 sticky나 fixed 위치 요소, getBoundingClientRect로 계산된 화면에 보이지 않는 요소)은 건너뛰고요. 그런 다음 형제 요소들을 계속 탐색하면서 화면(viewport)에 보이는 스크롤 가능한 요소를 찾아 스크롤을 수행합니다.
아래 코드는 Link
컴포넌트 사용 예시입니다. scroll={false}
속성을 줘서 해당 링크 클릭 시 스크롤 이동이 일어나지 않도록 설정하고 있어요:
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
)
}
prefetch 속성에 대해 잠깐
Next.js의 Link
컴포넌트엔 prefetch
라는 속성도 있는데요, 이게 켜져 있으면 해당 링크에 마우스를 올리거나 화면에 보일 때 미리 페이지 리소스를 다운로드해서 빠르게 이동할 수 있게 해줍니다. 기본값은 보통 true
이지만, 특정 상황에서는 성능 최적화를 위한 선택적 비활성화가 가능해요.
예를 들어, 사용자가 자주 클릭하지 않는 링크에 대해서는 prefetch={false}
를 줘서 불필요한 데이터 로드를 막을 수 있습니다.
<Link href="/some-page" prefetch={false}>
Some Page
</Link>
이렇게 Next.js에서 스크롤과 프리페치 설정을 적절히 활용하면 사용자 경험을 더 풍부하고 빠르게 만들어줄 수 있습니다!
Next.js의 Link
컴포넌트에서 프리페칭(prefetching) 기능은 사용자가 링크를 화면에서 보게 될 때(초기 로드 시 혹은 스크롤로 인해) 자동으로 해당 경로와 데이터를 미리 불러오는 기능이에요. 이렇게 하면 사용자가 실제로 링크를 클릭했을 때 훨씬 빠르게 페이지가 열려서, 좀 더 부드럽고 빠른 사용자 경험을 제공할 수 있죠.
프리페칭 작동 방식
- 사용자가 링크 컴포넌트를 화면에 보게 되면 Next.js가 백그라운드에서 해당 경로와 데이터를 미리 요청해요.
- 만약 미리 받아온 데이터가 시간이 지나 만료되었다면, 사용자가 링크에 마우스를 올릴 때 다시 한 번 프리페칭을 시도합니다.
- 중요한 점! 이 기능은 프로덕션 환경에서만 활성화돼요. 개발 모드에서는 자동으로 프리페칭하지 않습니다.
prefetch 속성에 들어갈 수 있는 값과 의미
prop 값 | 설명 |
---|---|
null (기본값) | 라우트가 정적(static)인 경우 전체 경로와 데이터를 미리 가져와요. 동적(dynamic)인 경우 가장 가까운 loading.js 경계(segment)까지만 부분적으로 프리페치합니다. |
true | 정적, 동적 상관없이 전체 경로와 데이터를 전부 미리 불러와요. |
false | 뷰포트 진입 시나 마우스 오버 시에도 프리페칭이 절대 일어나지 않아요. |
실제 예시 코드
import Link from 'next/link'
export default function Page() {
return (
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
)
}
위 예에서는 /dashboard
경로의 프리페칭을 끈 상태예요. 이렇게 하면 페이지가 무거운 경우, 불필요한 데이터 요청을 방지할 수 있죠.
추가 팁!
- 프리페칭을 적절히 활용하면 사용자가 링크를 클릭했을 때 대기 시간을 크게 줄일 수 있어요. 하지만 너무 많은 페이지를 미리 불러오면 네트워크 비용과 메모리 사용량이 증가할 수 있으니 꼭 필요한 페이지에만 사용하는 걸 추천합니다.
- 만약 여러분이 동적 라우팅을 많이 쓴다면,
loading.js
같은 경계점을 잘 활용해서 불필요한 데이터가 미리 불러와지는 걸 막을 수 있어요. - 그리고 React의
Suspense
와 잘 활용하면 더욱 유연한 로딩 경험을 제공할 수 있으니 관심이 있다면 같이 공부해보면 좋아요!
프리페칭을 이해하고 적절하게 적용하면 더 빠르고 쾌적한 Next.js 앱을 만들 수 있으니, 꼭 활용해보세요!
onNavigate 이벤트 핸들러
Next.js에서 client-side navigation, 즉 페이지가 새로고침 없이 이동할 때 실행되는 이벤트 핸들러가 있는데요, 바로 onNavigate
입니다. 이 핸들러는 이벤트 객체를 받고, 이 객체 안에는 preventDefault()
메서드도 있어서 필요하다면 이동을 취소할 수도 있어요.
import Link from 'next/link'
export default function Page() {
return (
<Link
href="/dashboard"
onNavigate={(e) => {
// SPA 내비게이션 중에만 실행돼요.
console.log('Navigating...')
// 필요하면 이걸 호출해서 내비게이션을 막을 수 있어요.
// e.preventDefault()
}}
>
Dashboard
</Link>
)
}
알아두면 좋은 점:
onClick
과onNavigate
는 비슷해 보일 수 있지만, 용도가 좀 달라요.
onClick
은 모든 클릭 이벤트에 대해 실행돼요.onNavigate
는 오직 SPA 내비게이션(클라이언트 사이드 라우팅) 때만 실행됩니다.몇 가지 차이점 정리해 보면:
- Ctrl/Cmd + 클릭(새 탭 열기) 시에는
onClick
은 실행되지만,onNavigate
는 실행되지 않아요. (Next.js가 기본적으로 새 탭에서 내비게이션을 막거든요)- 외부 URL로 이동할 때는
onNavigate
가 작동하지 않아요. (클라이언트 사이드, 동일 출처 내비게이션 전용이라서요)download
속성이 있는 링크는onClick
이벤트는 발생하지만, 브라우저가 직접 파일 다운로드를 처리해서onNavigate
는 실행되지 않아요.
추가로 알려드릴 게 있어요!
onNavigate
는 next/link
컴포넌트를 통해 내부 페이지 전환을 좀 더 세밀하게 제어하거나 로깅할 때 아주 유용합니다. 예를 들어, 사용자에게 경고 알림을 띄우거나, 아직 저장하지 않은 작업이 있을 때 확인 메시지를 띄워서 실수로 페이지를 벗어나지 않게 할 수도 있죠. 이럴 때 e.preventDefault()
가 아주 중요한 역할을 합니다.
반면에, 외부 링크나 새 탭 열기 등에는 작동하지 않기 때문에, 전체 클릭 이벤트를 감지하려면 onClick
도 함께 활용하는 게 좋습니다. 두 이벤트를 적절히 구분해서 쓰면 사용자 경험을 한층 더 세심하게 만들 수 있답니다.
그 밖에 SPA 내비게이션이 제대로 감지되는지 꼭 테스트도 해보세요! Next.js는 자주 업데이트되니 공식 문서나 최신 릴리즈 노트도 틈틈이 체크하는 걸 추천해요.
예제들
아래 예제들은 Link
컴포넌트를 다양한 상황에서 어떻게 사용하는지 보여줘요.
동적 세그먼트에 링크 걸기
동적 세그먼트에 링크를 걸 때는 템플릿 리터럴과 변수 삽입을 활용해서 링크 목록을 쉽게 만들 수 있어요. 예를 들어, 블로그 게시물 목록을 만들 때 이렇게 할 수 있죠:
Next.js에서 포스트 리스트를 보여주는 코드와, 현재 경로에 따라 링크에 'active' 클래스를 추가하는 방법에 대해 살펴볼게요.
포스트 리스트 컴포넌트
import Link from 'next/link'
interface Post {
id: number
title: string
slug: string
}
export default function PostList({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
이 코드는 간단하게 posts
배열을 받아서, 각 포스트에 맞는 링크를 생성해줍니다. slug
를 이용해 동적인 URL(/blog/${post.slug}
)을 만들고 있죠.
활성화된(Active) 링크 확인하기
사용자가 어느 페이지에 있는지 알 수 있게 하려면, 해당 링크에 'active' 클래스를 추가하는 게 일반적이에요. Next.js 13 버전부터는 usePathname()
훅을 이용해 현재 경로 정보를 쉽게 가져올 수 있습니다.
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}
pathname
이 현재 URL 경로를 반환해요.- 이걸 비교해서 조건부로 클래스 이름에 'active'를 붙여줍니다.
- 이렇게 하면 CSS에서
.active
에 특별한 스타일을 줘서 현재 위치를 한눈에 알 수 있어요.
내가 추가로 알려주고 싶은 팁!
-
부분 일치도 활용하기:
예를 들어/blog/abc
같은 세부 포스트 경로에 있을 때/blog
메뉴를 활성화하고 싶다면 엄격히pathname === '/blog'
로 비교하는 대신pathname.startsWith('/blog')
로 체크하면 좋아요. -
스타일링은 어떻게?
예를 들어 CSS 모듈을 쓰고 있다면, 클래스들 연결을clsx
라이브러리를 활용해 조금 더 깔끔하게 관리할 수도 있어요. -
Link 컴포넌트 감싸기
가끔 액티브 상태에 따라 아이콘을 바꾸거나 조작할 때<Link>
를 별도의 커스텀 컴포넌트로 만들어 사용하는 방법도 있어요.
정리하자면
내용 | 설명 |
---|---|
usePathname() | 현재 경로 문자열을 가져오는 훅 |
Link 컴포넌트 | 페이지 이동용 Next.js 기본 링크 컴포넌트 |
active 클래스 추가 | 현재 경로와 링크 경로를 비교해 클래스명 추가 |
이렇게 하면 내비게이션 메뉴에서 사용자가 어느 페이지에 있는지 쉽게 시각적으로 알려줄 수 있죠! Next.js로 개발할 때 유용하게 활용해보세요. 😊
특정 id로 스크롤하기
웹사이트에서 네비게이션할 때 특정 id로 바로 스크롤하고 싶다면, URL 뒤에 #
해시 링크를 붙이거나 href
속성에 해시 링크를 전달하면 돼요. Next.js의 Link
컴포넌트가 결국 <a>
태그로 변환되기 때문에 이 방법이 가능합니다.
<Link href="/dashboard#settings">Settings</Link>
// 실제 렌더링 결과
<a href="/dashboard#settings">Settings</a>
알아두면 좋은 점: Next.js에서는 네비게이션 후에 해당 페이지가 화면에 보이지 않으면 자동으로 그 영역으로 스크롤해줘요. 그래서 별도의 스크립트 없이도 원하는 id로 깔끔하게 이동할 수 있답니다.
참고로, 만약 페이지 내에서 부드러운 스크롤 효과를 원한다면 CSS scroll-behavior: smooth;
를 html
이나 body
태그에 적용해주는 것도 좋은 방법이에요. 그러면 사용자 경험이 더 좋아져요!
동적 라우트 세그먼트에 링크 걸기
동적 라우트 세그먼트를 사용할 때는 템플릿 리터럴(template literals)을 활용해서 링크 경로를 만들면 정말 편리해요!
예를 들어, Next.js에서 app/blog/[slug]/page.js
와 같은 동적 라우트가 있을 때, 여러 게시물(post) 목록을 받아 각 게시물별로 동적 경로에 맞춰 링크를 쉽게 만들 수 있답니다. 아래 예제를 보시면 감이 딱 올 거에요:
import Link from 'next/link'
export default function Page({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
posts
배열 안에는 게시물 데이터가 들어있고, 각 게시물마다 고유한slug
가 있죠.- 이
slug
를 템플릿 리터럴을 이용해 URL에 넣어줘서 동적인 링크를 만들어 주는 거예요.
Tip!
Next.js 13부터는 app
디렉토리가 도입되면서 라우팅 방식이 약간 바뀌었는데, 이 점 참고하시고 항상 최신 공식 문서를 확인하는 습관을 들이면 좋아요. 또한, Link
컴포넌트가 내부적으로 클라이언트 사이드 네비게이션을 해주기 때문에 페이지 이동이 더 부드럽고 빠르답니다!
간단하지만 실무에 꼭 필요한 꿀팁이니 꼭 활용해 보세요~!
Link의 자식이 a
태그를 감싸는 커스텀 컴포넌트라면?
Next.js에서 Link
컴포넌트의 자식이 그냥 a
태그가 아니라, a
태그를 감싸고 있는 커스텀 컴포넌트라면 반드시 passHref
속성을 Link
에 추가해줘야 해요. 특히 styled-components 같은 라이브러리를 사용할 때 이게 꼭 필요해요.
왜냐하면 passHref
를 안 넣으면 실제 렌더링 되는 a
태그에 href
속성이 빠져버리거든요. 그럼 접근성(Accessibility)이 떨어지고, 검색 엔진 최적화(SEO)에도 부정적인 영향이 생길 수 있어요.
만약 ESLint를 사용한다면 next/link-passhref
라는 규칙이 있어서 passHref
를 제대로 썼는지 체크해주니 참고하세요.
import Link from 'next/link'
import styled from 'styled-components'
// 이렇게 a 태그를 스타일링한 커스텀 컴포넌트를 만들었어요
const RedLink = styled.a`
color: red;
`
function NavLink({ href, name }) {
return (
<Link href={href} passHref legacyBehavior>
<RedLink>{name}</RedLink>
</Link>
)
}
export default NavLink
- 만약 emotion 라이브러리의 JSX 프래그마(
@jsx jsx
) 기능을 쓴다면, 직접a
태그를 사용해도 무조건passHref
를 넣어줘야 해요. - 그리고 커스텀 컴포넌트는
onClick
프로퍼티를 지원해야 링크 내비게이션이 제대로 작동합니다. 가령 클릭 이벤트를 잘 전달하는지 꼭 확인하세요.
추가로 말하자면, passHref
는 Next.js 12 이전 버전에서는 필수였는데, 최신 버전에서는 보통 자동으로 전달해주기도 해요. 그러나 여전히 커스텀 컴포넌트나 CSS-in-JS 라이브러리와 함께 쓸 때는 명시적으로 passHref
를 넣는 게 안전합니다. 실수로 빠뜨리면 디버깅할 때 엄청 헷갈릴 수 있으니 기억해두세요!
함수형 컴포넌트 안에 중첩된 Link 사용하기
Next.js에서 <Link>
컴포넌트의 자식이 함수형 컴포넌트일 때는, 단순히 passHref
와 legacyBehavior
를 사용하는 걸로 끝나지 않습니다. 이럴 경우에는 React.forwardRef
로 해당 컴포넌트를 감싸줘야 제대로 동작해요.
아래 예시 코드를 보면, MyButton
이라는 함수형 컴포넌트를 만들고, React.forwardRef
를 이용해서 ref를 전달하는 방법을 보여주고 있답니다:
import Link from 'next/link'
import React from 'react'
// MyButton의 props 타입 정의
interface MyButtonProps {
onClick?: React.MouseEventHandler<HTMLAnchorElement>
href?: string
}
// React.ForwardRefRenderFunction 타입을 사용해 ref 처리
const MyButton: React.ForwardRefRenderFunction<
HTMLAnchorElement,
MyButtonProps
> = ({ onClick, href }, ref) => {
return (
<a href={href} onClick={onClick} ref={ref}>
Click Me
</a>
)
}
// forwardRef로 컴포넌트 감싸기
const ForwardedMyButton = React.forwardRef(MyButton)
export default function Page() {
return (
<Link href="/about" passHref legacyBehavior>
<ForwardedMyButton />
</Link>
)
}
여기서 중요한 점은 Link
에 passHref
를 넣어줘야 자식 컴포넌트에 href
가 올바르게 전달되고, legacyBehavior
는 Next.js 13에서 <Link>
의 내부 동작 방식을 이전 버전과 호환하게 해줍니다. 그리고 ref를 전달하려면 반드시 React.forwardRef
로 감싸줘야 해요.
URL 변경 시, push 대신 replace 사용하기
Next.js의 라우터에서 페이지 이동할 때 기본적으로 router.push()
를 씁니다. 그런데 때에 따라 이동 기록을 남기고 싶지 않을 때도 있죠? 예를 들어 로그인 후 리다이렉트할 때 뒤로가기를 못 하게 만들고 싶을 때요.
그럴 땐 router.replace()
를 사용하세요! replace()
는 현재 기록을 새 URL로 대체하기 때문에, 사용자가 브라우저 뒤로가기로 이전 페이지에 접근할 수 없게 됩니다.
import { useRouter } from 'next/router'
function SomeComponent() {
const router = useRouter()
const handleRedirect = () => {
router.replace('/dashboard')
}
return <button onClick={handleRedirect}>Go to Dashboard</button>
}
실제로 많이 사용되는 편리한 기능이라 참고해두시면 웹 앱 내비게이션 관리할 때 훨씬 깔끔해질 거예요.
궁금한 점 있으면 언제든 물어봐주세요!
Next.js에서 Link 컴포넌트를 사용할 때 기본 동작은 새로운 URL을 히스토리 스택에 쌓는(push) 거예요. 하지만 이게 항상 원하는 결과는 아닐 수 있죠. 예를 들어, 페이지 이동할 때 새 기록을 남기고 싶지 않다면 replace라는 속성을 사용해줘야 해요. 이렇게요:
import Link from 'next/link'
export default function Page() {
return (
<Link href="/about" replace>
About us
</Link>
)
}
replace를 넣으면 히스토리에 새 항목이 추가되는 대신, 현재 기록이 대체되기 때문에 뒤로 가기 했을 때 이전 페이지가 바로 나타나는 효과를 낼 수 있어요.
페이지 이동 시 스크롤 위치 유지하기
Next.js Link의 기본 스크롤 동작은 '페이지 상단으로 자동 스크롤' 하는 게 아니라, 브라우저에서 뒤로 가기나 앞으로 가기 할 때처럼 현재 스크롤 위치를 그대로 유지하는 거예요. 즉, 새 페이지로 이동해도 사용자가 보던 위치가 그대로 유지된다는 뜻이죠.
이게 의외로 유용한 이유는 긴 페이지를 보고 있던 상황에서 페이지 전환 후에도 스크롤 위치가 유지되면 사용자는 자연스럽게 이어서 볼 수 있기 때문이에요. 물론 상단으로 강제로 스크롤하고 싶으면 별도의 조치를 취해야 한다는 점 참고하세요.
추가 팁!
만약 페이지가 바뀔 때마다 무조건 상단으로 스크롤 하고 싶다면, Next.js의 useRouter
훅을 사용해 router.events
의 routeChangeComplete
이벤트에 리스너를 걸어서 수동으로 스크롤을 조작할 수 있어요.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = () => {
window.scrollTo(0, 0)
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return <Component {...pageProps} />
}
이렇게 하면 페이지가 바뀔 때마다 스크롤이 맨 위로 이동해서 깔끔한 사용자 경험을 만들 수 있어요.
요약하자면,
기능 | 기본 동작 | 변경 방법 |
---|---|---|
히스토리에 새 URL 추가 여부 | 새 URL을 히스토리에 푸시(push) | replace prop 사용 |
페이지 전환 시 스크롤 위치 | 현재 위치 유지 | 직접 스크롤 위치 조작 필요 |
Next.js의 Link는 이렇게 유연하게 동작하니 상황에 따라 적절히 활용해 보세요!
Next.js에서 링크를 클릭할 때 페이지가 화면에 보이지 않으면 자동으로 첫 번째 페이지 요소의 맨 위로 스크롤이 이동하는 기본 동작이 있어요. 근데 이 스크롤 이동이 불편하거나 필요 없을 때가 있죠? 그럴 때는 Link
컴포넌트에 scroll={false}
를 넣거나, router.push()
또는 router.replace()
를 사용할 때 옵션으로 { scroll: false }
를 넘겨주면 이 자동 스크롤을 막을 수 있어요.
예를 들어, 아래는 Link
컴포넌트에서 스크롤 이동을 막는 방법입니다:
import Link from 'next/link'
export default function Page() {
return (
<Link href="/#hashid" scroll={false}>
Disables scrolling to the top
</Link>
)
}
router.push()
나 router.replace()
를 사용할 때는 이렇게 하면 됩니다:
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
여기서 한 가지 팁을 더 드리자면, scroll={false}
옵션을 사용하면 URL 이동은 되지만 페이지가 바뀌면서 자동으로 스크롤이 되지 않기 때문에 해시 링크(#hashid
)나 특정 위치로 부드럽게 스크롤하려면 직접 스크롤을 제어하는 로직을 추가해줘야 해요. 예를 들어, useEffect
와 window.scrollTo
를 이용해서 원하는 위치로 스크롤을 움직이는 방식이죠.
또한, 이런 스크롤 제어 옵션은 SPA 내에서 페이지 이동 시 유저 경험을 다양하게 조절할 수 있어서 상황에 맞게 쓰면 훨씬 깔끔한 UX를 만들 수 있으니 기억해두세요!
Middleware에서 링크 프리페칭(Prefetching) 하기
Middleware를 쓸 때, 보통 인증 처리나 특정 조건에 따라 사용자를 다른 페이지로 리다이렉트하는 작업을 많이 하죠. 그런데 Next.js에서 Link
컴포넌트를 사용할 때 Middleware에 의해 경로가 리라이트(rewrite) 되는 경우, 그냥 href
만 넣으면 프리페칭이 제대로 되지 않을 수 있어요.
그래서 Next.js한테 "이 링크는 사용자에게는 /
경로로 보여주지만, 실제 프리페칭을 할 땐 /auth/dashboard
나 /public/dashboard
로 미리 자료를 가져와" 라고 알려줘야 합니다. 이러면 Middleware에서 불필요하게 fetch를 여러 번 해서 정체를 파악하는 일을 줄일 수 있어요. (사실 미리 경로까지 알려주는 꼼수랄까요?)
예를 들어, 인증된 사용자와 비인증 방문자를 각각 다르게 보여주는 /dashboard
라우트를 셋업했다고 하면, Middleware를 이렇게 작성합니다:
import { NextResponse } from 'next/server'
export function middleware(request: Request) {
const nextUrl = request.nextUrl
if (nextUrl.pathname === '/dashboard') {
if (request.cookies.authToken) {
// 인증된 사용자라면 /auth/dashboard로 리라이트
return NextResponse.rewrite(new URL('/auth/dashboard', request.url))
} else {
// 비인증 방문자는 /public/dashboard로 리라이트
return NextResponse.rewrite(new URL('/public/dashboard', request.url))
}
}
}
좀 더 알면 좋은 팁!
-
NextResponse.rewrite()
는 사용자가 요청한 URL을 실제 서버측 처리 경로로 바꿔줘요. 즉, 사용자는/dashboard
를 보지만 내부적으로는/auth/dashboard
또는/public/dashboard
를 보여주게 되는 거죠. -
이렇게 하면 SEO에도 도움이 됩니다. 클라이언트 측에서 보는 URL은 깔끔하면서도, 내부에서는 사용자 권한에 맞는 콘텐츠를 노출할 수 있어요.
-
프리페칭을 제대로 하려면 Next.js
Link
컴포넌트에서href
와as
(또는prefetch
관련 옵션)를 적절히 써야 하는데, Next.js 13 이상에서 미들웨어 기반 리라이트가 지원되는 환경이라면 이 방법이 특히 효과적입니다. -
만약 레거시 라우팅 시스템을 쓰고 있거나, 미들웨어가 복잡한 경우라면 프리페치가 꼬일 수 있으니 이 점도 참고하세요.
마무리
Middleware는 아주 강력한 도구지만, 링크 프리페칭 같은 세밀한 부분까지 챙겨야 빠르고 깔끔한 사용자 경험을 제공할 수 있습니다. 사용자가 화면 전환 시 빠른 로딩을 느끼도록, 미들웨어 리라이트를 제대로 이해하고 Next.js에 알려주는 게 중요해요!
이번에 Link
컴포넌트에서 경로를 조건에 따라 다르게 연결하고 싶을 때, 다음과 같이 작성해주면 됩니다:
'use client'
import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // 인증 상태를 확인하는 커스텀 훅
export default function Page() {
const isAuthed = useIsAuthed()
// 인증 상태에 따라서 이동할 경로를 바꿔줘요
const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
return (
<Link as="/dashboard" href={path}>
Dashboard
</Link>
)
}
여기서 useIsAuthed
는 내가 직접 만든 인증 관련 커스텀 훅이고, 로그인 상태인지 아닌지에 따라 /auth/dashboard
혹은 /public/dashboard
로 연결해주고 있어요. 그리고 as
속성은 실제 URL에 표시될 경로를 정해주는 역할을 하니, /dashboard
로 깔끔하게 보이도록 한 거죠.
네비게이션 차단하기 (Blocking navigation)
사용자가 폼을 작성 중인데 저장하지 않고 페이지를 벗어나려고 할 때, 네비게이션을 막아서 "변경사항이 사라질 수 있어요!"라고 경고하고 싶을 때가 있죠? 이런 경우엔 onNavigate
라는 prop을 활용하면 돼요. 조건에 따라서 이동을 차단할 수 있도록 구현할 수 있습니다.
근데 앱 규모가 커지면 이런 차단 로직을 여러 컴포넌트에서 일관되게 관리해야 하잖아요? 그럴 때 React Context를 사용하면 앱 전체에 차단 상태를 공유하고 관리하기가 편해집니다.
먼저, 네비게이션 차단 상태를 관리할 Context를 만들어볼게요:
import { createContext, useState, useContext } from 'react'
const NavigationBlockContext = createContext({
isBlocked: false,
setIsBlocked: () => {},
})
// 차단 상태를 전역에서 관리할 Provider 컴포넌트
export function NavigationBlockProvider({ children }) {
const [isBlocked, setIsBlocked] = useState(false)
return (
<NavigationBlockContext.Provider value={{ isBlocked, setIsBlocked }}>
{children}
</NavigationBlockContext.Provider>
)
}
// Context를 쉽게 사용하는 커스텀 훅
export function useNavigationBlock() {
return useContext(NavigationBlockContext)
}
이렇게 만들어두면, 폼이나 링크 컴포넌트 어디서든 useNavigationBlock
을 불러와서 네비게이션 차단 여부를 읽거나 설정할 수 있어요.
예를 들어, 폼에서는 사용자가 작성 중일 때 setIsBlocked(true)
로 네비게이션을 막고, 저장하거나 취소하면 setIsBlocked(false)
로 해제해주면 됩니다.
추가 팁!
- 네이티브
beforeunload
이벤트와 연동해서 사용자가 탭을 닫거나 새로고침하려고 할 때도 경고를 띄우는 걸 같이 구현할 수 있어요. onNavigate
콜백에서 사용자가 정말로 이동할지 확인하는 모달을 만들어 보여주면 UX가 훨씬 좋아집니다.- Next.js 13 기준이라면,
Link
컴포넌트가 기본적으로 클라이언트 컴포넌트이므로use client
선언을 잊지 마세요!
필요하다면 네비게이션 차단과 관련된 샘플 코드도 더 공유할게요. 도움이 되었길 바랍니다!
작성해주신 코드는 React context를 활용해서 페이지 이동(네비게이션)을 막을 수 있는 간단한 구조를 만들려는 시도입니다. 폼에서 데이터가 변경되면 isBlocked
상태를 true로 바꿔서 이동을 막고, 저장하면 false로 다시 풀어주는 형태입니다.
근데 조금 수정이 필요한 부분이 있어서, 전체적으로 사용법과 함께 설명을 드리고, 마지막에 네비게이션을 막는 커스텀 Link 컴포넌트까지 만들어볼게요!
1. NavigationBlockerContext 고쳐보기
'use client'
import { createContext, useState, useContext, ReactNode } from 'react'
interface NavigationBlockerContextType {
isBlocked: boolean
setIsBlocked: (isBlocked: boolean) => void
}
export const NavigationBlockerContext = createContext<NavigationBlockerContextType>({
isBlocked: false,
setIsBlocked: () => {},
})
export function NavigationBlockerProvider({ children }: { children: ReactNode }) {
const [isBlocked, setIsBlocked] = useState(false)
return (
<NavigationBlockerContext.Provider value={{ isBlocked, setIsBlocked }}>
{children}
</NavigationBlockerContext.Provider>
)
}
export function useNavigationBlocker() {
return useContext(NavigationBlockerContext)
}
여기서 수정한 점
value={ isBlocked, setIsBlocked }
→ 이건 객체 형태여야 해서{ isBlocked, setIsBlocked }
로 바꿔줘야 해요.children
타입에 ReactNode를 붙여줬어요.
2. Form 컴포넌트에서 이동 방지 상태 변경하기
'use client'
import { useNavigationBlocker } from '../contexts/navigation-blocker'
export default function Form() {
const { setIsBlocked } = useNavigationBlocker()
return (
<form
onSubmit={(e) => {
e.preventDefault()
setIsBlocked(false) // 저장하면 이동 가능
}}
onChange={() => setIsBlocked(true)} // 변경되면 이동 막음
>
<input type="text" name="name" />
<button type="submit">Save</button>
</form>
)
}
여기서도 onSubmit
이벤트 함수 닫는 괄호가 빠졌네요. 수정해주었습니다.
3. 네비게이션을 막는 커스텀 Link 컴포넌트 만들기
일단 React에서 페이지 이동을 제어할 때 보통 react-router
나 next/router
를 씁니다.
이 예시에서는 Next.js 13 또는 React Router 같은 걸 생각하면서 기본적인 커스텀 Link를 만들어볼게요.
'use client'
import Link, { LinkProps } from 'next/link' // next/link 쓰는 경우
import { useNavigationBlocker } from '../contexts/navigation-blocker'
import { useRouter } from 'next/navigation'
import { MouseEvent } from 'react'
interface NavigationBlockerLinkProps extends LinkProps {
children: React.ReactNode
}
export default function NavigationBlockerLink({ children, href, ...props }: NavigationBlockerLinkProps) {
const { isBlocked } = useNavigationBlocker()
const router = useRouter()
const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {
if (isBlocked) {
e.preventDefault()
if (confirm('변경사항이 저장되지 않았습니다. 페이지를 이동하시겠습니까?')) {
router.push(href.toString())
}
}
}
return (
<Link href={href} {...props} onClick={handleClick}>
{children}
</Link>
)
}
주요 포인트
isBlocked
상태가 true면 이동 전에 confirm 창을 띄워 확인받아요.- 확인하면
router.push
로 직접 이동 처리를 해줍니다. next/link
의 경우는onClick
이벤트를 지원하므로 이렇게 커스텀이 가능합니다.
4. 마무리: 페이지 이동 제한 기능 이해하기
- 이 패턴은 보통 사용자가 입력 중인 폼에서 확인 없이 떠나는 걸 막고자 할 때 유용해요.
- 브라우저 기본 경고성 팝업(예:
beforeunload
이벤트)을 쓰는 방법도 있지만 UX 제어에 한계가 있어요. - 이렇게 컨텍스트와 커스텀 Link로 제어하면 리액트단에서 한결 편하고 깔끔하게 구현됩니다.
- 참고로 브라우저 새로고침, 닫기 등의 동작까지 막으려면
window.addEventListener('beforeunload', ...)
를 추가하는 것도 고려해보세요!
참고용 전체 코드
파일명 | 주요 내용 |
---|---|
contexts/navigation-blocker.tsx | NavigationBlockerContext와 Provider 정의 |
components/Form.tsx | 변경 시 이동 막고, 저장 시 이동 허용하는 폼 |
components/NavigationBlockerLink.tsx | 이동 전에 확인창 띄우는 커스텀 Link |
필요하면 제가 beforeunload
이벤트 활용법도 추가 작성해드릴게요. 언제든 질문 주세요!
이제 이 패턴으로 더 깔끔하게 사용자 이동 제한 기능을 구현해보세요~!
자, 이번에는 Next.js 프로젝트에서 "저장하지 않은 변경사항이 있을 때 페이지 이동을 막는" 커스텀 링크 컴포넌트를 만들어보는 방법을 다뤄볼게요. 사용자 경험을 고려할 때, 이런 기능은 정말 유용하답니다!
1. CustomLink
컴포넌트 만들기
'use client'
import Link from 'next/link'
import { useNavigationBlocker } from '../contexts/navigation-blocker'
interface CustomLinkProps extends React.ComponentProps<typeof Link> {
children: React.ReactNode
}
export function CustomLink({ children, ...props }: CustomLinkProps) {
const { isBlocked } = useNavigationBlocker()
return (
<Link
onNavigate={(e) => {
if (
isBlocked &&
!window.confirm('You have unsaved changes. Leave anyway?')
) {
e.preventDefault()
}
}}
{...props}
>
{children}
</Link>
)
}
여기서
useNavigationBlocker
는 사용자 정의 훅으로, 저장되지 않은 변경사항이 있는지를 상태로 관리해주는 역할을 해요. 상태가true
라면 이동 직전에 확인창을 띄워서 사용자가 실수로 페이지를 벗어나지 않도록 막아줍니다.
2. Nav
컴포넌트 구현하기
'use client'
import { CustomLink as Link } from './custom-link'
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
</nav>
)
}
Nav
컴포넌트는 위에서 만든CustomLink
를 이용해서 메뉴를 구성한 예시입니다.
3. 루트 레이아웃에 NavigationBlockerProvider
사용하기
아직 NavigationBlockerProvider
코드는 주어지지 않았지만, 대략 아래와 같은 형태로 감싸주면 됩니다:
'use client'
import { NavigationBlockerProvider } from '../contexts/navigation-blocker'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<NavigationBlockerProvider>
{children}
</NavigationBlockerProvider>
)
}
이렇게 하면 앱 전체에 저장되지 않은 변경사항 감지 상태가 공유되고, CustomLink
가 이를 활용해 네비게이션 차단기능을 수행할 수 있게 됩니다.
간단히 정리
구성 요소 | 역할 |
---|---|
NavigationBlockerProvider | 저장되지 않은 변경사항 감지 상태를 Context API로 제공 |
useNavigationBlocker | Context에서 isBlocked 값과 상태 제어 함수 훅으로 가져오기 |
CustomLink | Link 컴포넌트를 확장하여 네비게이션 전에 확인창 표시 |
Nav | CustomLink 를 이용한 네비게이션 UI |
추가 팁
- 사용자 경험 관점에서, 페이지 떠나기 전 확인창은 너무 잦으면 귀찮아질 수 있어요. 변경사항을 꼼꼼히 체크하는 로직과 적절한 상태 초기화가 중요합니다.
- React의
beforeunload
이벤트와 연동하여 브라우저 탭 닫기나 새로고침도 막아줄 수 있어요. 필요하다면 추가 구현해보세요. - Next.js의
Link
컴포넌트가 아직 네비게이션 인터셉트 기능을 완전 지원하지 않는 경우,router.events
활용하는 방법도 고려해볼 수 있습니다.
이제 여러분도 저장하지 않은 내용 때문에 당황하지 않는 친절한 웹 앱을 만들어 보세요! 필요하면 useNavigationBlocker
와 NavigationBlockerProvider
구현 방법도 알려드릴게요~
이번에는 Next.js 같은 React 프로젝트에서 사용자 경험을 업그레이드하는 ‘네비게이션 차단’ 기능을 만들어보는 얘기를 해볼게요.
먼저, NavigationBlockerProvider
라는 컨텍스트 프로바이더를 RootLayout
에 감싸서 앱 전체에서 네비게이션을 관리할 수 있게 했어요. 이렇게 하면 어느 페이지나 컴포넌트에서 ‘아직 저장 안 한 변경사항이 있는데 정말 나가도 되냐’는 확인창을 띄울 수 있죠.
import { NavigationBlockerProvider } from './contexts/navigation-blocker';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<NavigationBlockerProvider>{children}</NavigationBlockerProvider>
</body>
</html>
);
}
그리고 실제 페이지에서는 Nav
컴포넌트(아마 내비게이션 바)와 Form
컴포넌트를 함께 사용했네요.
import Nav from './components/nav';
import Form from './components/form';
export default function Page() {
return (
<div>
<Nav />
<main>
<h1>Welcome to the Dashboard</h1>
<Form />
</main>
</div>
);
}
이 상태에서 만약 폼에 수정 사항이 있는데 CustomLink
같은 커스텀 내비게이션 링크를 통해 페이지를 옮기려고 하면, ‘정말 페이지를 떠나도 되나요?’ 하는 확인창이 뜰 거예요. 덕분에 사용자는 실수로 작업하던 내용을 날리는 일이 줄어들겠죠?
조금 더 이야기해볼게요
- 사실 이 기능을 구현하려면
NavigationBlockerProvider
내부에 React Router의 Prompt 개념이나, Next.js 13의useRouter
등 네비게이션 훅을 잘 활용해야 해요. - 브라우저 탭 닫기나 새로고침 시 경고를 띄우는
beforeunload
이벤트 리스너도 함께 등록하면 실수 방지에 더 효과적이에요. - 또한,
CustomLink
컴포넌트는 기본<Link>
대신 네비게이션 차단 로직을 포함해야 하므로, 클릭 시e.preventDefault()
로 네비게이션을 잠깐 막은 뒤 사용자 확인을 체크하는 구조로 만드는 게 보통이에요.
이런 부분들은 추후에 한번 더 깊게 다뤄볼게요. 혹시 관심 있다면 직접 만들어보고 궁금한 점 질문해주세요!
버전 히스토리
버전 | 변경 사항 |
---|---|
v15.3.0 | onNavigate API 추가 |
v13.0.0 | 더 이상 자식 <a> 태그가 필요하지 않습니다. 코드베이스를 자동으로 업데이트 해주는 codemod 도 제공됩니다. |
v10.0.0 | 동적 라우트를 가리키는 href 속성이 자동으로 처리되어 as 속성이 더 이상 필요하지 않습니다. |
v8.0.0 | 사전 페치(prefetch) 성능 향상 |
v1.0.0 | next/link 컴포넌트 도입 |
이 버전 히스토리를 보면서 Next.js의 next/link
컴포넌트가 얼마나 발전해왔는지 한눈에 알 수 있는데요, 특히 v13 버전부터는 <a>
태그를 직접 감싸지 않아도 된다는 점이 굉장히 편리해졌어요. 덕분에 JSX가 훨씬 깔끔해지고, 링크 관리가 좀 더 쉬워졌답니다.
또한, v10에서 동적 라우팅이 더 자연스러워진 것도 주목할 만한 변화예요. 예전에는 동적 경로를 사용할 때 as
같은 뭔가 추가적인 속성을 계속 신경 써야 했는데, 이제는 href
만 잘 넘기면 알아서 처리해주니 개발자의 실수를 줄여주죠.
마지막으로 onNavigate
API 같은 새로운 기능도 추가되고 있으니, 다음 프로젝트에 적용해보시면 한층 더 향상된 사용자 경험을 제공할 수 있을 거예요! 😊
다음에도 업데이트나 새로운 팁 있으면 또 공유할게요!