반응형
해당글은 2021년 5월 21일에 작성되어 레거시한 내용이 포함되어 있습니다.
설치
Apollo Client 및 GraphQL 종속성을 추가합니다.
yarn add @apollo/client graphql subscriptions-transport-ws
lib/apolloClient.js 파일 생성
Apollo 클라이언트 설정을 저장할 lib/apolloClient.js 파일을 생성합니다.
import { ApolloClient, HttpLink, InMemoryCache, createHttpLink, split } from "@apollo/client";
import { concatPagination, getMainDefinition, offsetLimitPagination } from "@apollo/client/utilities";
import { WebSocketLink } from "@apollo/client/link/ws";
import isEqual from "lodash/isEqual";
import merge from "deepmerge";
import { setContext } from "@apollo/client/link/context";
import { useMemo } from "react";
let apolloClient;
const httpLink = createHttpLink({
uri: process.env.GRAPHQL_SERVER_URL,
credentials: "same-origin",
});
const wsLink = process.browser
? new WebSocketLink({
// if you instantiate in the server, the error will be thrown
uri: process.env.GRAPHQL_SOCKET_URL,
options: {
reconnect: true,
connectionParams: {
accessToken: process.env.COMMON_TOKEN,
},
},
})
: null;
const splitLink = process.browser
? split(
//only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
)
: httpLink;
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
// const token = localStorage.getItem("token");
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
"x-access-token": process.env.COMMON_TOKEN,
// "x-access-token": token ? token : "",
},
};
});
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: authLink.concat(splitLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
comments: offsetLimitPagination(["articleId"]),
},
},
},
}),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) => sourceArray.every((s) => !isEqual(d, s))),
],
});
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
- createApolloClient()는 Apollo Client 인스턴스를 반환하는 기본 함수입니다. ssrMode는 페이지가 SSR (서버 측 렌더링)을 사용하여 사전 렌더링 될 때 true이고 클라이언트에서 렌더링 될 때 false 이어야 합니다. 이를 수행하기 위해 윈도우 객체 타입을 비교하여 서버인지 클라이언트인지 유동적으로 확인을 합니다.
- authLink는 Apollo Client Header에 x-access-token와 같은 임의의 인증 토근을 삽입하기 위해 Header를 커스텀합니다. splitLink는 Http Link와 WebSocket Link를 효율적으로 사용하기 위해 두 개의 다른 링크 중 하나를 사용할 수 있는 splice()을 제공합니다. process.browser 는 next js에서 클라이언트 요청일 때만 splice()가 실행되도록 합니다. splitLink 안의 httpLink와 wsLink는 http와 websocket의 GraphQL 엔드 포인트를 설정합니다. 그리고 authLink와 splitLink를 concat으로 연결하여 하나의 link객체를 생성합니다.
- cache는 InMemoryCache 객체를 활용하여 원하는 필드의 데이터들을 캐싱으로 관리할 수 있습니다. 이것을 잘만 활용하면 상태값을 전역으로 관리할 수 있기 때문에 다른 상태 관리 라이브러리인 Redux나 MobX 같은 것을 사용하지 않아도 됩니다.
- createApolloClient 함수를 호출하는 initializeApollo 함수가 있습니다. initializeApollo()는 다른 페이지에 대한 새 인스턴스를 생성하지 않고 Apollo 캐시를 초기 상태와 병합합니다. 새 클라이언트가 없으면 Apollo 캐시를 initializeApollo에 전달되는 Apollo 캐시 값인 initialState (null이 아닌 경우)와 병합합니다.
- useApollo()는 initialState값이 변경된 경우에만 Apollo 클라이언트 인스턴스를 업데이트하기 위해 useMemo 훅을 사용합니다.
ApolloProvider 추가
이제 pages/_app.js에서 useApollo() 사용하여 Apollo Client 인스턴스를 다른 페이지로 전달합니다. 이것은 각 페이지에 대한 pageProps의 initialApolloState를 가져옵니다.
import { ApolloProvider } from "@apollo/client";
import { useApollo } from "../lib/apolloClient";
export default function App({ Component, pageProps }) {
const apolloClient = useApollo(pageProps.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
<div style={{ margin: "20px" }}>
<Component {...pageProps} />
</div>
</ApolloProvider>
);
서버사이드 렌더링 처리
initialApolloState는 next js의 getStaticProps나 getServerSideProps에서 미리 데이터를 호출하면 캐싱된 데이터를 추출하여 initialApolloState props에 넣어줍니다.
export async function getStaticProps({ params }) {
const apolloClient = initializeApollo();
const { data } = await apolloClient.query({
query: ARTICLE,
variables: { id: parseInt(params.idx) },
});
const article = data.article;
return {
props: {
article,
initialApolloState: apolloClient.cache.extract(),
},
// revalidate: 1,
};
}
이렇게 initialApolloState를 ApolloProvider에 넣어주면 캐싱된 데이터가 전역으로 관리되기 때문에 동일한 쿼리의 필드를 요청하면 캐싱되어 있는지 확인하고 캐싱되어 있다면 캐싱된 필드를 서버에 요청하지 않고 바로 반환합니다.
참고
반응형
'기억보단 기록을 > Next JS (App Router)' 카테고리의 다른 글
[NextJS 13] Getting Started - app Routing Project Structure(앱 라우팅 프로젝트 구조) (0) | 2023.05.24 |
---|---|
[NextJS 13] Getting Started - Installation(설치 방법) (0) | 2023.05.23 |
[NextJS 13] Getting Started - 소개 (0) | 2023.05.23 |
[Next JS] SSG와 SSR (0) | 2023.05.17 |
[Next JS] <Image> 정리 (0) | 2023.05.17 |