Client Components와 Server Components는 리액트 컴포넌트를 서버와 클라이언트 측에서 어떻게 처리하고 렌더링하는지에 대한 새로운 개념이다.
기본 리액트 컴포넌트
기본 리액트 앱에서는 모든 컴포넌트가 클라이언트 측에서 렌더링된다. 즉, 사용자의 브라우저에서 JS를 실행해서 UI를 구성한다는 의미이며, 이 방식은 상호작용이 많은 동적인 웹 애플리케이션에 적합하지만, 초기 로딩 시간이 길거나 SEO(검색 엔진 최적화)에 불리한 점이 있다.
서버 컴포넌트
서버 컴포넌트는 이름 그대로, 서버에서만 렌더링이되고, 최종 HTML 결과만 클라이언트로 전송된다. 즉, 상호작용이 없거나 병경되지 않는 UI를 작성할 때 이상적이라고 할 수 있다. 예를 들어, 데이터를 로드하고 표시하는 컴포넌트가 서버 컴포넌트로 작성될 수 있다.
서버 컴포넌트는 초기 로딩 시간이 감소되거나 더 나은 SEO를 제공하지만, 브라우저 API(예를 들어 window, document)에 접근할 수 없고, 상태나 생명주기 메서드도 사용할 수 없다는 점이 특징이다.
클라이언트 컴포넌트
클라이언트 컴포넌트는 전통적인 리액트 컴포넌트와 유사하며, 렌더링이 클라이언트 측에서 이루어진다. 사용자 상호작요이 필요한 부분에 적합하고, 상태 관리나 생명주기 메서드, 브라우저 API 접근 등 클라이언트 측에서 실행되어야 하는 모든 기능을 포함할 수 있다.
혼합 사용
Next js의 가장 큰 장점은 서버 컴포넌트와 클라이언트 컴포넌트를 혼합하여 사용할 수 있다는 것이다. 이를 통해 애플리케이션 성능을 최적화 하면서도, 사용자 경험을 개선시키는 두 가지 장점을 동시 활용이 가능하다. 예를들어, 초기 페이지 로드에 필요한 데이터를 렌더링하는 서버 컴포넌트와, 상호작용이 많은 부분을 처리하는 클라이언트 컴포넌트를 조합한다.
활용 예시
Json Server 사용
npx json-server --port 9999 --watch db.json
db.json
{
"topics": [
{"id": 1,"title": "html","body": "html is ..."},
{"id": 2,"title": "css","body": "css is ..."}
],
"posts": [
{
"id": 1,
"title": "json-server",
"author": "typicode"
}
],
"comments": [
{
"id": 1,
"body": "some comment",
"postId": 1
}
],
"profile": {
"name": "typicode"
}
}
아래는 json 서버를 활용하여 클라이언트 컴포넌트를 작성하는 기본적인 예이다.
기본적으로, Next JS는 컴포넌트를 서버 컴포넌트로 간주한다. 따라서 아래와 같이 useState를 사용하려면 "use client"를 명시해주어야하며, metadata는 서버 컴포넌트에서만 동작하므로, 사용할 수 없다.
Client Component
"use client"; // 클라이언트 컴포넌트를 사용하기 위한 import
import './globals.css'
import Link from "next/link";
import {useEffect, useState} from "react";
// export const metadata = {
// title: 'Web tutorials',
// description: 'Generated by jaysung',
// }
export default function RootLayout({ children }) {
const [topics, setTopics] = useState([]);
useEffect(() => {
/* 에러. nextjs는 기본적으로 컴포넌트를 Server 컴포넌트로 간주 */
fetch('http://localhost:9999/topics').then(resp => resp.json())
.then(result =>{
setTopics(result);
})
}, []);
return (
<html>
<body>
<h1>
<Link href="/">WEB</Link>
</h1>
<ol>
// 클라이언트 컴포넌트
{topics.map((topic) => {
return <li key={topic.id}><Link href={`/read/${topic.id}`}>{topic.title}</Link></li>
})}
</ol>
{children}
<ul>
<li>
{/* create라는 폴더 안에 있는 page.js 파일에 라우팅됨. */}
<Link href="/create">Create</Link>
</li>
<li>
<Link href="/update/1">Update</Link>
</li>
<li>
<input type="button" value="delete"/>
</li>
</ul>
</body>
</html>
)
}
Server Component
import Link from "next/link";
export const metadata = {
title: 'Web tutorials',
description: 'Generated by jaysung',
}
export default async function RootLayout({ children }) {
const resp = await fetch('http://localhost:9999/topics');
const topics = await resp.json();
return (
<html>
<body>
<h1>
<Link href="/">WEB</Link>
</h1>
<ol>
{topics.map((topic) => {
return <li key={topic.id}><Link href={`/read/${topic.id}`}>{topic.title}</Link></li>
})}
</ol>
{children}
<ul>
<li>
{/* create라는 폴더 안에 있는 page.js 파일에 라우팅됨. */}
<Link href="/create">Create</Link>
</li>
<li>
<Link href="/update/1">Update</Link>
</li>
<li>
<input type="button" value="delete"/>
</li>
</ul>
</body>
</html>
)
}
두 컴포넌트(클라이언트, 서버) 코드 간의 차이점 정리
구분 | 데이터 로드 | 실행 환경 | 렌더링 |
클라이언트 컴포넌트 | - useEffect와 useState를 사용하여, 클라이언트 측에서 비동기적으로 데이터를 불러온다. - 컴포넌트가 브라우저에서 실행되면, useEffect 내부의 fetch 함수가 호출되어 백엔드로부터 데이터를 가져온다. |
- 브라우저의 모든 기능(예: window, document 객체)를 사용할 수 있다. | - 동적 렌더링: 사용자 브라우저에서 컴포넌트가 로드될 때까지 내용이 나타나지 않는다. -따라서 초기 HTML에는 topic 데이터가 포함되지 않음.(SEO 불리) |
서버 컴포넌트 | - fetch 함수를 사용하여 서버 측에서 데이터를 동기적으로 불러온다. -이 함수는 서버에서 컴포넌트가 렌더링되기 전에 호출되어 데이터를 준비한다. |
- 컴포넌트가 서버에서 실행되므로, 클라이언트 측의 특정 기능 ( 예: window, document 객체 )은 사용할 수 없다. | - 정적 렌더링: 서버에서 컴포넌트가 렌더링되고, 최종 html이 클라이언트로 전송된다. - 초기 html에는 이미 데이터가 포함되어 있으므로, 페이지 로딩이 빠름(SEO 유리) |
'FrontEnd' 카테고리의 다른 글
이벤트 버블링(Event Bubbling) - 상위 요소로 이벤트 전파 (0) | 2024.02.28 |
---|---|
NextJS 데이터 최신화 설정, ISR과 캐싱 전략 (1) | 2024.01.07 |
axios의 .catch()와 .finally() 메서드 (0) | 2023.10.26 |
axios의 .then() 메서드 (2) | 2023.10.26 |
axios (AJAX) (0) | 2023.10.26 |