소프트웨어/ReactRemix

Remix에서 클라이언트 상태관리는 어떻게 해야 할까?

테크와재테크 2025. 3. 30. 19:26

Remix는 서버 중심 아키텍처(Server-first) 를 철학으로 삼는 웹 프레임워크입니다.
데이터 패칭, 폼 처리, 전환 상태 관리까지 대부분 서버를 통해 처리하고, 클라이언트 코드는 가능한 한 얇게 유지하자는 접근이죠.

그렇다면 이런 Remix에서 클라이언트 상태는 어떻게 관리해야 할까요?
Redux, Zustand, React Query 같은 기존 상태관리 도구를 써야 할까요? 아니면 Remix만으로 충분할까요?

이 글에서는 Remix에서 클라이언트 상태를 다루는 3가지 범주로 나눠서 설명합니다:


1. 💾 UI 상태 (로컬 상태)

✅ 언제나 useState가 기본

UI 상의 일시적인 상태 — 예: 모달 열림 여부, 탭 선택, 인풋 값 등 — 은 기존 React와 동일하게 useState로 처리합니다.

const [isOpen, setIsOpen] = useState(false);

return (
  <>
    <button onClick={() => setIsOpen(true)}>열기</button>
    {isOpen && <Modal />}
  </>
);

Remix는 React 기반이므로 기본적인 컴포넌트 로컬 상태는 그대로 사용하면 됩니다.


 

2. 🔁 폼 상태와 요청 흐름

Remix의 진정한 힘은 서버와의 통신을 UI 상태처럼 제어할 수 있는 구조에 있습니다.

📦 useFetcher()로 낙관적 UI + 서버 상태 처리

const fetcher = useFetcher();

return (
  <fetcher.Form method="post" action="/api/toggle">
    <button type="submit">
      {fetcher.state === "submitting" ? "변경 중..." : "상태 변경"}
    </button>
  </fetcher.Form>
);
  • fetcher.state로 요청 상태 추적 가능
  • 폼 데이터를 서버로 보내면서도 전체 페이지 리렌더 없이 부분 전환 가능
  • 서버 응답 결과는 fetcher.data로 받음

Remix는 fetcher를 통해 서버 상태를 마치 클라이언트 상태처럼 다루게 해줍니다.


 

3. 🧠 전역 상태 (글로벌 상태 공유) 👇 사용하는 방식은 크게 2가지

3-1. Context API (Remix 친화적)

createContext와 useContext를 통해 전역 상태를 구성할 수 있습니다.
예: 테마 설정, 로그인 사용자 정보, 글로벌 알림 등

// context/theme.ts
const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);
// root.tsx
<ThemeProvider>
  <Outlet />
</ThemeProvider>

이 구조는 Remix의 중첩 라우팅(Outlet) 과도 잘 어울립니다.

 

3-2. 상태 관리 라이브러리 (Zustand, Redux 등)

  • 복잡한 전역 상태가 많거나, API 응답 캐싱/동기화가 필요하다면 사용
  • Remix는 SSR이므로 상태 초기화를 클라이언트에서 분리해야 함
  • 대부분의 라이브러리는 클라이언트 전용으로 useEffect 안에서만 동작하도록 제한

예: Zustand

import create from "zustand";

const useStore = create((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
}));

function Counter() {
  const { count, inc } = useStore();
  return <button onClick={inc}>{count}</button>;
}

Zustand는 SSR 환경에서도 안전하게 작동하며, Remix와 궁합이 좋은 상태 라이브러리 중 하나입니다.


 

4. 🧵 React Query? TanStack Query?

Remix는 React Query 같은 클라이언트 사이드 데이터 패칭 라이브러리와도 함께 사용할 수는 있지만, 권장되진 않습니다.

왜냐하면 Remix는 이미:

  • 서버에서 loader()로 데이터를 패칭하고
  • useLoaderData()로 클라이언트에 전달하므로
  • React Query의 역할을 중복할 필요가 없음

다만 대시보드나 비동기 UI가 많은 복잡한 SPA 구조에서는 사용할 수 있으며, 이때는 hydration + Suspense 구조를 잘 설계해야 합니다.


 

5. ☑️ Remix 스타일의 상태관리 철학 요약

상태 유형 권장 방식 설명

UI 상태 useState, useReducer 일반 React 방식 그대로 사용
서버 통신 결과 useFetcher, useLoaderData 클라이언트-서버 연결을 자연스럽게 처리
글로벌 상태 Context, Zustand 사용자 정보, 테마, 알림 등
복잡한 비동기 데이터 필요 시 React Query 데이터 동기화와 캐싱이 중요할 경우만

 

✅ 마무리: Remix에서의 상태 관리는 더 “웹스럽게”

Remix는 상태를 클라이언트에서 모두 관리하지 말고, 웹의 구조에 맞게 분산하자는 철학을 지향합니다.

 

즉:

  • 가능한 상태는 서버에서 관리하고
  • 필요한 경우에만 클라이언트에서 최소한으로 관리하되
  • fetcher, Form, useLoaderData 등을 활용해 상태 전환을 URL과 연결하자

이는 기존의 SPA 중심 상태관리 전략과는 다른 방향이지만,
결과적으로 더 예측 가능하고, 접근성(A11y)과 성능(Performance)에 유리한 구조로 이어집니다.