Next.js에서의 상태 관리와 서버-클라이언트 데이터 공유

Hanbin·2025년 5월 7일
1
post-thumbnail

Next.js의 App Router 기반 프로젝트에서 상태를 어떻게 관리하고,
서버와 클라이언트 사이에서 데이터를 어떻게 주고받는지를 정리해 보았습니다.


1. 서버 → 클라이언트로 데이터 전달

Next.js의 서버 컴포넌트에서 데이터를 fetch한 뒤,
클라이언트 컴포넌트에 props로 전달하여 상태로 전환.

// server component
async function Page() {
  const data = await getData(); // 서버에서 데이터 fetch
  return <Client data={data} />; // 클라이언트 컴포넌트로 전달
}


// client component
'use client';

export default function Client({ data }) {
  const [info, setInfo] = useState(data); // 상태화
  return <div>{info.title}</div>;
}

📌
서버에서 데이터를 먼저 가져오고, 클라이언트에서는 그 데이터를 상태로 만들어 반응형 UI를 구현

이때 반응형 UI는 화면 크기 대응이 아니라,
데이터 변화에 따라 자동으로 UI가 갱신되는 리액티브한 동작을 의미



2. Context API로 상태 공유

Context API는 여러 컴포넌트에서 전역 상태를 공유할 수 있게 해주는 React의 내장 기능

// context/UserContext.tsx
'use client';

import { createContext, useContext, useState } from 'react';

const UserContext = createContext(null);

export function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

export function useUser() {
  return useContext(UserContext);
}

컴포넌트에서 사용

'use client';
import { useUser } from '@/context/UserContext';

function Profile() {
  const { user } = useUser();
  return <p>{user ? `${user.name}님 환영합니다` : '로그인하세요'}</p>;
}

UserProvider로 감싸진 모든 자식 컴포넌트는 useUser()로 상태를 꺼내 쓸 수 있다.



3. 외부 상태 관리 라이브러리 (Zustand, Jotai)

1️⃣ Zustand

  • 단일 스토어에 여러 상태를 선언하고, hook처럼 사용
  • React Context 없이도 상태 공유 가능
// stores/useCounterStore.ts
import { create } from 'zustand';

export const useCounterStore = create((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
}));
// components/Counter.tsx
'use client';
import { useCounterStore } from '@/stores/useCounterStore';

function Counter() {
  const { count, increase } = useCounterStore();
  return <button onClick={increase}>Count: {count}</button>;
}

2️⃣ Jotai

  • 상태를 atom() 단위로 쪼개어 관리
  • useAtom()으로 사용, useState()와 비슷한 사용감
// atoms/counterAtom.ts
import { atom } from 'jotai';
export const counterAtom = atom(0);
// components/Counter.tsx
'use client';
import { useAtom } from 'jotai';
import { counterAtom } from '@/atoms/counterAtom';

function Counter() {
  const [count, setCount] = useAtom(counterAtom);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

✍🏻 Zustand vs Jotai 요약

항목ZustandJotai
상태 구조스토어 하나에 모아 관리atom 단위로 쪼개서 관리
사용법useStore()useAtom()
구조화명시적으로 store 구성파일 흩어짐 주의
러닝커브조금 있음useState랑 비슷
파생 상태직접 계산atom((get) => ...) 사용 가능


4. 서버-클라이언트 상태 동기화

서버에서 받아온 데이터를 클라이언트에서도 정확하게 유지해야 함
( 그렇지 않으면 hydration mismatch 발생 가능 )

대표 전략

전략설명
Props로 전달 후 useState가장 기본적인 방법
SWR/React Query클라이언트에서 fetch + 캐시로 상태 유지
ISR 사용페이지 자체를 일정 시간마다 서버에서 재생성

SWR 예시

import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());

function Profile() {
  const { data } = useSWR('/api/user', fetcher, {
    refreshInterval: 5000,
  });
  return <div>{data?.name}</div>;
}

✅ SWR은 브라우저에서 직접 데이터를 가져오고,
가져온 데이터를 캐시에 저장해 재사용하거나 일정 시간마다 새로고침할 수 있다.

SWR의 refreshInterval vs ISR의 revalidate

항목SWR refreshIntervalISR revalidate
동작 위치브라우저 (클라이언트)서버
대상데이터전체 HTML 페이지
사용법useSWR에서 설정서버 컴포넌트에 설정
대표 목적사용자 화면에서 실시간 데이터 반영정적 페이지를 주기적으로 최신화


정리 🔥

  • 서버 → 클라이언트로 데이터를 넘길 땐 props로 전달 후 상태화
  • 전역 상태는 Context, Zustand, Jotai 등으로 관리 가능
  • 상태가 많거나 자주 바뀌는 경우엔 SWR/React Query로 CSR + 캐시 관리
  • 서버-클라이언트 간 UI 깜빡임이나 mismatch 방지를 위해 상태 흐름을 잘 설계하자
profile
Software Engineer

0개의 댓글