[Next.js 13] pages -> app directory 리펙토링

henry·2023년 7월 10일
1
post-thumbnail

⚠️ 본 포스트는 프로젝트 리펙토링 과정을 담은 개인 기록입니다. Next.js 13 버전의 특징이나 사용법에 대한 학습을 원하신다면, 공식 문서를 참조하시기 바랍니다.

서버 컴포넌트를 사용할 수 있는 next.js@13.4 버전이 정식으로 릴리즈되면서 실무에서 적용된 page router를 app router으로 업그레이드 하기전에 개인 프로젝트에 적용을 해봤다.

next.config.js 파일에 아래 코드를 추가하고 app/경로에 파일을 생성해서 코드를 작성하면 된다

module.exports = {
  experimental: {
    appDir: true,
  },
  // Other configuration options
};

1. Nested Routes & Layouts

기존 Next.js 앱에는 애플리케이션의 모든 페이지와 뷰를 관리하는데 중요한 두 가지구성 요소를 제공한다.

1. _document.tsx

  • 이 구성 요소는 정적 마크업을 정의하는 데 사용된다. 이 파일은 서버에서 렌더링되며 클라이언트에서 다시 렌더링되지 않는다 . , 태그 및 기타 메타데이터 에 영향을 주는 데 사용된다 . 이러한 항목을 사용자 지정하지 않으려는 경우 애플리케이션에 포함하는 것은 선택적이다.

2. app.tsx

  • 이 파일은 앱 전반에 걸쳐서 필요한 로직을 정의하는 데 사용된다. 앱의 모든 단일 뷰에서 표시되어야 하는 모든 것이 이 파일에 포함된다. 들, 전역 정의, 응용 프로그램 설정 등에 사용된다.

기존 Next.js@12 에서는 특정 라우팅 하위에 레이아웃을 구성하는 것이 불가능했으며, 이로 인해 코드를 중복으로 작성해야 하는 불편함이 있었다.
관련 이슈

공통적으로 사용하는 레이아웃은 _app.jsx 파일이나 _document.jsx 에 적용할 수 있었지만, /main/first /main/second 와 같아 특정 라우트 하위에서 공통 레이아웃을 구성하는 것은 불가능 해서 이로인해 코드의 중복 작성을 해야했다.

그러나 Next.js 13버전 app폴더 구조에서는 이 문제를 해결했다. 특히 layout.tsx을 공유하는 페이지를 탐색할 때 레이아웃이 리렌더링이 되지 않는다. 이를 통해 불필요한 렌더링을 줄이고, 웹 성능을 향상시킬 수 있었다.

아래와 같이 구성된 프로젝트의 폴더 구조가 있다고 가정해보자

- app
  📂 main
    - layout.tsx
    📂 first
      - page.tsx
    📂 second
      - page.tsx

main 세그먼트에 작성된 layout.tsx 파일은 Leaf 세그먼트인 /first/second 에도 영향을 준다. 따라서, 하위 라우트는 모두 <body>{chlidren}</body>안에 구성된다. 이로써, 중복된 레이아웃 코드 작성을 방지하고, 더 효율적인 코드 구조를 만들 수 있게 됐다.

// app/main/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

2. app 디렉토리에서 SSR, SSG, ISR 구현

next.js@13에서는 모든 컴포넌트가 서버 컴포넌트로 기존에 사용하던 getServerSideProps, getStaticProps 함수가 필요 없어졌다. 그러면 Next 13버전에서는 어떻게 데이터를 가져올까?

fetch API를 사용한다

Next.js 13에서는 fetch 함수에서 cache 옵션과 next 옵션 값을 줘서 SSR, ISR, SSG 와 같은 통신을 구현할 수 있다.

1. SSG

직접 무효화 하기 전까지는 request가 캐싱된다.
빌드 시점에 fetch를 하고 요청 시 캐싱된 데이터를 반환해서 getStaticProps`방식과 비슷하다.
force-cache 옵션은 default 값으로 생략 가능하다.

fetch(URL, { cache: 'force-cache' });

2. SSR

매번 요청 때마다 refetch 된다 (getServerSideProps 방식과 비슷하다)

fetch(URL, { cache: 'no-store' });

3. ISR

// 일정 시간 동안만 캐싱

fetch(URL, { next: { revalidate: 20 } });

니콘내콘 프로젝트에서는 데이터를 가져오는 데 SSR, ISR, SSG 세 가지 방법을 모두 사용하고 있었다.

  1. 메인 페이지(/) : ISR
  2. 브랜드 페이지(/brand/[id]): SSR
  3. 카테고리 페이지(/itemList/[id]): ISR (dynamic routes로 getStaticPaths 사용)
  4. 상품 상세 페이지(/items/[id]): SSR

메인 페이지에서는 '카테고리 종류' 및 '땡처리 콘' 데이터를 가져오기 위해 두 가지 별도의 통신을 사용하여 데이터를 가져와 화면에 표시하고 있었다. 그러나 화면 상단에 보여지는 카테고리 종류 데이터는 자주 변경되지 않았기 때문에 SSG 방식으로 구현해도 충분했으나, 떙처리 콘 데이터는 자주 변경되는 데이터라 불필요하게 revalidate 값을 설정하여 데이터를 주기적으로 업데이트하고 받아왔다.

Pages Router ISR

위와 같은 문제를 App router의 서버컴포넌트를 도입하면서 페이지 단위가 아닌 컴포넌트 단위로 캐시 전략을 세울 수 있어서 한 페이지에서 통신마다 revalidate속성값을 줄 수 있게 되면서 서버의 부하를 최소화 시켰다.

App router의 서버 컴포넌트를 도입함으로써 페이지 단위가 아닌 컴포넌트 단위로 캐시 전략을 수립할 수 있게 됐다.

App Router Server Component

마이그레이션 후 초기 렌더링 속도 비교 영상

1. Pages Router

2. App Router


App Router로 마이그레이션한 결과, Pages Router 방식에 비해 TTFB가 감소한 것을 확인할 수 있었다. 아래 두 가지 링크에서 비교할 수 있다.

Next.js page router Link : https://ncnc-9ytztvgzd-hyjoong.vercel.app/
Next.js app router Link: https://ncnc.vercel.app/

Intercepting Routes를 활용한 상세 페이지 모달

App Router의 Intercepting Routes을 이용하여, 페이지 이동 시 soft navigation으로 모달 형태로 상세 정보를 표시하고, hard navigation으로 페이지 이동 시 상세 페이지를 보여주도록 구현했다.

App Router 마이그레이션으로 얻은 이점과 변화

1. 빨라진 TTI(Time to Interactive)

  • 기존 SSR방식은 JavaScript에 모든 내용을 포함해야 해서 CSR방식에 비해서 LCP, FCP속도만 개선되었지만, app router에서는 streaming 방식을 채택하여 TTI도 개선이 됐다.

2. 컴포넌트 단위의 캐시

  • 페이지 단위가 아닌 컴포넌트 단위로 캐시 전략을 세울 수 있어서 같은 페이지라도 자주 요청이 필요한 컴포넌트만 자주 요청해서 서버 부하를 최소화 할 수 있었다.

3. 감소한 번들사이즈

  • 서버 컴포넌트는 서버에서만 렌더링되기 때문에, 클라이언트 번들링에 포함되지 않아 빌드 시점에서 번들 사이즈가 감소하였다.

0개의 댓글