🧩배경
Nextjs를 사용하면서 자연스레 서버컴포넌트를 사용해왔지만 따로 포스팅으로 정리하는 시간을 가지진 않아 각잡고 React 공홈에서 Server Component 파트를 읽고 정리해보는 시간을 가졌다.
🧩React 19 부터 stable
react 18v Server Component Part를 보면 Canary 표시가 되어있는데 19v부터는 사라졌고 stable이라고 언급되었다.
🧩소스코드로 비교
아래에는 React 19 공식 홈페이지에 올라온 예시코드로 같은 기능을 클라이언트 컴포넌트, 서버 컴포넌트 각각 이용해서 구현했을 때의 소스코드이다. 보면서 느끼겠지만 서버 컴포넌트가 훨씬 코드가 짧고 개인적으로 더 직관적이다. ( 물론 이외에도 여러 장점/단점이 있다. )
클라이언트 컴포넌트로 구현
`use client`
// bundle.js
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
function Page({page}) {
const [content, setContent] = useState('');
// NOTE: loads *after* first page render.
useEffect(() => {
fetch(`/api/content/${page}`).then((data) => {
setContent(data.content);
});
}, [page]);
return <div>{sanitizeHtml(marked(content))}</div>;
}
// api.js
app.get(`/api/content/:page`, async (req, res) => {
const page = req.params.page;
const content = await file.readFile(`${page}.md`);
res.send({content});
});
위 코드의 동작과정을 이해하고자 하기 전 클라이언트 컴포넌트는 빌드 시 모두 번들에 포함된다는 사실을 인지해야 한다, 즉 위에서 사용된 `marked` `sanitizeHtml` (대략 75K) 는 모두bundle에 포함된다. 사용자는 이 번들을 로드하여 페이지를 렌더링을 한 후 useEffect를 내부에서 `fetch(`/api/content/${page}`)`를 통해 api 서버로 요청을 하고 response가 오면 이 data를 setContent를 통해 content 에 반영하면서 재렌더링을 한다.
서버 컴포넌트로 구현
위에서 제시된 코드와 동일한 동작을 서버컴포넌트로 구현했을 때이다.
import marked from 'marked'; // Not included in bundle
import sanitizeHtml from 'sanitize-html'; // Not included in bundle
async function Page({page}) {
// NOTE: loads *during* render, when the app is built.
const content = await file.readFile(`${page}.md`);
return <div>{sanitizeHtml(marked(content))}</div>;
}
위 코드는 서버에서 렌더링 되고 렌더링 결과물은 HTML생성에 이용되고 이 HTML이 브라우저에 전달된다. 클라이언트 컴포넌트로 구현했을 시 75K용량의 `marked`와 `sanitize-html` 은 번들에 포함되고 이 js번들이 브라우저에 전달되었지만 서버컴포넌트의 경우 서버에서 직접 렌더링 후 이 결과를 HTML에 반영하기에 번들에 포함되어 브라우저에 전달 될 필요가 없고 실제로 그렇기에 번들의 크기를 줄여준다. 동시에 위 예시에서 클라이언트 컴포넌트와 다르게 렌더링 와중에 직접 서버 소스에 접근하여 ( ` const content = await file.readFile(`${page}.md`);` ) 해당 리소스를 이용한다.
🧩서버 컴포넌트 / 클라이언트 컴포넌트 작성방법
클라이언트 컴포넌트의 경우 파일 최상단에 `use client` 지시어를 사용하면 되고 서버 컴포넌트의 경우 별다른 지시어가 필요없다. 즉 별다른 지시어가 없다면 서버 컴포넌트라고 생각하면 된다. 참고로 `use server`는 서버 함수를 작성할 때 사용하는 지시어이다.
🧩서버 컴포넌트에는 interaction(사용자 상호작용)을 줄 수 없다.
서버컴포넌트에서 onClick, onFoucus 와 같은 intercation(사용자 상호작용)은 작동하지 않는다. 생각해보면 당연한 이야기이다. 애초에 서버컴포넌트는 클라이언트로 전달되지 않기 때문이다. 따라서 언급한 onClick, onFoucs와 같은 interaction을 사용해야 한다면 서버컴포넌트가 아닌 클라이언트 컴포넌트로 작성해야 한다.
마찬가지로 useState와 같은 hook또한 사용할 수 없다. 사용하기 위해서는 역시나 client 컴포넌트를 사용해야 한다. 아래 예시 코드를 보면 client component에서 `use client` 지시어가 없다면 서버컴포넌트가 되고 서버컴포넌트 내부에 사용이 불가한 `useState`, `onClick` 으로 인해 에러가 발생할 것이다.
// Client Component
"use client"
export default function Expandable({children}) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<button
onClick={() => setExpanded(!expanded)}
>
Toggle
</button>
{expanded && children}
</div>
)
}
🧩서버 컴포넌트를 이용하여 비동기 컴포넌트 사용 가능하다.
서버컴포넌트에서 `async` 와 `await` 를 이용하면 컴포넌트의 렌더링 준비가 될 때까지 컴포넌트의 렌더링을 지연 시킬 수 있다. 클라이언트 컴포넌트는 `async`, `await` 를 이용할 수 없다. 대신 클라이언트 컴포넌트에서는 비동기를 구현하기 위해 React 19에서 새로나온 `use`라는 기능을 이용할 수 있는 것 같은데 해당과 관련해서는 다음에 살펴봐야겠다.
예시코드
// Server Component
import db from './database';
async function Page({id}) {
// Will suspend the Server Component.
const note = await db.notes.get(id);
return (
<div>
{note}
</div>
);
}
코드를 보면 컴포넌트 앞에 `async`가 붙어있다. 이로써 내부에서 `await`를 사용 할 수 있다. `db.notes.get(id)`는 서버 자원에 접근하는 비동기 함수이며 앞에 `await`가 붙어 있으므로 이 Promise가 완료될때 까지 해당 컴포넌트의 렌더링은 지연된다.
🧩참고
https://react.dev/reference/rsc/server-components
Server Components – React
The library for web and native user interfaces
react.dev
https://ko.react.dev/reference/rsc/server-components
서버 컴포넌트 – React
The library for web and native user interfaces
ko.react.dev
Rendering: Server Components | Next.js
Learn how you can use React Server Components to render parts of your application on the server.
nextjs.org
'⭐FE' 카테고리의 다른 글
React19 useFormStauts Hook (0) | 2025.02.26 |
---|---|
React19 useActionState Hook 활용 예시 (0) | 2025.02.23 |
JavaScript 일반함수, 화살표 함수 this (0) | 2025.02.17 |
Lodash debounce 탐색 (0) | 2025.01.29 |
서버단에서 쿠키 설정 후 브라우저에서 확인해보기 (0) | 2025.01.05 |