해당 페이지에 아직 안들어갔는데 왜 prefetch가 되죠?[nextjs]

김철준·2024년 9월 30일
2

next.js

목록 보기
9/10

프로젝트 메인 페이지에 접속했을 때, 네트워크 탭을 확인해 보니 아직 방문하지 않은 여러 페이지에 대한 프리페치(prefetch) 요청이 발생하는 것을 발견했다. 강력 새로고침을 실행하고 캐시를 제거했음에도 불구하고 이러한 요청이 계속 발생했다.

이러한 프리페치 요청은 내가 아직 접속하지 않은 페이지들을 대상으로 이루어지고 있었다.

RSC(React Server Component

Next.js는 React의 서버 컴포넌트를 지원하기 때문에, 초기 페이지 렌더링 시 서버 컴포넌트를 서버에서 미리 렌더링하고, 그 결과를 클라이언트로 전달합니다. 이때 서버 컴포넌트가 클라이언트에 전달될 때, 해당 컴포넌트와 관련된 데이터를 가져오기 위해 _rsc라는 쿼리 매개변수를 사용하여 서버에서 데이터를 요청하는 것입니다.

이러한 요청은 주로 서버에서 렌더링된 컴포넌트의 데이터를 클라이언트로 전달하기 위한 메커니즘입니다. 각 페이지나 컴포넌트에 대해 요청이 발생하는 이유는, 서버에서 그 컴포넌트들이 어떤 데이터나 상태를 가지고 있는지 클라이언트에 알려주기 위해 필요한 데이터를 쿼리 형태로 요청하는 것입니다.

분석

자동 프리페칭(Prefetching)

Next.js는 성능 최적화를 위해 자동 프리페칭(prefetching) 기능을 사용합니다. 이 기능은 사용자가 특정 페이지를 클릭하기 전에 미리 데이터를 가져와서 페이지 이동 시 더 빠르게 로드될 수 있도록 합니다. 이 경우, Next.js가 특정 경로와 관련된 컴포넌트를 미리 불러오는 작업이 발생할 수 있습니다.

프리페칭(Prefetching)은 일반적으로 페이지 전환 속도를 개선하기 위한 기능이기 때문에, 사용자 입장에서는 긍정적인 성능 향상을 경험할 수 있습니다. 하지만 프리페칭도 상황에 따라 오버헤드가 발생할 수 있습니다. 특히 페이지가 많거나, 프리패칭할 데이터가 많을 경우에는 불필요한 네트워크 요청이 누적되어 초기 페이지 로드 속도가 느려질 수 있습니다.

나의 경우, 최상단 layout.tsx에서 header 컴포넌트를 포함하고 있다. 즉, 모든 페이지에서 헤더 컴포넌트가 나타나게 되는데 헤더 컴포넌트는 위 프리패칭된 페이지에 대한 Link들을 포함하고 있다!
layout.tsx와 Header 컴포넌트는 다음과 같이 생겼다.

layout.tsx

import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import Header from "@/app/_layout/header";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

export const metadata: Metadata = {
  title: "개발자들의 아지트, 코아",
  description: "퀴즈를 풀고, 코드 템플릿을 사용하며 함께 성장하세요.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
      <Header/>
      <main className={"w-full h-[calc(100vh-80px)] bg-background  flex justify-center items-center"}>
        {children}
      </main>
      </body>
    </html>
  );
}

header.tsx

"use client"

import React from 'react';
import Link from "next/link";
import {usePathname} from "next/navigation";
import PrimaryButton from "@/app/_components/button/primaryButton";

/**
 * 헤더
 */
const Header = () => {
    const pathname = usePathname();


    // 네비게이션 메뉴
    const NAVMENU = [
        {
            title: "퀴즈",
            link: "quiz",
        },
        {
            title: "코드 템플릿",
            link: "codeTemplate",
        },
        {
            title: "스터디",
            link: "study",
        },
    ]


    // 현재 페이지에 맞는 클래스명
    function getActiveClass(link:string){
        if(pathname===`/${link}`){
            return "bg-primary-normal text-white hover:bg-primary-dark "
        }
    }
    return (
        <header className={"w-full h-[80px] bg-headerBackground flex justify-between items-center px-container"}>
            {/*로고,메뉴*/}
            <div className={"flex gap-10 items-center"}>
                <Link href={"/"}>
                <h1 className={"text-primary-normal text-headline3 font-bold"}>코아</h1>
                </Link>
                <nav>
                    <ul className={"flex gap-3 text-title3Normal text-primary-normal"}>
                        {NAVMENU.map((item, index) => (
                            <li key={index} className={`cursor-pointer px-[12px] flex justify-center items-center w-[100px]  h-[32px] rounded-[8px] hover:bg-primary-dark hover:text-white ${getActiveClass(item.link)}`}>
                                <Link href={`/${item.link}`}>{item.title}</Link>
                            </li>
                        ))}
                    </ul>
                </nav>
            </div>
            {/* 로그인,회원가입 & 프로필 */}
            <div className={"flex gap-3 items-center"}>
                <Link href={"/login"}>
                    <PrimaryButton text={"로그인"} color={"primary"} />
                </Link>
                <Link href={"/signup"}>
                    <PrimaryButton text={"회원가입"} color={"primarySecondary"} />
                </Link>

            </div>
        </header>
    );
};

export default Header;

원인

Next.js의 기본 동작으로, Link 컴포넌트는 해당 경로로 이동하기 전에 페이지를 미리 불러오는 자동 프리페칭을 수행한다고 한다. 이로 인해 메인 페이지를 로드할 때 quiz, codeTemplate, study 등의 페이지에 대한 RSC 요청이 발생한 것이다.

해결방법으로는 Link 태그의 prefetch 속성값을 false로 지정하면 된다고 한다.
그렇다면 어떤 페이지들을 false로 지정해볼까?

어떤 페이지들을 prefetch false로 지정해볼까?

프리페칭이 성능에 미치는 영향

페이지가 적은 경우:

프리페칭은 주로 빠른 페이지 전환을 위해 데이터를 미리 가져오는 기능입니다.
페이지 수가 적고, 데이터 용량이 크지 않다면 프리페칭을 유지하는 것이 유리합니다.
사용자가 다른 페이지로 이동할 때, 프리페칭된 데이터를 사용해 즉각적으로 전환할 수 있기 때문입니다.

페이지가 많은 경우:

페이지 수가 많거나 각각의 페이지에서 불러오는 데이터가 많다면, 모든 페이지에 대한 프리페칭은 불필요하게 많은 리소스를 소비할 수 있습니다.

이런 경우, 모든 페이지에 대해 프리페칭을 유지할 경우 초기 페이지 로딩 시간이 길어질 수 있으며, 불필요한 네트워크 요청이 많아질 수 있습니다.

언제 prefetch={false}를 사용하는 것이 좋은가?

필수적이지 않은 페이지:

자주 접근하지 않는 페이지나, 사용자가 바로 클릭할 가능성이 낮은 페이지는 prefetch={false}로 설정하는 것이 좋습니다.

많은 페이지가 있는 경우:

페이지가 많다면, 모든 페이지에 대해 프리페칭을 하지 않는 것이 나을 수 있습니다. 이 경우 사용자가 클릭할 가능성이 높은 페이지만 선택적으로 프리페칭하는 것이 좋습니다.

무거운 페이지: 각 페이지에서 큰 데이터를 로드하거나 렌더링 시간이 긴 페이지는 프리페칭을 피하는 것이 성능에 유리할 수 있습니다.

결론

적당한 페이지 수나 자주 사용되는 페이지는 프리페칭을 유지하여 페이지 전환 속도를 빠르게 하는 것이 좋습니다.

그러나 페이지가 많거나 초기 로딩 성능이 중요한 경우, 불필요한 프리페칭을 줄이기 위해 prefetch={false}를 적용하는 것이 맞습니다.

그렇다면 나의 경우는 어떻게 적용할까?

사실 헤더에 링크 항목(퀴즈,코드 템플릿..등)들은 사용자가 접근하기 쉬운 페이지입니다. 때문에 헤더의 네비게이션을 통해 해당 페이지로 이동했을 때, 빠르게 페이지가 나타나는 것은 더 나은 UX를 제공할 수 있습니다.

그러므로 헤더에 있는 네비게이션 항목은 모두 prefetch를 적용해도 된다고 생각하므로 모두 default 값으로 두기로 하였습니다.

prefetch를 적용할 페이지

나의 경우에는 위 페이지들을 prefetch 적용하려고 한다.
위 페이지들은 자주 접근하는 페이지이고 사용자가 바로 클릭할 가능성이 높기 때문이다.
prefetch를 설정함으로써 빠른 페이지 전환이 가능하므로 페칭된 데이터를 사용해 즉각적으로 전환할 수 있다.

최종 코드

위 사항들을 고려하여 헤더 컴포넌트를 다음과 같이 반영하였다.
header.tsx

"use client"

import React from 'react';
import Link from "next/link";
import {usePathname} from "next/navigation";
import PrimaryButton from "@/app/_components/button/primaryButton";

/**
 * 헤더
 */
const Header = () => {
    const pathname = usePathname();


    // 네비게이션 메뉴
    const NAVMENU = [
        {
            title: "퀴즈",
            link: "quiz",
        },
        {
            title: "코드 템플릿",
            link: "codeTemplate",
        },
        {
            title: "스터디",
            link: "study",
        },
    ]


    // 현재 페이지에 맞는 클래스명
    function getActiveClass(link:string){
        if(pathname===`/${link}`){
            return "bg-primary-normal text-white hover:bg-primary-dark "
        }
    }
    return (
        <header className={"w-full h-[80px] bg-headerBackground flex justify-between items-center px-container"}>
            {/*로고,메뉴*/}
            <div className={"flex gap-10 items-center"}>
                <Link href={"/"}>
                <h1 className={"text-primary-normal text-headline3 font-bold"}>코아</h1>
                </Link>
                <nav>
                    <ul className={"flex gap-3 text-title3Normal text-primary-normal"}>
                        {NAVMENU.map((item, index) => (
                            <li key={index} className={`cursor-pointer px-[12px] flex justify-center items-center w-[100px]  h-[32px] rounded-[8px] hover:bg-primary-dark hover:text-white ${getActiveClass(item.link)}`}>
                                <Link
                                    href={`/${item.link}`}
                                    prefetch={true}
                                >{item.title}</Link>
                            </li>
                        ))}
                    </ul>
                </nav>
            </div>
            {/* 로그인,회원가입 & 프로필 */}
            <div className={"flex gap-3 items-center"}>
                <Link
                    href={"/login"}
                >
                    <PrimaryButton text={"로그인"} color={"primary"} />
                </Link>
                <Link
                    href={"/signup"}
                >
                    <PrimaryButton text={"회원가입"} color={"primarySecondary"} />
                </Link>

            </div>
        </header>
    );
};

export default Header;

짠! 이제 메인페이지에 들어가니 prefetch를 true로 지정한 페이지들만 요청하는 것을 확인할 수 있다.

아직 quiz,study,codeTemplate에 대한 페이지 코드 구성이 안되어있어 해당 페이지들의 UI들이 복잡하게 구성되어있다면 추후에 false로 지정하는 것도 고려해봐야겠다.

profile
FE DEVELOPER

0개의 댓글