FE/리뷰

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

따봉치치 2024. 9. 10. 16:49



Next.js 13
app 디렉터리 등장
  • /pages로 정의하던 라우팅 방식이 /app 디렉터리로 이동함
    • 파일명으로 라우팅하는 것이 불가능해짐 => 폴더명까지만 주소로 변환됨
  • layout.js
    • 페이지의 기본적인 레이아웃 구성하는 요소
    • 해당 폴더에 layout이 존재하면 그 하위 폴더 및 주소에 모두 영향을 미침
    • 주소별 공통 UI 포함할 수 있음
    • _app과 _document를 대신해 웹페이지를 시작하는 데 필요한 공통 코드를 삽입할 수 있음
    • 단, layout예약어를 필수적으로 사용해야 함
    • children을 props로 받아서 렌더링 해야함
  • page.js
    • layout을 기반으로 위와 같은 리액트 컴포넌트를 노출함
  • error.js
    • 해당 라우팅 영역에서 사용되는 공통 에러 컴포넌트
    • 특정 라우팅별 서로 다른 에러 UI를 렌더링할 수 있음
    • 에러 정보를 담고 있는 error 객체와 에러 바운더리를 초기화할 reset 함수를 props로 받음
    • 단, 에러 바운더리는 클라이언트에서만 작동하기 때문에 error 컴포넌트 또한 클라이언트 컴포넌트여야 함
    • layout에서 에러가 발생할 경우 error 컴포넌트로 이동하지 않음 => <layout><erorr>{children}</error></layout> 구조로 렌더링되기 때문
  • not-found.js
    • 특정 라우팅 하위의 주소를 찾을 수 없는 404 페이지를 렌더링할 때 사용
  • loading.js
    • 리액트 suspense를 기반으로 해당 컴포넌트가 불러오는 중임을 나타낼 때 사용함
  • route.js
    • REST API의 get, post와 같은 메서드명을 예약어로 선언해 두면 HTTP 요청에 맞게 해당 메서드를 호출하는 방식으로 작동함

 

리액트 서버 컴포넌트
  • 기존 리액트 컴포넌트와 서버 사이드 렌더링의 한계 => 즉 리액트가 클라이언트 중심으로 돌아가기 때문에 발생하는 문제
    • 자바스크립트 번들 크기가 0인 컴포넌트를 만들 수 없음
    • 백엔드 리소스에 대한 직접적인 접근이 불가능함
    • 자동 코드 분할이 불가능함 => 일일이 lazy로 감싸고 이를 기억해야 함
    • 연쇄적으로 발생하는 클라이언트와 서버의 요청을 대응하기 어려움
    • 추상화에 드는 비용이 증가함
  • 서버 컴포넌트
    • 하나의 언어, 하나의 프레임워크, 그리고 하나의 API와 개념을 사용하면서 서버와 클라이언트 모두에서 컴포넌트를 렌더링할 수 있는 기법을 의미
    • 서버에서 할 수 있는 일은 서버가 처리하게 두고, 서버가 할 수 없는 나머지 작업은 클라이언트인 브라우저에서 수행
    • 단, 클라이언트 컴포넌트는 서버 컴포넌트를 import 할 수 없음

 

 

리액트 컴포넌트
  • 서버 컴포넌트
    • 요청이 오면 그 순간 서버에서 딱 한 번 실행됨 => 상태를 가질 수 없음, 리액트에서 상태를 가질 수 있는 훅을 사용할 수 없음
    • 렌더링 생명주기 사용할 수 없음
    • effect나 state에 의존하는 사용자 정의 훅 또한 사용할 수 없음
    • 브라우저에서 실행되지 않고 서버에서만 실행되기 때문에 DOM API를 사용하거나 window, document 등 객체에 접근할 수 없음
    • 데이터베이스, 내부 서비스, 파일 시스템 등 서버에만 있는 데이터를 async/await 으로 접근할 수 있음
    • 다른 서버 컴포넌트를 렌더링하거나 혹은 클라이언트 컴포넌트를 렌더링할 수 있음
  • 클라이언트 컴포넌트
    • 브라우저 환경에서만 실행되므로 서버 컴포넌트를 불러오거나, 서버 전용 훅이나 유틸리티를 불러올 수 없음
    • 클라이언트가 자식으로 서버 컴포넌트를 갖는 구조가 가능함
  • 공용 컴포넌트
    • 서버와 클라이언트 모두에서 사용할 수 있음
    • 서버 컴포넌트와 클라이언트 컴포넌트의 모든 제약을 받는 컴포넌트가 됨

 

 

서버 사이드 렌더링과 서버 컴포넌트의 차이
  • 서버 사이드 렌더링
    • 응답반은 페이지 전체를 HTML로 렌더링하는 과정을 서버에서 수행한 후 그 결과를 클라이언트에 내려줌
    • 이후 클라이언트에서 하이드레이션 과정을 거쳐 서버의 결과물을 확인하고 이벤트를 붙이는 등의 작업 수행
    • 초기에 인터렉션을 부가능하지만 정적인 HTML을 빠르게 내려주는 데 목적을 둠
  • 서버 컴포넌트
    • 서버에서 렌더링할 수 있는 컴포넌트는 서버에서 완성해서 제공받은 다음, 클라이언트 컴포넌트는 서버 사이드 렌더링으로 초기 HTML으로 빠르게 전달받을 수 있음
    • 이러한 방식으로 하면 클라이언트 및 서버 컴포넌트를 모두 빠르게 보여줄 수 있고, 동시에 클라이언트에서 내려받아야 하는 자바스크립트의 양도 줄어 브라우저의 부담을 덜 수도 있음

 

서버 컴포넌트 작동 방식
  1. 서버가 렌더링 요청을 받는다. 서버가 렌더링 과정을 수행해야야 하므로 리액트 서버 컴포넌트를 사용하는 모든 페이지는 항상 서버에서 시작되
  2. 서버는 받은 요청에 따라 컴포넌트를 JSON으로 직렬화함. 이때 서버에서 렌더링할 수 있는 것은 직렬화해서 내보내고, 클라이언트 컴포넌트로 표시된 부분은 해당 공간을 플레이스홀더 형식으로 비워두고 나타냄. 브라우저는 이 후 결과물을 받아 다시 역직렬화한 다음 렌더링을 수행함
  3. 브라우저가 리액트 컴포넌트 트리를 구성함. 브라우저가 서버로 스트리밍으로 JSON 결과물을 받았다면 이 구문을 다시 파싱한 결과물을 바탕으로 트리를 재구성해 컴포넌트를 만들어 나감. 최종적으로 이 트리를 렌더링해 브라우저의 DOM에 커밋함

 

 

서버 컴포넌트의 작동 방식의 특별한 점
  • 서버에서 클라이언트로 정보를 보낼 때 스트리밍 형태로 보내 클라이언트가 줄 단위로 JSON을 읽고 컴포넌트를 렌더링할 수 있어 브라우저에서는 되도록 빨리 사용자에게 결과물을 보여줄 수 있음
  • 컴포넌트별로 번들링이 별개로 돼 있어 필요에 따라 컴포넌트를 지연해서 받거나 따로 받는 등의 작업이 가능함
  • 서버 사이드 렌더링과는 다르게 결과물이 HTML이 아닌 JSON 형태로 보내짐

 

 

Next.js에서의 리액트 서버 컴포넌트
  • 서버 컴포넌트는 클라이언트 컴포넌트를 불러올 수 없음
  • 클라이언트 컴포넌트는 서버 컴포넌트를 children props로 받는 것만 가능
  • 루트 컴포넌트는 각 페이지에 존재하는 page.js, layout.js이고, 모두 반드시 서버 컴포넌트여야 함
  • getServerSideProps, getStaticProps, getInitialProps 삭제
  • fetch API를 확정해 같은 서버 컴포넌트 트리 내에서 동일한 요청이 있다면 재요청이 발생하지 않도록 요청 중복 방
    • 서버 : 렌더링이 한 번 끝날 때까지 캐싱
    • 클라이언트 : 별도의 지시자나 요청이 없는 이상 해당 데이터를 최대한 캐싱
  • 정적 렌더링과 동적 렌더링
    • 정적 라우팅 : 기본적으로 빌드 타임에 렌더링을 미리 해두고 캐싱해 재사용할 수 있게 함
    • 동적 라우팅 : 서버에 매번 요청이 올 때마다 컴포넌트를 렌더링하도록 변경
  • fetch 옵션에 따른 작동 방식
    • fetch(URL, { cache : 'force-cach' }) : 기본값으로 getStaticProps와 유사하게 불러온 데이터를 캐싱해 해당 데이터로만 관리
    • fetch(URL, { cache : 'no-store' }) , fetch(URL, { next: {revalidate : 0}}): getServerSideProps와 유사하게 캐싱하지 않고 매번 새로운 데이터를 불러옴
    • fetch(URL, { next: {revalidate : 10} }) : getStaticProps에 revalidate를 추가한 것과 동일하며, 정해진 유효시간 동안에는 캐싱하고, 이 유효시간이 지나면 캐시를 파기함
  • revalidate
    • 페이지에 revalidate라는 변수를 선언해서 정해진 시간 간격으로 갱신해 새로 렌더링 하게 됨
  • 스트리밍을 활용한 점진적인 페이지 불러오기
    • 하나의 페이지가 모두 다 완성될 때까지 기다리는 것이 아니라 HTML을 작은 단위로 쪼개서 완성되는 대로 클라이언트로 점진적으로 보내는 스트리밍 도입
    • 모든 데이터가 로드될 떄까지 기다리지 않더라도 먼저 데이터가 로드되는 컴포넌트를 빠르게 보여주는 방법이 가능
    • 사용자가 일부라도 페이지와 인터렉션 할 수 있음
    • 최초 바이트까지의 시간 (TTFB), 최초 콘텐츠풀 페인팅(FCP)를 개선하는 데 도움됨
    • 경로에 loading 배치
    • suspense 배치
  • 터보팩 등장
    • 웹팩 대비 최대 700배, vite 대비 최대 10 빠르다고 함
  • 서버 액션
    • API를 굳이 생성하지 않더라도 함수 수준에서 서버에 직접 접근해 데이터 요청 등을 수행할 수 있는 기능
    • 서버 컴포넌트와 달리, 특정 함수 실행 그 자체만을 서버에서 수행할 수 있다는 장점
    • 최초에 페이지를 서버에서 렌더링한 이후에 폼에서 action으로 서버에 데이터 수정을 요청하고, 수정된 결과를 다시 조회해서 새로운 결과로 렌더링하는 모든 과정에 페이지 새로고침없이 수행할 수 있음
    • form의 action
      • action props를 추가해서 양식 데이터를 처리할 URI를 넘겨줄 수 있음
    • input의 submit, image의 formAction
    • startTransition과의 연동

 

서버 액션 사용 시 주의할 점
- 서버 액션은 클라이언트 컴포넌트 내에서 정의될 수 없음
- 서버 액션을 import하는 것뿐만 아니라, props 형태로 서버 액션을 클라이언트 컴포넌트에 넘기는 것 또한 가능함