Table of contents
리액트 서버 컴포넌트는 리액트 쿼리를 없애버리게 될까요? 이 질문은 몇 달간 제가 가장 많이 받은 질문일 것입니다. 사실 저도 잘 모르겠습니다. 업계의 대부분 개발자들이 그러하듯 저도 그냥 흘러가며 만들고 있다는 것을 기억해 주세요. 모든 것에 대한 원대한 계획이 제게 있을 거라고 기대하신다면 실망하실 겁니다. 저는 여러분과 같이 결말이 어떻게 될 것인지 궁금해하는 사람 중 한 명입니다. 😅
트윗: (데이터 요청 라이브러리의 유지관리자로서) 서버 컴포넌트와 서스펜스에 대해 두려움을 느끼고 있다는 것을 속이지 않겠습니다. "리액트 쿼리와 어떻게 작동될 것인가"는 좋은 질문입니다. 마치 답을 가지고 있어야 할 것 같지만 제게 답은 없습니다. 지금 거대한 가면 증후군에 걸린 느낌이네요.
그렇긴 하지만 이 주제에 대해 좀 더 자세히 볼 시간을 가지게 되었고 저보다도 이 주제에 대해 훨씬 많이 아는 사람들과도 논의를 해보았습니다. 이제는 이 주제에 대해 저의 의견을 드릴 수 있을 만큼 자신감이 생겼습니다. 하지만 이는 결국 단지 저의 생각일뿐이며 신중히 받아들이세요.
제 생각
우리가 사용하는 모든 툴은 우리가 겪고 있는 문제를 해결하는 데 도움을 주어야 합니다. 전통적으로 리액트는 애플리케이션에서 데이터를 어떻게 요청할 것인지에 대한 방식을 정해두지 않았습니다. 말 그대로 "useEffect
가 여기 있으니 원하는 방식대로 하세요"라고 하는 것처럼 신경을 쓰지 않았습니다.
이때 React Query나 swr이 탄생하게 되었습니다. 이들은 큰 틈을 메꿔주었고 뛰어난 개발 경험과 사용자들을 위한 개선으로 빠르게 채택되었습니다. React Router도 비슷하게 "뷰" 라이브러리가 당장 해줄 수 있는 게 없을 때 라우팅의 필요성을 해결해 주었습니다.
서버 사이드 렌더링이 유명해졌을 때 저희는 서버에서 html을 미리 렌더링하여 초기 페이지 로딩을 빠르게 하는 데 집중했습니다. 그러면 앱은 완전히 SPA처럼 동작합니다 (클라이언트 사이드 페이지 내비게이션 등). 이런 세계에서 리액트 쿼리도 중요한 역할을 합니다. 초기 데이터 요청을 서버로 옮긴 다음 요청 결과를 클라이언트에서 하이드레이트(hydrate) 할 수 있게 하죠. 이건 서버에서 가능한 한 빨리 캐시를 채우는 좋은 방법입니다.
그래서 뭐가 변했나요?
시대는 진화하고 있고 더 좋아지고 있습니다. 앞뒤로 왔다 갔다 흔들리는 것처럼 보이지만 실제로는 앞으로 나아가고 있습니다.
트윗: 저는 @Mappletons가 더 잘할 수 있다고 확신하는데 "개발자들은 왔다 갔다를 끊임없이 반복한다"는 댓글을 볼 때마다 제 머릿속에선 이런 게 보입니다."
리액트는 여전히 컴포넌트를 렌더링하는 라이브러리일 뿐이지만 서버 컴포넌트를 사용하면 서버에서 미리 렌더링할 수 있는 새로운 애플리케이션 아키텍처를 제공합니다. 클라이언트에서 질의해야 하는 API를 만들지 않아도 빌드 타임이나 런타임 시 데이터에 접근할 수 있게 해 줍니다.
export default async function Page() {
const data = await fetch(
`https://api.github.com/repos/tanstack/react-query`
)
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
</div>
)
}
여전히 리액트 컴포넌트 안에서 async/await
를 그냥 사용할 수 있다는 게 놀랍고 프레임워크가 문제를 포착해서 이에 대한 최고 수준의 해결책을 제공하는 걸 보는 게 흥미롭습니다. 이건 이 아키텍처를 채택하는 애플리케이션의 상황을 엄청나게 변화시킵니다. 리액트 쿼리는 무엇보다도 클라이언트에서 비동기 상태를 관리하기 위한 라이브러리입니다. 데이터 요청을 서버에서만 한다면 리액트 쿼리가 왜 필요할까요?
리액트 쿼리가 필요하지 않을 수도 있습니다
제 대답은 "필요 없을 수도 있다"는 것입니다. 새 애플리케이션을 시작하는데 Next.js나 Remix처럼 데이터 요청과 변형에 대해 좋은 이야기가 있는 발달된 프레임워크를 사용한다면 리액트 쿼리는 필요하지 않을 겁니다.
그리고 그건 정말 괜찮습니다. 제가 리액트 쿼리의 유지관리자라고 해서 모든 상황에 이를 사용하라고 하지는 않습니다. 리액트 쿼리를 사용하기로 했다면 문제 해결에 도움이 되기 때문이어야 합니다.
통합
서버 컴포넌트라는 새로운 세계엔 리액트 쿼리를 통합시킬 수 있는 부분들이 아직 많이 있습니다. 우선 대부분 프로젝트는 백지상태에서 시작하지 않습니다. 수년에 걸쳐 개발된 수많은 애플리케이션이 존재하고 점진적으로 app
디렉터리를 적용할 수는 있지만 서버 컴포넌트를 활용하려면 어느 정도의 재구성이 필요합니다.
이런 과도기에 리액트 쿼리는 app
디렉터리, 서버 컴포넌트와 아주 잘 통합됩니다. 일부 컴포넌트를 옮겨서 서버에서만 요청하도록 하거나 서버 컴포넌트로 캐시를 미리 요청한 뒤에 useQuery
를 사용하는 클라이언트 컴포넌트에 전달할 수도 있습니다. 모 아니면 도일 필요는 없습니다. 이미 공식 문서에 이런 통합에 대한 좋은 가이드가 있고 저는 주의해야 할 것들에 대해 블로그 글로 작성해 나갈 것입니다.
하이브리드 접근법
이 하이브리드 접근법은 서버 컴포넌트에서 (아직) 잘 지원되지 않는 사용 사례를 마주칠 때 특히 도움이 될 수 있습니다.
예를 들어 무한 스크롤되는 목록을 렌더링할 때 첫 페이지는 서버에게 미리 요청하고 사용자가 끝까지 스크롤했을 때는 클라이언트에서 더 많은 페이지를 가져오고 싶을 수 있습니다. 혹은 네트워크 연결 없이도 앱이 작동해야 한다는 요구 사항이 있을 수도 있습니다. 또 사용자의 명시적인 상호작용 없이도 항상 새로운 데이터를 볼 수 있는 사용자 경험을 원할 수도 있습니다 (일정 간격으로 데이터를 요청하거나 리액트 쿼리에서 제공하는 모든 자동 재요청(refetch)을 생각해 보세요).
리액트 쿼리는 이 모든 상황에 대한 훌륭한 이야기를 가지고 있어서 서버 컴포넌트와 결합하는 게 적합한 경우가 분명히 있습니다. 하지만 리액트 쿼리를 주로 데이터를 요청해서 사용자에게 보여주는 데 사용했다면 서버 컴포넌트만으로도 충분히 다룰 수 있을 겁니다. 그리고 데이터 변형 (서버 액션)이 확실히 자리를 잡으면 데이터를 업데이트할 때도 필요 없을 수 있습니다.
"해결사"는 아닙니다
이런 저런 이유로 모든 사람이 서버 컴포넌트를 채택하진 않을 거라는 것도 말이 된다고 생각합니다. 백엔드가 NodeJs로 작성되지 않았을 수도 있고 전용 서버가 없는 SPA 프론트엔드여도 괜찮습니다. 리액트 네이티브로 모바일 앱을 만들고 있을 수도 있습니다. TanStack Query 사용자라면 리액트를 아예 사용하지 않을 수도 있습니다.
게다가 데이터 요청 이외의 작업에 리액트 쿼리를 사용할 수 있습니다. 이 트윗에 달린 답을 보고 영감을 얻어 보세요.
트윗: 데이터 요청이 아닌 다른 용도로 TanStack Query를 사용하고 계신다면 어떤 것들이 있나요? 다른 사용 사례는 어떤 게 있는지 궁금합니다 😄
위의 모든 사용 사례는 클라이언트에서 비동기 상태 관리자로 쿼리(Query)를 선택하기에 완벽하게 좋은 사례들입니다. 하지만 이걸 최고 수준으로 지원하는 내장 기능이 있는 프레임워크를 선택하기로 했다면 그 기능을 사용하세요! 제 말은 리믹스를 사용하면서 로더(loader)에서 데이터를 요청하지 않을 이유가 있을까요? 🤷♂️
TanStack Query를 리액트 서버 컴포넌트 외부에서, 심지어는 결합해서도 사용하는 경우가 여전히 많을 거라고 예상합니다. 요즘 나오는 이야기들은 모두 RSC(리액트 서버 컴포넌트)에 관한 것들이지만 괜찮습니다. 모두가 사용해 보고 싶어 하는 새롭고 반짝이는 기술이니까요.
그래도 RSC(리액트 서버 컴포넌트)는 아직 초기 단계인 최첨단 기술입니다. 사용하려면 프레임워크, 라우터, 번들러와 긴밀하게 통합해야 합니다. 추가적인 서버 부하를 처리할 수 있는 인프라가 있어야 한다는 뜻이기도 합니다. 스스로 계속 반복하는 말이지만
그래서 저는 모든 것을 즉시 서버 컴포넌트로 이전시켜야 한다는 의무감을 느끼진 않습니다. Next.js 사용자로서 앱을 app
디렉터리로 옮기게 되어 기쁩니다. 중첩 라우트의 장점이 주된 이유입니다. 그리고 좀 더 정적인 데이터 요청 (예를 들어 staleTime: Infinity
를 사용하는 곳)은 분명히 서버 컴포넌트로 옮길 겁니다.
그럼에도 리액트 쿼리의 사망 신고는 엄청나게 과장된 것입니다.