728x90
반응형
마이페이지에 다른 사람의 정보가 보이는 문제가 발생했습니다!!!
회사에서 개발 중인 사이트의 마이페이지는
사용자의 정보를 Next.js getServerSideProps 에서 Apollo Client Query 로 가져온 다음에
캐시 데이터를 클라이언트 Apollo Client 인스턴스의 캐시와 병합시키고,
그 캐시 데이터를 클라이언트 단에서 뽑아서 화면에 보여주는 방식으로 개발 했었는데요.
이것은 rehydreate 를 하는 여러 방법 중에 보편적으로 알려진 방식입니다.
대부분 Next.js + Apollo 궁시렁 검색하면 이렇게 나올겁니다.
ClientSide 에서 useQuery 사용시
- _app.tsx 에서 pageProps 를 useApollo hook에 전달
- useApollo 에서 initializeApollo() 함수 호출하는데 클라이언트에서는 하나의 인스턴스를 싱글톤 처럼 재사용 하게 함. 당연히 캐시 데이터도 재사용 됨
- 클라이언트에서 useQuery 사용하여 캐시 데이터 조회시 가지고 있던 cache 를 그대로 사용
getServerSideProps 에서 query 사용 후 ClientSide 에서 useQuery 사용시
- getServerSideProps 에서 query 호출후 캐시 데이터를 extract 하여 addApolloStateAndReturnPageProps() 함수에 전달
- 이 정보는 pageProps 의 __APOLLO_STATE__라는 이름의 서버사이드 cache 데이터로 _app.tsx 에서 pageProps 를 useApollo hook에 전달됨
- useApollo hook 에서는 __APOLLO_STATE__ 라는 이름의 서버사이드 cache 데이터가 있다면, 클라이언트의 ApolloClient 인스턴스에 전달
- 클라이언트의 ApolloClient 인스턴스의 cache 데이터와 서버사이드 cache 데이터와 병합 시킴
- 클라이언트에서 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 |