렌더링은 작성한 코드를 UI로 바꿔주는 것인데요. Next에서 렌더링은 서버와 클라이언트 양쪽에서 모두 이뤄질 수 있습니다. 하지만 서버와 클아이언트 각각의 환경에 더 어울리는 작업이 있고 이를 이해하는 것이 Next를 효율적으로 사용할 수 있는 방법입니다.
"use client"나 "use server"를 선언하여 코드가 실행되는 환경을 지정할 수 있습니다. server-only나 client-only 패키지를 import해서 각각의 환경에서만 실행될 것을 보장하도록 설정할 수도 있습니다.
기본적으로 Next는 서버 컴포넌트를 사용합니다. 필요한 경우에만 "use client"를 명시하여 클아이언트 컴포넌트를 사용할 수 있습니다.
렌더링은 개별 route 세그먼트와 Suspense Boundary에 의해서 청크로 나눠집니다. route 세그먼트는 url에서 /로 구분됩니다. url이 /board/article이라면 board와 article 세그먼트로 나눠지는 것입니다.
각각의 청크는 두 단계로 렌더링이 진행됩니다.
1. 리액트는 서버 컴포넌트를 리액트 서버 컴포넌트 페이로드 (RSC Payload)라는 데이터 포맷으로 렌더링합니다.
2. Next는 서버 사이드에서 RSC Payload와 클라이언트에서 실행될 js 코드를 html로 렌더링합니다.
이후 클라이언트에서
1. 초기 페이지 로딩을 진행합니다.
2. RSC Payload는 클라이언트/서버 컴포넌트 트리를 조정하고 DOM을 업데이트 합니다.
3. js 코드는 클라이언트 컴포넌트를 hydrate(로딩)하여 어플리케이션이 상호작용 가능한 상태로 만듭니다.
정적 렌더링 (기본 동작)
빌드 타임이나 데이터 최신화 이후에 렌더링을 완료합니다. 렌더링 결과는 캐싱되고 CDN에 업로드할 수도 있습니다. 정적 렌더링은 정적 블로그 포스트와 같이 개인화된 데이터 로딩이 없을 때 사용할 수 있습니다.
동적 렌더링
사용자가 요청할 때 렌더링이 이뤄집니다. cookies(), headers(), useSearchParams()와 같은 동적인 함수나 캐싱되지 않는 데이터 fetch 작업이 있을 때 Next는 해당 route의 렌더링을 동적 렌더링으로 진행합니다.
스트리밍
loading.js를 사용하는 route 세그먼트와 Suspense를 사용하는 UI 컴포넌트로 점진적 UI 렌더링을 할 수 있습니다.
클라이언트 컴포넌트는 상태 관리, effect, 이벤트와 같은 작업을 처리할 수 있습니다. 또한 geolocation이나 localStorage와 같은 브라우저 API에 접근할 수 있습니다.
클라이언트 컴포넌트는 full page load인지 네비게이션에 의한 route 이동인지에 따라서 다른 방식으로 렌더링 됩니다. full page load는 브라우저를 켜서 사이트에 처음 방문한 경우 혹은 새로고침을 한 경우에 진행됩니다.
full page load의 렌더링 과정은 서버 컴포넌트에서 설명한 렌더링 과정과 동일합니다.
서버에서 렌더링한 html없이 클라이언트 컴포넌트 js 번들을 다운로드 받고 파싱합니다. 해당 번들이 준비되면 리액트는 RSC Payload와 결합해서 DOM을 업데이트 합니다.
써드파티 라이브러리의 컴포넌트를 사용하려고 할 때 해당 컴포넌트에는 "use client" 구문이 없을 것입니다. 그래서 이를 서버 컴포넌트에서 그대로 사용하려고 하면 에러가 발생하고 이를 고치려면 파일을 하나 만들어서 써드파티 라이브러리의 컴포넌트를 클라이언트 컴포넌트로 명시해줘야합니다.
'use client'
import { Carousel } from 'acme-carousel'
export default Carousel
모든 써드파티 라이브러리 컴포넌트르 위와 같은 방법으로 wrapping하려면 매우 귀찮은 작업이 될 것입니다. 클라이언트 컴포넌트 내부에서 import한다면 에러도 발생하지 않고 이와 같은 wrapping 작업을 하지 않아도 됩니다.
컨텍스트 프로바이더는 보통 어플리케이션의 루트에서 렌더링 됩니다. React Context는 서버 컴포넌트에서 지원하지 않기 때문에 그대로 사용하면 에러가 발생합니다. 따라서 이에 대한 대안으로 프로바이더를 클라이언트 컴포넌트로 wrapping할 수 있습니다.
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext({})
export default function ThemeProvider({ children }) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
클라이언트 컴포넌트 내부에서도 번들 사이즈를 줄이거나 데이터 fetch 작업을 하는 등 서버에서 실행하고 싶은 코드가 존재할 수 있습니다. 클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import할 수는 없습니다. 이럴 때는 클라이언트 컴포넌트에 props로 서버 컴포넌트를 넘겨줘야합니다.