반응형
snowman95
코딩수련장
snowman95
전체 방문자
오늘
어제
  • 분류 전체보기 (229)
    • 앱테크 (3)
    • 옵시디언 (5)
    • 드라마, 영화 (1)
    • 개발자 이야기 (23)
    • 프로젝트 (10)
      • 프로젝트 방법론 (7)
      • 프로젝트 기록 (2)
      • Github (1)
    • 개발 지식 (0)
      • 디자인 패턴 (0)
    • 프론트엔드 개발 (5)
      • 테크트리 (2)
      • React.js (19)
      • ReactNative (2)
      • Next.js (6)
      • GraphQL (6)
      • 패키지 매니저 (2)
      • 라이브러리 (3)
      • 상태관리 라이브러리 (4)
      • Web 지식 (3)
      • HTML CSS (26)
      • Javascript (16)
      • 도구 (Tool) (3)
      • 성능 최적화 (1)
      • 디자인시스템 (0)
    • Python (53)
      • 모음집 (1)
      • 문법 (12)
      • 라이브러리 (15)
      • 알고리즘 (10)
      • 백준 문제풀이 (9)
      • 코딩테스트 (2)
      • 도구 (Tool) (3)
    • C++ (20)
      • 알고리즘 (6)
      • 삼성SW기출 (6)
      • 삼성 A형 (6)
    • 데이터사이언스 (1)
    • 인프라 (9)
      • 하드웨어 지식 (4)
      • Ansible (2)
      • Database (2)
      • 쉘스크립트 (1)
    • 주식 (0)
    • 취업 준비 (4)
      • 취업 이야기 (0)

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • 전공요약
  • Next.js #graphql #tailwind.css
  • 전공 요약 #운영체제
  • 삼성SW역량테스트
  • 전공 요약 #데이터베이스
  • 백준
  • GraphQL
  • 나의 해방일지
  • nextjs
  • 언어
  • A형
  • 삼성SDS
  • 면접
  • C++
  • 전공 요약 #네트워크
  • 오블완
  • 공간복잡도
  • 알고리즘
  • 티스토리챌린지
  • 기계식키보드 #nuphy

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
snowman95

코딩수련장

Next.js 의 ServerSide 에서 Apollo Client Cache 데이터가 모든 사용자 간에 공유되는 이슈
프론트엔드 개발/GraphQL

Next.js 의 ServerSide 에서 Apollo Client Cache 데이터가 모든 사용자 간에 공유되는 이슈

2023. 12. 19. 00:50
728x90
반응형

마이페이지에 다른 사람의 정보가 보이는 문제가 발생했습니다!!!

회사에서 개발 중인 사이트의 마이페이지는

사용자의 정보를 Next.js getServerSideProps 에서 Apollo Client Query 로 가져온 다음에

캐시 데이터를 클라이언트 Apollo Client 인스턴스의 캐시와 병합시키고,

그 캐시 데이터를 클라이언트 단에서 뽑아서 화면에 보여주는 방식으로 개발 했었는데요.

이것은 rehydreate 를 하는 여러 방법 중에 보편적으로 알려진 방식입니다.

 

 

대부분 Next.js + Apollo 궁시렁 검색하면 이렇게 나올겁니다.

ClientSide 에서 useQuery 사용시

  1. _app.tsx 에서 pageProps 를 useApollo hook에 전달
  2. useApollo 에서 initializeApollo() 함수 호출하는데 클라이언트에서는 하나의 인스턴스를 싱글톤 처럼 재사용 하게 함. 당연히 캐시 데이터도 재사용 됨
  3. 클라이언트에서 useQuery 사용하여 캐시 데이터 조회시 가지고 있던 cache 를 그대로 사용

getServerSideProps 에서 query 사용 후 ClientSide 에서 useQuery 사용시

  1. getServerSideProps 에서 query 호출후 캐시 데이터를 extract 하여 addApolloStateAndReturnPageProps() 함수에 전달
  2. 이 정보는 pageProps 의  __APOLLO_STATE__라는 이름의 서버사이드 cache 데이터로  _app.tsx 에서 pageProps 를 useApollo hook에 전달됨
  3. useApollo hook 에서는 __APOLLO_STATE__ 라는 이름의 서버사이드 cache 데이터가 있다면, 클라이언트의 ApolloClient 인스턴스에 전달
  4. 클라이언트의 ApolloClient 인스턴스의 cache 데이터와 서버사이드 cache 데이터와 병합 시킴
  5. 클라이언트에서 useQuery 사용하여 캐시 데이터 조회시 병합된 cache 데이터 가져와서 결론적으로는 서버에서 응답받은 캐시 데이터를 가져다가 사용 가능
import { useMemo } from 'react'
import {
  ApolloClient,
  InMemoryCache,
  type NormalizedCacheObject,
  from,
} from '@apollo/client'
import merge from 'deepmerge'
import isEqual from 'lodash/isEqual'
import { authLink, errorLink, terminatingLink } from './apolloLinks'

export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'

let apolloClient: ApolloClient<NormalizedCacheObject> | undefined

const cache = new InMemoryCache()

// * apolloClient를 만드는 함수
function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: from([authLink, errorLink, terminatingLink]),
    cache, // 이 cache 데이터가 서버에 그대로 유지되어서 모든 사용자 간에 공유되는 것 같음
    connectToDevTools: true,
  })
}

// * apolloClient를 초기화하는 함수
export function initializeApollo(
  initialState: NormalizedCacheObject | null = null,
) {
  // * let으로 선언한 apolloClient가 undefined라면 ( === initializeApollo 한번도 호출된 적 없다면 )
  // * createApolloClient()를 통해 새로운 apolloClient를 만들고 할당
  const _apolloClient = apolloClient ?? createApolloClient()

  // * initialState 있으면(cache를 넘겨받았다면) 기존 클라이언트 단의 apolloClient cache와
  // * 새롭게 넘겨받은 cache를 병합한다.
  if (initialState) {
    const existingCache = _apolloClient.extract()
    const data = merge(initialState, existingCache, {
      arrayMerge: (destinationArray, sourceArray) => [
        ...sourceArray,
        ...destinationArray.filter(d => sourceArray.every(s => !isEqual(d, s))),
      ],
    })

    _apolloClient.cache.restore(data)
  }
  // * in server 항상 새로운 인스턴스 만들어서 사용. 그러나 캐시는 재활용 되는 듯 함
  if (typeof window === 'undefined') return _apolloClient

  if (!apolloClient) apolloClient = _apolloClient

  return _apolloClient
}

export function addApolloStateAndReturnPageProps(
  client: ApolloClient<NormalizedCacheObject>,
  pageProps: any,
) {
  if (pageProps?.props) {
    pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
  }

  return pageProps
}

export function useApollo(pageProps: any) {
  const state = pageProps[APOLLO_STATE_PROP_NAME]
  const store = useMemo(() => initializeApollo(state), [state])
  return store
}

 

// _app.tsx

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  const apolloClient = useApollo(pageProps)

  return (
    <>
      <SessionProvider session={pageProps.session}>
        <ApolloProvider client={apolloClient}>
          <Component {...pageProps} />
        </ApolloProvider>
      </SessionProvider>
    </>
  )
}

 

// pages/파일명.tsx

export const getServerSideProps: GetServerSideProps = async context => {
  try {
    const headers = { authorization: `Bearer ${토큰}` }
    const piperGqlClient = initializeApollo()

    const queryMyPage = await piperGqlClient.query({
      query: QueryMyPageDocument,
      context: { headers },
      fetchPolicy: 'no-cache', // 캐시하게 되면 모든 사용자 간에 캐시가 공유됨 !!!
    })

    return addApolloStateAndReturnPageProps(piperGqlClient, {
      props: {
        queryMe: queryMyPage.data.queryMe,
      },
    })
  } catch (error) {
    ...
  }
}

 

Server-Side 에서 Apollo Client 캐시 데이터가 모든 사용자 간에 공유 되는 이슈

 

그런데 Server-Side 에서 Apollo Client 캐시 데이터가 모든 사용자 간에 공유가 된다는 매우 크리티컬한 사실을 오늘에서야 알게 되었습니다...!

아무리 검색하고 뒤져보아도 아무도 이런 문제를 이야기 해주지 않더군요...😢😢😢

 

개발 단계에서 찾아서 참 다행인데요..
사용자 마다 다르게 보여야 하는 정보가 모든 사용자 간에 공유가 되어버려서 정말 큰일이 날 뻔 했습니다.

 

Apollo Client fetchPolicy 의 기본 값은 cache-first 로 캐시값 먼저 확인하고, 없으면 서버에 요청하는 정책 입니다.

fetch-policy 를 no-cache 로 설정해서 캐시하지 않고 항상 네트워크 요청하도록 해서 해결했습니다.

반응형
저작자표시 비영리 동일조건 (새창열림)

'프론트엔드 개발 > GraphQL' 카테고리의 다른 글

Apollo Client 와 Relay 를 비교해보자  (1) 2024.10.01
Apollo Subscription 으로 GraphQL 서버와 WebSocket 통신하기  (0) 2023.04.17
React에서 Apollo Client 를 상태관리 라이브러리로 사용하기  (0) 2023.04.16
Apollo graphql 스키마와 리졸버에 대해 알아보자. 사용법  (0) 2023.02.12
Apollo 오디세이(ODYSSEY) graphql 강의  (0) 2023.02.11
    '프론트엔드 개발/GraphQL' 카테고리의 다른 글
    • Apollo Client 와 Relay 를 비교해보자
    • Apollo Subscription 으로 GraphQL 서버와 WebSocket 통신하기
    • React에서 Apollo Client 를 상태관리 라이브러리로 사용하기
    • Apollo graphql 스키마와 리졸버에 대해 알아보자. 사용법
    snowman95
    snowman95
    (17~19) Unity/Unreal Engine 게임 프로그래머 (20~21) System Administrator ___________ (22~) React 웹 프론트앤드 개발자 __________ 깃헙 : https://github.com/snowman95

    티스토리툴바