✨회고

8/26 회고 - next.js에서의 초기 데이터 fetch + 무한 스크롤

2025. 9. 12. 15:38

이번에는 서버 컴포넌트에서 초기 데이터를 fetch하고, 클라이언트 컴포넌트에서 리액트 쿼리의 무한 스크롤 훅으로 추가 데이터를 가져오는 ✨하이브리드 렌더링 ✨ 을 구현해보았다.

 


🎯 next.js에서의 무한 스크롤

next.js에서는 리액트와 달리 무한 스크롤로 추가 데이터를 가져올 때 신경 써야 할 부분이 좀 있었다.
특히 app router 환경이라면 서버 컴포넌트의 데이터 페칭과 클라이언트 컴포넌트의 리액트 쿼리 사용을 잘 분리해서 구현해야 됐다.

문제

내가 원하는 데이터 페칭 및 무한 스크롤의 동작 흐름은 이러했다.

서버 컴포넌트에서 initialData fetch → 클라이언트 컴포넌트에서 initialData를 받아 렌더링 → 스크롤 시 클라이언트 컴포넌트에서 추가 데이터 렌더링

 

그래서 먼저 MainPage.tsx(서버 컴포넌트)에서 fetchServerData()로 initialData를 fetch 하고, 이를 ActivityList.tsx(클라이언트 컴포넌트)에 바로 보냈다.


그리고 ActivityList에 무한 스크롤 훅을 적용했는데, 훅이 호출되면 추가 데이터는 잘 불러왔지만 이상하게도 MainPage까지 리렌더링되면서 스크롤이 최상단으로 튀어버렸다.

 

무한 스크롤이 정상적으로 작동한다면 ActivityList에서 추가 데이터들만 렌더링 되고, 그외 요소들은 리렌더링되면 안되는데 왜 ActivityList를 넘어 MainPage까지 리렌더링 되었을까?

 

원인

🔎 원래 구조
MainPage (Server Component, async)
 └─ ActivityList (Client Component, useInfiniteQuery)
  • ActivityList 안에서 fetchNextPage() 호출 → React Query 상태 변경 발생
  • Client Component(ActivityList)에서 state가 변하면 React는 위쪽 Server Component(MainPage) 까지도 다시 렌더링 시도 (hydration mismatch 방지용)
  • 그 결과 MainPage → section → ActivityList 전체가 언마운트 후 다시 마운트
  • 당연히 DOM이 갈아엎어지니까 스크롤이 최상단으로 튀어버림 🌀

여기서 내가 놓친 부분은 'hydration mismatch'였다.

 

나는 서버 컴포넌트가 initialData를 내려주기만 하고, 리렌더링은 자식 클라이언트 컴포넌트에서만 일어날 줄 알았는데 hydration mismatch 방지 기능 때문에 부모 컴포넌트인 MainPage까지 리렌더링 된 것이다.

 

해결

🌟 변경 후 구조
MainPage (Server Component, async)
 └─ MainPageClient (Client Component)
     └─ ActivityList (렌더링 only)
  • MainPage는 이제 단순히 initialData만 내려주는 정적인 Server Component
  • 무한 스크롤과 React Query 상태 변경은 MainPageClient 내부에서만 일어남
  • React는 MainPageClient를 하나의 독립적인 Client 경계로 취급 → 상태 변경 시 MainPageClient 아래만 다시 렌더링
  • 따라서 MainPage(서버 부분)는 전혀 건드려지지 않고, section DOM도 유지 ✅
  • 결과: ActivityList UI만 업데이트 → 스크롤 위치 보존됨 🎉
  • 작업 과정:
    • 원래 서버 컴포넌트로만 존재했던 MainPage로부터 MainPage.client.tsx라는 별도의 클라이언트 컴포넌트를 생성.
    • 여기에 initialData를 전달하고, 해당 클라이언트 컴포넌트에서 useInfiniteQuery(리액트 쿼리 훅)와 useIntersectionObserver(무한 스크롤 커스텀 훅) 적용.
    • 기존에 initialData를 전달받던 ActivityList는 이제 MainPageClient로부터 무한 스크롤로 갱신된 data를 받아 '렌더링만 담당'.

마무리

리액트 쿼리의 무한 스크롤 기능과 서버/클라이언트 컴포넌트를 잘 활용하려면
MainPage (초기 데이터 fetch) - MainPageClient (리액트 쿼리 무한 스크롤) - ActivityList (리스트 렌더링)처럼
중간에 클라이언트 컴포넌트를 하나 더 두는 구조로 설계해야겠다.

 

 

'✨회고' 카테고리의 다른 글

9/1 회고 - zustand와 'use client'  (0) 2025.09.12
8/29 회고 - headless ui의 Menu vs ListBox | sharp 라이브러리의 용도에 관해  (0) 2025.09.12
8/23 회고 - debounce.ts (with TypeScript)  (0) 2025.09.12
자바스크립트 리팩토링 회고🔍 - 폼 유효성 검사  (0) 2025.05.01
'✨회고' 카테고리의 다른 글
  • 9/1 회고 - zustand와 'use client'
  • 8/29 회고 - headless ui의 Menu vs ListBox | sharp 라이브러리의 용도에 관해
  • 8/23 회고 - debounce.ts (with TypeScript)
  • 자바스크립트 리팩토링 회고🔍 - 폼 유효성 검사
쥬피썬더의노예
쥬피썬더의노예
오히려 좋아
  • 쥬피썬더의노예
    d.log
    쥬피썬더의노예
    글쓰기 관리
    • 분류 전체보기 (112)
      • JS (37)
      • TS (3)
      • WEB (10)
      • React.js (20)
      • Next.js (4)
      • tanstack query (2)
      • Node.js (2)
      • HTML (5)
      • CSS (13)
      • CS (1)
      • 에이전트 (1)
      • Git (4)
      • JAVA (0)
      • SQL (0)
      • db (0)
      • GSAP (0)
      • 자료구조 (1)
      • 알고리즘 (0)
      • ✨회고 (5)
      • 포꾸 (0)
      • 인터뷰 (0)
      • 개발일지 (0)
      • 일기 (1)
      • etc (3)
      • 정처기 실기 (0)
        • C (0)
        • Java (0)
        • Python (0)
      • fonts (0)
      • articles (0)
      • 도서 (0)
  • 인기 글

  • 태그

    유효성 검사
    리팩토링
    zustand
    CSR
    React
    TypeScript
    자바스크립트
    React.JS
    useState
    WEB
    클로저
    폼
    상태 관리
    javascript
    안티그래비티
    HTML
    조합 패턴
    state
    useEffect
    슬라이딩 윈도우
    아키텍처
    GIT
    SSG
    Next.js
    프론트엔드
    SSR
    css
    Til
    리액트
    React Query
  • 최근 글

  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.3
쥬피썬더의노예
8/26 회고 - next.js에서의 초기 데이터 fetch + 무한 스크롤
상단으로

티스토리툴바