서버 컴포넌트는 어떻게 사용해야 할까?
https://younghun123.tistory.com/2 - 서버 컴포넌트란?
안녕하세요! 지난 포스트에서는 서버 컴포넌트가 무엇인지 알아봤습니다!
서버 컴포넌트는 서버에서 "완성된 HTML"을 보내고 브라우저에서 JS 번들을 다운받지 않습니다.
[React Client Component 와 React Server Component]
그림에서도 알 수 있듯이 (서버컴포넌트에서)한번 화면에 HTML을 보여주면 JS번들 다운을 받지 않기 때문에 Create, Update, Delete 로직은 수행할 수 있다는 한계가 있습니다.
CQRS
사용자에게 조금이라도 쾌적한 서비스를 제공하고 싶다면, 서버 컴포넌트의 사용을 고민할텐데요.
"어떤 컴포넌트"를 서버 컴포넌트로 만들어야 할지에 대한 기준이 있다면, 그리고 그 기준이 관심사에 맞게 분리된다면 개발자와 동료들은 조금 더 안정적인 코드를 만들 수 있지 않을까요? 필자는 그 기준을 CQRS 패턴에 찾았습니다!
프론트엔드 개발자라면 CQRS 패턴이 생소할 수 있을 것 같은데요, CQRS는 Command Query Responsibility Segregation의 약자입니다. 말그대로 명령과 조회를 기준으로 책임을 나누는 패턴입니다.
그림과 같이 DATA를 공급하는 객체안에서 조회와 책임을 기준으로 모델을 나눕니다.
조회와 명령이라는 책임을 기준으로 객체를 나눈 다는 것은 세가지 의미를 가집니다.
1. clarity
분명한 책임을 기준으로 하나의 객체를 만들 수 있습니다.
분명한 책임을 갖는 객체들로 이루어진 소프트웨어는 협업, 안정성, 생산성 측면에서 효율적입니다.
2. performance
객체가 하나의 일만 처리한 다는 것은 소프트웨어의 성능향상으로 이어집니다.
3. scalability
"단일 책임원칙"을 갖는 객체는 확장성과 유지보수에 유연합니다.
CQRS with View Component
필자는 클라이언트 컴포넌트와 서버 컴포넌트를 나누는 기준을 CQRS 패턴에 영감을 받았습니다.
우리는 데이터를 패칭하는 역할을 수행하는 "쿼리 컴포넌트", 생성 삭제 수정을 담당하는 "커맨드 컴포넌트"로 분리할 수 있습니다.
1. Query Component
리액트 서버 컴포넌트 객체로 만듭니다. 데이터를 불러와 사용자가 조회한 데이터를 보여주는 객체입니다.
사용자는 원하는 데이터와 UI를 빠르게 볼 수 있습니다. 단순히 데이터를 받아서 보여주는 컴포넌트를 작성해봅시다.
function async ViewDateComp(){
const response = await fetchData();
return (<div>{response.data}<div>)
}
해당 컴포넌트에서 사용자는 데이터를 보기만 할 뿐, 다른 액션은 불필요합니다. 따라서 서버 컴포넌트로 작성하는 것이 바람직합니다.
2. Command Component
아래 게시물을 생성하는 비즈니스 함수를 작성해보겠습니다. Next JS 서버에서 직접 DB에 쿼리 요청을 보내는 서버 액션 함수입니다.
"use server";
import { db } from "@/lib/db";
export async function createPost(title: string) {
await db.post.create({
data: { title },
});
}
커맨드 컴포넌트에서는 리액트 서버 컴포넌트와, 클라이언트 컴포넌트 중 선택할 수 있습니다.
개발자가 둘을 고르는 기준은 클라이언트(브라우저)에서 JS 번들이 필요한지 유무입니다. 사용자에게 즉각적인 UI를 제공해야 한다면 React Hook을 적절히 사용해야합니다. 해당 경우에는 클라이언트 컴포넌트를 사용하는 것이 적절합니다. useState, useEffect 등 다양한 리액트 훅을 사용해 사용자 액션에 즉각 반응하는 UI를 만들 수 있습니다.
다음 컴포넌트가 수행하는 역할은 다음과 같습니다.
1. useRef를 통해 input tag의 DOM 요소에 접근합니다.
2. useState를 통해 제출 여부에 대한 상태를 관리합니다.
3. useEffect()를 통해 제출 여부에 따라 경고창을 띄어줍니다.
(해당 컴포넌트는 물론 useState와 useEffect가 없어도 Promise.then()처리를 통해 경고창을 띄울 수 있지만 다양한 React Hook의 사용을 보여주기 위한 예시입니다! ^^)
// 클라이언트 컴포넌트
"use client";
function CreateDataComp() {
const titleRef = useRef<HTMLInputElement>(null);
const [isSubmit, setIsSubmit] = useState<boolean>(false);
useEffect(()=>{
if(!isSubmit) return;
alert("제출되었습니다.");
}, [isSubmit])
return (
<div>
<input type="text" ref={title} />
<button onClick={
async()=>{createPost(titleRef.current?.value as string).then(
(data.success)=>{setIsSubmit(false)}
)}
}>
추가
</button>
<div>
);
}
반면 개발자가 컴포넌트를 작성하는데 반응적인 UI를 제공할 필요가 없다고 생각한다면 HTML의 form을 활용해 서버 컴포넌트를 만들 수 있습니다. 해당 경우 또한 서버 액션을 활용한다면 더 깔끔한 코드를 작성할 수 있습니다.
function CreateDataComp() {
return (
<form action={createPost}>
<input type="text" name="title" placeholder="제목 입력" required />
<button type="submit">추가</button>
</form>
);
}
이렇듯 조회와 명령을 기준으로 컴포넌트를 분리해 개발한다면 확장성에 염두할 수 있습니다.
개발 중 특정 컴포넌트를 RSC로 작성하고 있다고 가정합니다. 비즈니스 담당자와 디자이너가 해당 부분의 UI의 변경을 요청한다면? 그리고 만일 그 부분이 즉각적인 반응을 요구한다면? 개발자는 리액트 훅을 사용해야합니다. 만일 컴포넌트가 역할대로 체계적으로 분리되어 있다면 개발자는 커맨드 컴포넌트에서 해당하는 부분만 RCC로 변경하면 되는겁니다!
💡
눈치 채셨겠지만 CQRS를 적극적으로 활용하기 위해서 "비즈니스와 뷰 관심사 분리"가 전제되어야 합니다. 위 예제에서는 DB에서 데이터를 받아와 비즈니스 로직을 수행하는 함수(객체)와, 렌더링을 담당하는 함수(리액트 컴포넌트 객체)를 분리합니다. 다음 게시물에서는 비즈니스와 뷰 관심사의 분리라는 주제로 포스팅하겠습니다!
참조
https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs
'프론트엔드' 카테고리의 다른 글
프론트엔드 성능 최적화 도전기 (0) | 2025.04.14 |
---|---|
[Next JS] Next JS 배포하자! - 2 (feat Docker, AWS EC2) (0) | 2025.04.05 |
[Next JS] Next JS 배포하자! - 1 (feat Docker, AWS EC2) (0) | 2025.04.04 |
[Next JS] 슬기롭게 서버컴포넌트 사용하기 3 - 아키텍처 (0) | 2025.03.20 |
[Next JS] 슬기롭게 서버 컴포넌트 사용하기 1 - 서버 컴포넌트란? (0) | 2025.02.20 |