본문 바로가기

Next.js

[Next.js] 라우팅과 네비게이션의 동작

라우팅과 네비게이션 동작을 이해하는데 도움이 되고자 How Routing and Navigation Works 내용을 더 상세하게 정리했습니다.

 

 

1. 코드 분할 (code splitting)

 

유저가 페이지에 접속하면 최초 경로에 대한 분할된 코드만 응답합니다. 이게 가능한 이유는 Next.js 서버가 각 경로마다 코드를 별도의 청크로 코드 분할하기 때문입니다. 후속으로 경로를 탐색해도 새로 탐색한 경로에 대한 코드만 가져오게 됩니다. 따라서 요청에 따른 응답 데이터양과 전송 시간이 줄어듭니다.

 

 

2. 미리 가져오기 (prefetching)

 

사용자의 뷰포트에 노출된 Link

 

유저가 링크로 된 경로로 이동하기 전에, Next.js는 코드를 미리 로드합니다. Link 컴포넌트의 prefetch를 설정하면, 위 이미지와 같이 사용자의 뷰포트에 노출될 때 링크된 경로를 백그라운드에서 미리 로드합니다.

 

그러면 Next.js 소스 코드의 Link 컴포넌트가 어떻게 동작하는지 살펴보겠습니다. Link 컴포넌트가 렌더링될 때 해당 경로에 대해 prefetch 메서드를 호출합니다.

// packages/next/src/client/link.tsx
function prefetch(
  router: NextRouter | AppRouterInstance,
  href: string,
  as: string,
  options: PrefetchOptions,
  appOptions: AppRouterPrefetchOptions,
  isAppRouter: boolean
): void {
    // 생략...
    const doPrefetch = async () => {
        if (isAppRouter) {
            return (router as AppRouterInstance).prefetch(href, appOptions)
        } else {
            return (router as NextRouter).prefetch(href, as, options)
        }
    }

    doPrefetch().catch((err) => {
        if (process.env.NODE_ENV !== 'production') {
            throw err
        }
    })
}

const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
    function LinkComponent(props, forwardedRef) {
        // 생략...
        React.useEffect(() => {
            // 생략...
            prefetch(
            //...
            )
        }, [
            //...
        ])
        // 생략...
    }
)

prefetch 기능은 프로덕션 환경에서만 활성화기 때문에 개발 환경에서 확인할 수 없습니다. 프로덕션 환경이라면 라우터의 prefetch를 호출합니다.

 

라우터의 prefetch를 따라가보면, 경로를 파싱해서 해당 경로에 있는 페이지를 비동기로 로드합니다.

// packages/next/src/shared/lib/router/router.ts
export default class Router implements BaseRouter {
    // 생략...
    async prefetch(
        url: string,
        asPath: string = url,
        options: PrefetchOptions = {}
      ): Promise<void> {
        // 생략...
        let parsed = parseRelativeUrl(url)
        const urlPathname = parsed.pathname

        let { pathname, query } = parsed    

        // 생략...

        const route = removeTrailingSlash(pathname)

        // 생략...

        await Promise.all([
          this.pageLoader._isSsg(route).then((isSsg) => {
            // ...
          }),
          this.pageLoader[options.priority ? 'loadPage' : 'prefetch'](route),
        ])
      }
    // 생략...
}

 

아래 코드는 클라이언트의 네비게이션 컴포넌트를 제가 직접 구현한 코드입니다. 각 Link 컴포넌트는 prefetch가 되도록 설정하고 실제로 어떤 데이터가 미리 가져와지는 확인 해보겠습니다.

import Link from "next/link";

type Path = {
    href: string;
    name: string;
};

function NavItem({ href, name }: Path) {
    return (
        <li>
            <Link href={href} prefetch>
                {name}
            </Link>
        </li>
    );
}

const paths: Path[] = [
    {
        href: "/",
        name: "Home",
    },
    {
        href: "/warnings",
        name: "Warnings",
    },
    {
        href: "/statics",
        name: "Statics",
    },
];

export default function Navigation() {
    return (
        <div className="fixed bottom-0 w-full py-12">
            <ul className="flex justify-evenly">
                {paths.map((path) => (
                    <NavItem key={path.name} {...path} />
                ))}
            </ul>
        </div>
    );
}

 

소스 코드 확인이 끝났고, 이제 개발 서버를 돌려서 홈 화면인 / 경로로 이동해봅니다.

위 이미지를 보시면 / 경로 외 나머지 경로로는 이동하지 않았음에도, /warnings/statics 경로가 한꺼번에 로드된 걸 확인할 수 있습니다. 응답 데이터는 직렬화되어 전송됩니다.

 

 

3. 캐싱 (caching)

Next.js 클라이언트는 라우트 캐시라는 클라이언트 사이드 인메모리 캐시를 가지고 있습니다. prefetch된 경로와 방문했던 경로들의 RSC 페이로드가 캐시에 저장됩니다.

이미 로드된 페이지에서는 다른 경로로 이동해도 아무것도 다운로드되지 않고, 클라이언트 사이드에서 계속 페이지가 이동하는 것을 확인할 수 있습니다.

 

Next.js  라우트 캐시  동작

이 이유는 캐시가 동작하기 때문입니다. prefetch 또는 이미 방문한 페이지는 라우터 캐시에 저장됩니다. 네비게이션 동작은 새로 고침 전까지 유지되고, 직접 prefetch를 설정한 경우 캐시 기본 지속 시간은 30초입니다. 그래서 더 이상 다운로드 없이 페이지 이동이 가능했던 것입니다.

 

 

4. 부분 렌더링 (Partial Rendering) & 소프트 네비게이션

페이지 내 이동에 따라 변경되어야 하는 부분만 렌더링되고, 나머지 공유되는 부분(상위 경로)은 유지됩니다. 위 그림에서는 settings 페이지와 analytics 페이지 부분이 부분 렌더링 되는 영역이고, 상위 경로의 레이아웃은 리렌더링 되지 않습니다. 이것을 소프트 네비게이션이라고도 하며, 리액트의 상태를 유지할 수 있습니다.

 

 

출처

Next.js Docs

'Next.js' 카테고리의 다른 글

CSR부터 RSC까지 렌더링 방식의 변화  (0) 2024.06.08
SSR(Server-side Rendering)  (0) 2023.06.30
CSR(Client-side Rendering)  (0) 2023.06.30