팀에서 프로젝트를 진행하던 중 사용하고 있던 스타일링 라이브러리인 twin.macro가 Next.js 서버 컴포넌트에서 사용이 불가한 것을 확인하고, 해당 문제를 해결하면서 SSR , CSR , RSC , RCC 개념에 대해 제대로 짚고 넘어가보기로 했다.
우선 일반적인 클라이언트 사이드 렌더링에서는
CSS / JS 파일이나 이미지같은 파일의 크기가 클때 , 이런 모든 파일들이 모두 로드 될때까지
프론트 - 백엔드 API 요청이 계속해서 대기 상태에 머무르게 된다. 이 현상을 워터폴 현상이라 한다.
워터폴현상이 생길시에
- 로딩 지연: 각 요청이 이전 요청의 완료를 기다려야 하므로, 전체 페이지의 로딩 시간이 길어집니다.
- 사용자 경험 저하: 페이지의 주요 콘텐츠가 늦게 나타나거나, 사용자가 상호작용을 시도할 때 응답이 느릴 수 있습니다.
같은 문제가 일어나고, 서버 사이드 렌더링(SSR)은 워터폴 문제를 해결하는 데 도움이 될 수 있다.
SSR은 초기 페이지 로드 시 필요한 HTML, CSS, 데이터를 서버에서 미리 생성하여 클라이언트로 전송함으로써 , 클라이언트의 네트워크 요청 수를 줄이고 , 페이지 로드 시간을 단축해준다.
서버는 렌더링 엔진을 이용해 페이지를 렌더하고, 완성된 HTML을 브라우저에 응답한다. HTML뿐 아니라, 필요에 따라 js 파일 등의 추가 리소스도 포함될 수 있다.
이후 CSR와 마찬가지로 HTML을 해석하여 DOM을 생성하고 https://github.com/vercel/next.js/discussions/22276 을 수행한다.
그럼 SSR과 RSC는 어떻게 다를까?
RSCS는 서버에서만 렌더링되고 클라이언트로는 결과적인 HTML과 최소한의 JS가 전송된다 => Hydration이 일어나지 않는다!
// MongoDB 연결 함수
async function connectToMongoDB() {
const dbUri = "your-mongodb-uri"; // MongoDB URI
try {
await mongoose.connect(dbUri, { useNewUrlParser: true, useUnifiedTopology: true });
console.log('MongoDB connected');
} catch (error) {
console.error('Error connecting to MongoDB', error);
}
}
export default async function Home() {
// MongoDB 연결
await connectToMongoDB();
// Course 모델을 이용하여 모든 코스를 조회
const allCourses = await CourseModel.find();
// 서버 쪽에서 콘솔에 조회 결과를 출력
console.log({ allCourses });
return (
<main>
<div>
<CourseList allCourses={allCourses} />
</div>
</main>
);
}
또한 해당 예시 코드처럼 직접적으로 데이터베이스에 연결이 가능하고( Node.js 또는 Express처럼 ),
데이터베이스를 쿼리하여 렌더링을 위해 JSX에 전달할 데이터를 가져온다.
콘솔 로그 또한 서버 콘솔에 찍힌다.
하지만 , 서버 컴포넌트에는 사용자 인터렉션을 추가할 수 없다. 이벤트 핸들러나 useState, useReducer, useEffect와 같은 React 훅을 서버 컴포넌트에서 사용할 수 없다.
RSC와 SSR 비교 하기
1. 컴포넌트 실행 위치:
- SSR: 전체 애플리케이션 또는 특정 페이지를 서버에서 HTML로 렌더링하고, 이를 클라이언트로 전송합니다. 클라이언트에서는 JavaScript를 통해 페이지를 다시 활성화(hydration)합니다.
- RSC: 서버에서 컴포넌트를 실행하고, 결과적인 HTML과 최소한의 JavaScript만 클라이언트로 전송합니다. 클라이언트에서는 추가적인 JavaScript 실행 없이 서버에서 렌더링된 결과를 사용합니다.
2. 데이터 페칭:
- SSR: 서버에서 페이지를 렌더링할 때 필요한 모든 데이터를 미리 가져와야 합니다.
- RSC: 서버 컴포넌트는 필요에 따라 데이터를 페칭하고, 이 데이터를 포함한 상태로 컴포넌트를 렌더링합니다. 클라이언트는 데이터 페칭 로직을 거치지 않고, 서버에서 처리된 결과만 받습니다.
(서버에서 데이터를 직접 페칭하고, 그 데이터를 사용하여 컴포넌트를 렌더링한 후, 그 결과를 클라이언트로 전송)
SSR에서는 서버에 컴포넌트가 머물지 않지만 , RSC는 계속 서버에 머문다고 생각하자!
서버 컴포넌트가 렌더링 되는 방식
1. React는 서버 구성요소를 React Server Component Payload(RSC Payload) 라는 특수 데이터 형식으로 렌더링합니다 .
2. Next.js는 RSC 페이로드 및 클라이언트 구성 요소 JavaScript 지침을 사용하여 서버에서 HTML을 렌더링합니다.
그런 다음 클라이언트에서 다음을 수행합니다.
1. HTML은 경로의 빠른 비대화형 미리보기를 즉시 표시하는 데 사용됩니다. 이는 초기 페이지 로드에만 해당됩니다.
2. React Server Components Payload 는 클라이언트 및 서버 구성 요소 트리를 조정하고 DOM을 업데이트하는 데 사용됩니다.
3. JavaScript 는 클라이언트 구성 요소를 Hydrate하는데 사용됩니다. 이는 응용 프로그램을 interactive하게 만듭니다.
서버 컴포넌트는 클라이언트 사이드와 달리 DOM이나 CSSOM에 직접 접근할 수 없다. 서버 컴포넌트의 주된 역할은 HTML을 생성하고 초기 상태를 설정하는 것이며, 스타일 적용은 주로 클라이언트 사이드에서 처리된다.
이러한 이유로, twin.macro와 같은 브라우저에서 html dom요소에 css를 적용하는 도구는 서버 컴포넌트에서 사용하기에 적합하지 않으며, 주로 클라이언트 사이드에서의 스타일링에 활용된다.