최근 프로젝트에서는 Next.js의 App Router를 기반으로 웹사이트를 개발했습니다.
이 사이트는 다음과 같은 특징을 가지고 있었습니다:
- 모든 페이지가 로그인 필수
- 모든 경로가 dynamic route (export const dynamic ='force-dynamic')
초기에는 prefetch에 대해 깊게 고려하지 않고, 기본 <Link>만 사용했습니다.
하지만 시간이 지날수록 성능 이슈를 체감하게 되었고, prefetch 전략을 개선하여 사이트를 훨씬 최적화할 수 있었습니다.
오늘은 그 경험을 정리해보려고 합니다.
프로젝트의 초기 상황
Next.js App Router는 <Link>를 사용할 때 기본적으로 prefetch를 수행합니다.
이 때 prefetch는 별도의 API 호출을 발생시키는 것이 아니라, Server Component의 결과물(Flight Response) 을 미리 받아오는 형태입니다.
Network 패널에서 보면 /project/[id], /approval/[id] 같은 dynamic route로 Server Component를 다운로드 받는 요청만 발생하고 별도 fetch API 요청이나 데이터 호출은 발생하지 않았습니다.
또한, 브라우저의 Network Connection 관점에서도 SSE나 websocket 같은 지속적인 연결은 관찰되지 않았습니다.
요약하면,
👉 prefetch가 별도 API 트래픽을 증가시키진 않았지만,
👉 dynamic route가 많은 환경에서는 불필요한 Server Component 다운로드로 네트워크 비용이 커지고 있었습니다.
무슨 문제가 있었을까?
사이트의 특성상:
하나의 페이지에 수십~수백 개의 dynamic 링크가 등장하는 경우가 많았고, 각 링크가 개별적으로 prefetch되면서 필요하지 않은 Server Component까지 대량으로 다운로드하는 상황이 발생했습니다.
결국 이런 문제가 나타났습니다:
- 페이지 이동을 하지 않아도 브라우저 네트워크에 부하
- 렌더링과 리소스 소모가 불필요하게 증가
- 실제로 이동하려는 순간보다 먼저 대량의 데이터를 받아 메모리 사용량이 증가
- 특히 모바일 네트워크 환경에서는 체감 속도가 급격히 느려짐
Prefetch 전략 수립
문제를 인식한 후, 다음과 같은 전략을 세웠습니다.
1. 기본 prefetch 끄기
우선, <Link> 컴포넌트에 기본 prefetch를 끄기 시작했습니다.
<Link href={`/project/${project.id}`} prefetch={false}>
{project.name}
</Link>
- 사용자가 직접 클릭하거나 명확한 액션을 취하기 전까지는 prefetch를 수행하지 않게 했습니다.
2. 명시적 prefetch 적용
필요한 경우에만 prefetch를 수동으로 트리거했습니다.
예를 들어 사용자가 해당 링크를 hover하거나 focus했을 때 prefetch를 실행합니다.
"use client";
import { useRouter } from '@shared/navigation';
export default function ProjectItem({ id, name }: { id: string, name: string }) {
const router = useRouter();
const handleMouseEnter = () => {
router.prefetch(`/project/${id}`);
};
return (
<Link href={`/project/${id}`} prefetch={false} onMouseEnter={handleMouseEnter}>
{name}
</Link>
);
}
이렇게 하면:
- 사용자가 실제로 접근할 가능성이 높은 링크만 prefetch
- 네트워크와 브라우저 메모리 사용을 최소화
- 결과적으로 성능이 훨씬 가벼워졌습니다.
적용 이후 결과
Prefetch 전략을 개선한 후, 다음과 같은 긍정적인 변화를 경험할 수 있었습니다.
항목 | 변경 전 | 변경 후 |
다운로드하는 데이터 양 | 많음 (불필요한 Server Component 다운로드) | 필요한 것만 최소 다운로드 |
네트워크 대역폭 사용량 | 높음 | 감소 |
페이지 렌더링 속도 | 느림 (특히 리스트 많은 화면) | 빨라짐 |
모바일 체감 속도 | 느림 | 개선 |
특히, dynamic route가 많은 환경에서는 prefetch 제어만으로 체감 속도가 개선되었습니다.
결론
Next.js App Router는 기본적으로 prefetch 기능을 제공합니다.
이는 사용자 경험을 빠르게 만드는 강력한 무기이지만,
dynamic route가 많고, 로그인 세션이 필요한 사이트에서는
prefetch를 무조건 사용하는 것이 오히려 네트워크, 메모리 부하를 키울 수 있습니다.
따라서:
- 언제 prefetch를 수행할지
- 언제 prefetch를 제한할지
명확한 전략을 세우는 것이 필수입니다.
'software engineering > frontend' 카테고리의 다른 글
Apollo Client Refetch 구조 개선하기: SSE 알림과 Error 핸들링까지 (0) | 2025.04.28 |
---|---|
퍼포먼스 최적화(Next.js App Router) (0) | 2025.04.28 |
Next.js에서 next/image를 사용하는 이유와 optimize 설정에 대한 고민 (0) | 2025.04.28 |
실시간 데이터 갱신을 위한 SSE(Server-Sent Events)와 WebWorker 활용기 (0) | 2025.04.28 |
JSON-Patch: 대규모 JSON 관리를 위한 효율적인 도구 (0) | 2025.01.22 |