랜더링은 작성한 코드를 사용자 인터페이스로 변환합니다.
React 18과 Next.js 13은 애플리케이션을 랜더링하는 새로운 방법을 도입했습니다. 이번에는 렌더링 환경, 전략, 런타임의 차이점과 이를 선택하는 방법을 정리합니다.
애플리케이션 코드를 랜더링할 수 있는 환경에는 클라이언트와 서버 두 가지가 있습니다.
클라이언트는 애플리케이션 코드에 대한 요청을 서버에 보내는 사용자 디바이스를 말합니다. 그런 다음 서버의 응답을 사용자가 상호 작용할 수 있는 인터페이스로 변환합니다.
서버는 애플리케이션 코드를 저장하고, 클라이언트로부터 요청을 받고, 일부 연산을 수행한 후 적절한 응답을 보내는 데이터 선테의 컴퓨터를 말합니다.
React 18 이전에는 React를 사용해 애플리케이션을 랜더링하는 주된 방법은 전적으로 클라이언트에서 이루어졌습니다.
Next.js는 애플리케이션을 페이지로 나누고 HTML을 생성하여 서버에서 미리 랜더링한 후 클라이언트에 전송하여 React가 하이드레이트할 수 있도록하는 쉬운 방법을 제공했습니다. 하지만 이로 인해 초기 HTML을 인터렉트비하게 만들기 위해 클라이언트에서 추가적인 자바스크립트가 필요했습니다.
이제 서버 및 클라이언트 컴포넌트를 사용하면 React가 클라이언트와 서버에서 랜더링할 수 있으므로 컴포넌트 수준에서 랜더링 환경을 선택할 수 있습니다.
기본적으로 앱 라우터는 서버 컴포넌트를 사용하므로 서버에서 컴포넌트를 쉽게 랜더링할 수 있고 클라이언트로 전송되는 자바스크립트의 양을 줄일 수 있습니다.
React 컴포넌트를 사용한 클라이언트 측 및 서버 측 랜더링 외에도 Next.js는 정적 및 동적 랜더링을 통해 서버에서 랜더링을 최적화할 수 있는 옵션을 제공합니다.
정적 랜더링을 사용하면 빌드 시점에 서버와 클라이언트 컴포넌트 모두 서버에서 미리 랜더링할 수 있습니다. 작업 결과는 캐시되어 이후 요청 시 재사용됩니다. 캐시된 결과는 재검증할 수도 있습니다.
정적 랜더링 중에는 서버 컴포넌트와 클라이언트 컴포넌트가 다르게 랜더링됩니다.
클라이언트 컴포넌트는 HTML과 JSON이 미리 랜더링되어 서버에 캐시됩니다. 그런 다음 캐시된 결과가 클라이언트로 전송되어 하이드레이트됩니다.
서버 컴포넌트는 React에 의해 서버에서 랜더링되며, 페이로드는 HTML을 생성하는 데 사용됩니다. 랜더링된 동일한 페이로드가 클라이언트에서 컴포넌트를 하이드레이트하는 데에도 사용되므로 클라이언트에서 자바스크립트가 필요하지 않습니다.
동적 랜더링을 사용하면 서버와 클라이언트 컴포넌트 모두 요청 시점에 서버에서 랜더링됩니다. 작업 결과는 캐시되지 않습니다.
Next.js에서는 라우트를 정적으로 랜더링하거나 동적으로 랜더링할 수 있습니다.
정적 라우트에서는 컴포넌트가 빌드 시점에 서버에서 랜더링됩니다. 작업 결과는 캐시되어 후속 요청에서 재사용됩니다.
동적 라우트에서는 컴포넌트가 요청 시점에 서버에서 랜더링됩니다.
기본적으로 Next.js는 성능을 향상시키기 위해 라우트를 정적으로 랜더링합니다. 즉, 모든 랜더링 작업이 미리 수행되어 사용자와 지리적으로 더 가까운 콘텐츠 전송 네트워크 (CDN)에서 제공될 수 있습니다.
기본적으로 Next.js는 캐싱 동작을 특별히 옵트아웃하지 않는 이상 fetch()
요청의 결과를 캐시합니다. 즉, 캐시 옵션을 설정하지 않은 fetch 요청은 강제 캐시 옵션을 사용합니다.
라우트의 모든 가져오기 요청이 재검증 옵션을 사용하는 경우 재검증 주에 경로가 정적으로 다시 랜더링됩니다.
정적 랜더링 중에 동적 함수 또는 동적 fetch()
요청 (캐싱 없음)이 발견되면 Next.js는 요청 시점에 전체 라우트를 동적으로 랜더링하는 것으로 전환합니다. 캐시된 데이터 요청은 동적 랜더링 중에 계속 재사용할 수 있습니다.
동적 함수는 데이터 불러오기의 캐시 여부에 관계없이 항상 동적 랜더링으로 라우트를 선택한다는 점에 유의하세요. 즉, 정적 랜더링은 데이터 가져오기 동작뿐만 아니라 라우트에 사용되는 동적 함수에 따라 달라집니다.
동적 함수는 사용자의 쿠키, 현재 요청 헤더 또는 URL의 검색 매개변수와 같이 요청 시점에만 알 수 있는 정보에 의존합니다. Next.js에서 이러한 동적 함수는 다음과 같습니다.
서버 컴포넌트에서 cookies()
또는 headers()
를 사용하면 요청 시 전체 경로를 동적 랜더링으로 선택합니다.
클라이언트 컴포넌트에서 useSearchParams()
를 사용하면 정적 랜더링을 건너뛰고 대신 클라이언트에서 가장 가까운 상위 서스펜스 경계까지 모든 클라이너트 컴포넌트를 랜더링합니다.
useSearchParams()
을 사용하는 클라이언트 컴포넌트를 <Suspense />
경계로 감싸는 것이 좋습니다. 이렇게 하면 그 위에 있는 모든 클라이언트는 정적으로 랜더링 될 수 있습니다.
searchParams Pages
프로퍼티를 사용하면 요청 시 페이지가 동적 랜더링됩니다.
동적 데이터 가져오기는 캐시 옵션을 no-store
으로 설정하거나 revalidate
을 0으로 설정하여 캐싱 동작을 특별히 옵트아웃하는 fetch()
요청입니다.
레이아웃 또는 페이지의 모든 가져오기 요청에 대한 캐싱 옵션은 세그먼트 설정 객체를 사용하여 설정할 수 있습니다.
Next.js의 맥락에서 런타임은 코드가 실행되는 동안 사용할 수 있는 라이브러리, API 및 일반 기능 집합을 의미합니다.
Next.js에는 애플리케이션 코드의 일부를 랜더링할 수 있는 두 개의 서버 런타임이 있습니다.
각 런타임에는 고유한 API가 있습니다. 사용 가능한 API의 전체 목록은 Node.js 문서와 Edge 문서를 참조하면 됩니다. 두 런타임 모두 배포 인프라에 따라 스트리밍을 지원할 수도 있습니다.
기본적으로 앱 디렉토리는 Node.js 런타임을 사용합니다. 하지만 라우터별로 다른 런타임을 선택할 수 있습니다.
런타임을 선택할 떄 고려해야 할 사항이 많습니다.
Next.js에서 경량 Edge 런타임은 사용 가능한 Node.js API의 하위 집합입니다.
Edge 런타임은 작고 간단한 기능으로 짧은 지연 시간으로 동적이고 개인화된 콘텐츠를 제공해야 하는 경우에 이상적입니다. Edge 런타임은 속도는 최소한의 리소스 사용에서 비롯되지만, 많은 시나리오에서 제한적일 수 있습니다.
예를 들어, Vercel의 Edeg 런타임에서 실행되는 코드는 1MB에서 4MB를 초과할 수 없으며, 이 제한에는 가져온 패키지, 글꼴 및 파일이 포함되며 배포 인프라에 따라 달라질 수 있습니다.
Node.js 런타임을 사용하면 모든 Node.js API와 이에 의존하는 모든 npm 패키지에 엑세스할 수 있습니다. 그러나 Edge 런타임을 사용하는 경로만큼 시작 속도가 빠르지는 않습니다.
Next.js 애플리케이션을 Node.js 서버에 배포하려면 인프라를 관리, 확장 및 구성해야 합니다. 또는 이 작업을 대신 처리해주는 Vercel과 같은 같은 서버리스 플랫폼에 Next.js 애플리케이션을 배포하는 것을 고려할 수 있습니다.
서버리스는 Edge 런타임보다 더 복잡한 계산 부하를 처리할 수 있는 확장 가능한 솔루션이 필요한 경우 이상적입니다. 예를 들어 Vercel의 서버리스 함수를 사요하면 가져온 패키지, 글꼴, 파일을 포함하여 전체 코드 크기가 50MB입니다.
세그먼트 런타임 옵션
Next.js 애플리케이션에서 개별 Route Segment에 런타임을 지정할 수 있습니다. 이렇게 하려면 runtime이라는 변수를 선언하고 내보내세요. 변수는 문자열이어야 하며, nodejs
또는 edge
런타임 중 하나의 값을 가져야 합니다.
다음 예제는 값이 edge
인 런타임을 내보내는 페이지 Route Segment를 보여줍니다.
export const runtime = 'edge' // 'nodejs' (default) | 'edge'