🚀 94sssh
Published on

2025.06.06

[리액트 딥다이브] - 11. Next.js 13과 리액트 18

11.1. app 디렉터리의 등장

11.1.1. 라우팅

layout.js

페이지의 기본적인 레이아웃을 구성하는 요소로 하위 폴더 및 주소에 모두 영향을 미침.
루트에는 하나의 layout만 둘 수 있으며, 모든 페이지에 영향을 미치는 공통 레이아웃이 됨.
페이지 탐색 중에는 리렌더링을 수행하지 않음.

page.js

page가 받는 props

  • params: [...id]와 같은 동적 라우트 파라미터를 사용할 경우 값이 들어옴
  • searchParams: URLSearchParams 객체 값이 들어옴. layout에서는 제공되지 않음.

error.js

해당 라우팅 영역에서 사용되는 공통 에러 컴포넌트.
Error 객체와 에러 바운더리를 초기화할 reset을 props로 받음.
에러 바운더리는 클라이언트에서만 작동하므로 error 컴포넌트는 클라이언트 컴포넌트여야 함.
같은 수준의 layout에서의 에러는 상위 컴포넌트의 error를 사용해야 함.

not-found.js

404 페이지를 렌더링할 때 사용.

loading.js

리액트 Suspense를 기반으로 로딩 상태를 나타낼 때 사용.

route.js

/app/api를 기준으로 디렉토리 라우팅을 지원함.
route.ts 파일 내부에 REST API의 get, post와 같은 메서드명을 예약어로 선언해 두면 HTTP 요청에 맞게 해당 메서드를 호출.
route.ts가 존재하는 폴더에는 page.tsx가 존재할 수 없음.

11.2. 리액트 서버 컴포넌트

11.2.1. 기존 리액트 컴포넌트와 서버 사이드 렌더링의 한계

지금까지의 서버 사이드 렌더링 구조의 한계점

  • 자바스크립트 번들 크기가 0인 컴포넌트를 만들 수 없다
  • 백엔드 리소스에 대한 직접적인 접근이 불가능하다
  • 자동 코드 분할(code split)이 불가능하다
  • 연쇄적으로 발생하는 클라이언트와 서버의 요청을 대응하기 어렵다
  • 추상화에 드는 비용이 증가한다

기존 서버 사이드 렌더링의 문제는 리액트가 클라이언트 중심으로 돌아가기 때문에 발생.
리액트 서버 컴포넌트는 SSR과 CSR의 장점을 모두 취하고자 도입

11.2.2. 서버 컴포넌트란?

하나의 언어, 하나의 프레임워크, 하나의 API와 개념을 사용하면서 서버와 클라이언트 모두에서 컴포넌트를 렌더링할 수 있는 기법. 일부 컴포넌트는 클라이언트에서, 다른 일부는 서버에서 렌더링하는 방식.
클라이언트 컴포넌트는 서버 컴포넌트를 import할 수 없다. 서버 컴포넌트를 실행할 방법이 없기 때문.
그렇지만 서버 컴포넌트를 children으로 받아 사용할 수 있다.

각 컴포넌트별 차이와 제약사항

서버 컴포넌트

  • 상태를 가질 수 없음
  • 렌더링 생명주기도 사용할 수 없음
  • 따라서 훅과 커스텀 훅을 모두 사용할 수 없음
  • 서버에서 실행되므로 DOM API나 window, document에 접근할 수 없음
  • 데이터베이스, 내부 서비스, 파일 시스템 등 서버의 데이터에 async/await로 접근할 수 있음
  • 다른 서버 컴포넌트를 렌더링하거나 div, span, p 같은 요소를 렌더링하거나, 클라이언트 컴포넌트를 렌더링할 수 있음

클라이언트 컴포넌트

  • 브라우저 환경에서만 실행되므로 서버 컴포넌트를 불러오거나, 서버 전용 훅, 유틸리티를 불러올 수 없음
  • 서버 컴포넌트를 자식으로 갖는 구조는 가능

공용 컴포넌트

  • 서버와 클라이언트 모두에서 사용 가능. 서버 컴포넌트와 클라이언트 컴포넌트의 모든 제약을 받음.

11.2.4. 서버 컴포넌트는 어떻게 작동하는가?

  1. 서버가 렌더링 요청을 받음.
  2. 받은 요청에 따라 컴포넌트를 JSON으로 직렬화 하는데, 서버에서 렌더링할 수 있는 것은 직렬화하고, 클라이언트 컴포넌트 부분은 플레이스홀더 형식으로 비워두고 나타냄.
  3. 브라우저가 리액트 컴포넌트 트리를 구성함. JSON 결과물을 다시 파싱 후, 트리를 재구성해 컴포넌트를 만든다.

11.3. Next.js에서의 리액트 서버 컴포넌트

11.3.1. 새로운 fetch 도입과 getServerSideProps, getStaticProps, getInitial Props의 삭제

과거 Next.js의 서버 사이드 렌더링과 정적 페이지 제공을 위해 이용되던 getServerSideProps, getStaticProps, getInitialProps가 /app 디렉토리 내부에서 삭제됨. 모든 데이터 요청은 표준 API인 fetch를 기반으로 이뤄짐.

11.3.2. 정적 렌더링과 동적 렌더링

Next.js 13에서 정적인 라우팅은 빌드 타임에 렌더링을 미리 해두고 캐싱을, 동적인 라우팅은 요청이 올 때마다 컴포넌트를 렌더링하도록 변경됨.

11.3.3. 캐시와 mutating, 그리고 revalidating

Next.js는 fetch의 기본 작동을 재정의해 데이터의 유효 시간을 정하고, 시간이 지나면 데이터를 다시 불러와 렌더링하도록 할 수 있음. 페이지에 revalidate라는 변수를 선언해 페이지 레벨로 정의하는 것도 가능.

// app/page.tsx
export const revalidate = 60

루트에 revalidate=60을 선언해 두면 하위의 모든 라우팅은 페이지를 60초 간격으로 갱신해 새로 렌더링하게 됨.
캐시를 무효화하고 싶다면 router에 추가된 refresh 메서드로 router.refresh()를 사용하면 되는데, 브라우저 새로고침 등 브라우저 히스토리에 영향을 미치지 않고, 브라우저나 리액트의 state에도 영향을 미치지 않음.

11.3.4. 스트리밍을 활용한 점진적인 페이지 불러오기

과거 서버 사이드 렌더링 방식은 요청받은 페이지를 모두 렌더링해서 내려줄 때까지 사용자는 아무것도 볼 수 없어 빈 페이지만 보게 됨.

이를 해결하기 위해 HTML을 작은 단위로 쪼개 보내는 스트리밍이 도입. 데이터가 로드되는 컴포넌트를 빠르게 보여줄 수 있게 됨. 스트리밍을 활용할 수 있는 방법은 두 가지가 있음.

  • 경로에 loading.tsx 배치
  • Suspense 배치