Next.js 13과 리액트 18 (1)

keemsebeen·2024년 12월 20일

모던 리액트 Deep Dive

목록 보기
17/18

11.1 app 디렉터리의 등장

13버전 이전까지 모든 페이지는 각각의 물리적으로 구별된 파일로 독립돼 있었다.
무언가를 집어 넣을 수 있는 곳은 _document와 _app이 유일하다.

  • _document : 페이지에서 쓰이는 <html><body> 태그를 수정하거나, 일부 CSS-in-JS를 지원하기 위한 코드를 삽입하는 제한적인 용도로 사용된다.
  • _app : 페이지를 초기화하기 위한 용도로 사용되며 , 다음과 같은 작업이 가능하다.
    • 페이지 변경 시 상태 유지
    • global CSS 주입
    • 페이지간 추가적인 데이터 삽입

이전깍지 공통 레이아웃을 유지할 방법이 _app으로 유일했고, 제한적인 레이아웃의 한계를 극복하기 위해 나온것이 Next.js의 app 레이아웃이다.

라우팅

/page 로 정의하던 라우팅 방식이 /app 디렉토리로 이동했고, 파일명으로 라우팅하는 것이 불가능해졌다.

라우팅을 정의하는 법
Next.js의 라우팅은 파일 시스템을 기반으로 하고 있다.

  • Next.js 12 이하 : /pages/a/b.tsx , /pages/a/index.tsx 는 모두 동일한 주소로 변환된다. 파일명이 index라면 이 내용은 무시된다.
  • Next.js 13 app : /app/a/b/a/b 로 변환되며, 파일명은 무시된다.

layout.js
페이지의 기본적인 레이아웃을 구성하는 요소다. 해당 폴더에 layout이 있다면 그 하위 폴더 및 주소에 모두 영향을 미친다.

따라서 루트에 단 하나의 layout을 만들고, 공통 UI를 포함하거나 웹페이지를 시작하는 데 필요한 공통 코드를 삽입할 수도 있다.

page.js

  • params : 옵셔널 값으로, 앞서 설명한 […id]와 같은 동적 라우트 파라미터를 사용할 경우 해당 파라미터에 값이 들어온다.
  • searchParams : URL에서 ?a=1 과 같은 URLSearchParams를 의미한다.

error.js
특정 라우팅별로 서로 다른 에러 UI를 렌더링하는 것이 가능해진다.

error 페이지는 에러 정보를 담고 있는 error 객체와 에러 바운더리를 초기화할 reset를 props로 받는다.

💡에러 바운더리는 클라이언트에서만 작동하므로 error 컴포넌트도 클라이언트 컴포넌트여야 한다.

not-found.js
특정 라우팅 하위의 주소를 찾을 수 없는 404 페이지를 렌더링할 때 사용된다.

loading.js
Suspense 기반으로 해당 컴포넌트가 불러오는 중임을 나타낼 때 사용한다.

route.js
/pages/api 와 동일하게 /app/api 를 기준으로 디렉터리 라우팅을 지원한다.디렉토리가 라우팅 주소를 담당하며 파일명은 route.js로 통일됐다.
route 함수들이 받을 수 있는 파라미터는 다음과 같다.

  • request : NextRequest 객체이며, fetch의 Request를 확장한 Next.js만의 Request라고 보면 된다. API 요청과 관련된 cookie, headers 뿐만 아니라 nextUrl 같은 주소 객체도 확인할 수 있다.
  • context : params 만을 가지고 있는 객체이며, 이 객체는 앞서 파일 기반 라우팅에서 언급한 것과 동일한 동적 라우팅 파라미터 객체가 포함돼 있다.

11.2 리액트 서버 컴포넌트

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

  • 자바스크립트 번들 크기가 0인 컴포넌트를 만들 수 없다.
  • 백엔드 리소스에 대한 직접적인 접근이 불가능하다.
    • 데이터 베이스에 직접 액세스하거나 백엔드 파일 시스템에 직접 접근한다면 클라이언트에 데이터를 제공하기 위한 수고로움이 줄어들 것이다.
  • 자동 코드 분할이 불가능하다.
    • 일일히 lazy를 사용해 조건문을 통해 사용하지 않고 서버에서 수행한다면, 자연스럽게 성능 이점을 누릴수 있을 것이다.
  • 연쇄적으로 발생하는 클라이언트와 서버의 요청을 대응하기 어렵다.
    • 데이터를 불러오고 컴포넌트 렌더링을 모두 서버에서 진행하게 되면 서버에 요청했을 때 발생하는 지연 시간을 줄일 수 있을 것이다.
  • 추상화에 드는 비용이 증가한다.

이처럼, 서버 사이드 렌더링은 정적 콘텐츠를 빠르게 제공하고, 서버에 있는 데이터에 손쉽게 제공할 수 있는 반면 사용자의 인터랙션에 따른 다양한 사용자 경험을 제공하기는 없다.

따라서 이 두 구조의 장점을 모두 취하고자 하는 것이 바로 리액트 서버 컴포넌트이다.

서버 컴포넌트란?

하나의 언어, 하나의 프레임워크, 하나의 API와 개념을 사용하면서 서버와 클라이언트 모두에서 컴포넌트를 렌더링할 수 있는 기법을 의미한다. 일부 컴포넌트는 클라이언트에서, 일부 컴포넌트는 서버에서 렌더링 되는 것이다.

한가지 주의점은 클라이언트 컴포넌트는 서버 컴포넌트를 import 할 수 없다는 것이다.

위와 같이 클라이언트 및 서버 컴포넌트가 혼재된 상황은 children으로 자주 사용되는 ReactNode에 달려 있다.

  • 서버 컴포넌트
    • 요청이 오면 그 순간 서버에서 딱 한번 실행될 뿐이므로 상태를 가질 수 없다. 따라서 리액트에서 상태를 가질 수 있는 useState, useReducer 등의 훅을 사용할 수 없다.
    • 렌더링 생명주기도 사용할 수 없다.
    • 사용자 정의 훅 또한 사용할 수 없다.
    • DOM API, window, document 등에 접근할 수 없다.
    • async/await으로 접근할 수 있다.
    • 다른 서버 컴포넌트를 렌더링 할 수 있다.
  • 클라이언트 컴포넌트
    • 브라우저 환경에서만 실행되므로 서버 컴포넌트를 불러오거나, 서버 전용 훅이나 유틸리티를 불러올 수 없다.
    • 서버 컴포넌트가 클라이언트 컴포넌트를 렌더링하는데, 그 클라이언트 컴포넌트가 자식으로 서버 컴포넌트를 갖는 구조는 가능하다.
  • 공용 컴포넌트
    • 서버/클라리언트 모두에서 사용할 수 있다.

그렇다면 리액트는 이 3가지 컴포넌트를 어떻게 판단할까? 리액트는 모든 것을 공용 컴포넌트도 판단한다. 대신 클라이언트 컴포넌트라는 것을 명시적으로 선언하려면 use client 라고 작성하면 된다.

서버 사이드 렌더링과 서버 컴포넌트의 차이

둘은 완전 다른 개념으로 볼 수 있다.

서버 사이드 렌더링

응답받는 페이지 전체를 HTML로 렌더링하는 과정을 서버에서 수행한 후 그 결과를 클라이언트에 내려준다. 이후 클라이언트에서 하이드레이션 과정을 거쳐 서버의 결과물을 확인하고 이벤트를 붙이는 등의 작업을 수행한다.

따라서 이후 서버사이드 렌더링과 서버 컴포넌트를 활용해 서버에서 렌더링할 수 있는 컴포넌트는 서버에서 완성해서 제공받은 다음, 클라이언트는 서버 사이드 렌더링으로 초기 HTML으로 빠르게 전달받을 수 있다.

이 두가지 방법을 결합하면 컴포넌트를 빨리 보여줄 수 있고, 동시에 클라이언트에서 내려받아야 하는 자바스크립트의 양도 줄어들어 브라우저의 부담을 덜 수도 있다. 결론적으로 둘은 대체제가 아닌 상호보완하는 개념이다.

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

  1. 서버가 렌더링 요청을 받는다. 서버가 렌더링 과정을 수행해야 하므로 리액트 서버 컴포넌트를 사용하는 모든 페이지는 항상 서버에서 시작된다. 즉 루트에 있는 컴포넌트는 항상 서버 컴포넌트다.
  2. 서버는 받은 요청에 따라 컴포넌트를 JSON으로 직렬화한다. 이후 브라우저에서 이결과물을 받아서 다시 역직렬화한 다음 렌더링을 수행한다.
  3. 브라우저가 리액트 컴포넌트 트리를 구성한다.

장점

  • 서버에서 클라이언트로 정보를 보낼 때 스트리밍 형태로 보냄으로써 클라이언트가 줄 단위로 JSON을 읽고 컴포넌트를 렌더링할 수 있어 브라우저에서는 되도록 빨리 사용자에게 결과물을 보여줄 수 있다.
  • 컴포넌트들이 하나의 번들러 작업에 포함돼 있지 않고 각 컴포넌트별로 번들링이 별개로 돼 있어 필요에 따라 컴포넌트를 지연해서 받거나 따로 받는 등의 작업이 가능해졌다.
  • 결과물이 HTML이 아닌 JSON 형태로 보내진 것에 주목해볼만 하다.
profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

0개의 댓글