현 상황
Next.js 13(13.4) App Router를 통한 구성으로 React Server Component에서 Apollo Client를 쉽게 사용할 수 있는 @apollo/experimental-nextjs-app-support 를 채택했습니다.
Apollo Client의 캐시 덕분에 Client Component는 Mutation의 결과로 애플리케이션의 Entity의 모든 사용을 자동으로 업데이트 할 수 있지만 Server Component는 데이터가 변경되면 수동으로 업데이트 해줘야 합니다.
React Client Component
"use client";
import {
ApolloClient,
ApolloLink,
HttpLink,
} from "@apollo/client";
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";
function makeClient() {
const httpLink = new HttpLink({
// https://studio.apollographql.com/public/spacex-l4uc6p/
uri: "https://main--spacex-l4uc6p.apollographos.net/graphql",
});
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true,
}),
httpLink,
])
: httpLink,
});
}
export function ApolloWrapper({ children }: React.PropsWithChildren) {
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
}
React Server Component
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
export const { getClient } = registerApolloClient(() => {
return new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
// https://studio.apollographql.com/public/spacex-l4uc6p/
uri: "https://main--spacex-l4uc6p.apollographos.net/graphql",
// you can disable result caching here if you want to
// (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
// fetchOptions: { cache: "no-store" },
}),
});
});
요구 상황
일부 페이지에 권한을 검증하는 authLink 가 추가되어야 했고, authLink는 promise객체로 반환하여 server session에 접근하여 데이터를 가져와야 했습니다. 코드는 다음과 같습니다.
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
export const { getClient } = registerApolloClient(() => {
return new ApolloClient({
cache: new InMemoryCache(),
link: from([errorLink, authLink, httpLink]),
}),
});
});
const authLink = setContext((_, { headers }: { headers: Headers }) => {
return getServerSession(authOptions).then((session) => {
return {
headers: {
...headers,
...getAuthHeaders(),
},
};
});
});
개발 중에는 문제가 없다고 생각했지만, 빌드를 실행해보니 ApolloError: Dynamic server usage: Page couldn't be rendered statically because it used headers와 같은 에러가 발생되어 빌드를 하지 못했습니다.
app/ 디렉토리는 빌드 타임에 정적으로 생성되어야 하는데 cookies(), headers()와 같은 dynamic server 값들이 런타임에 시도하여 static과 dynamic을 next에서 스위칭하지 못해 에러를 발생하는거였습니다.
해결 방법
- 빌드 시간과 런타임 사이에 페이지의 정적/동적 모드가 달라질 수 있는 동적 서버 값의 조건부 사용을 방지합니다. 이렇게 하면 페이지 렌더링 모드의 일관성이 보장됩니다. -> 하지만 동적 서버 값이 필수적으로 사용되어야해서 스킵
- 페이지를 export dynamic 을 통해 조절하면 됩니다. (export const dynamic = 'force-static' 또는 'force-dynamic')
👍 Dynamic server usage: Page couldn't be rendered statically because it used headers 와 같은 에러로 인해 'force-dynamic'을 해당 페이지(React Server Component 내부 getClient를 활용하는 page.tsx) 에 작성하니 해결됐습니다.
https://nextjs.org/docs/messages/app-static-to-dynamic-error
Resolving "app/ Static to Dynamic Error" in Next.js
This document explains the "app/ Static to Dynamic Error" in Next.js and provides potential solutions to resolve it.
nextjs.org