본문으로 바로가기
728x90

현 상황

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에서 스위칭하지 못해 에러를 발생하는거였습니다.

 

해결 방법

  1. 빌드 시간과 런타임 사이에 페이지의 정적/동적 모드가 달라질 수 있는 동적 서버 값의 조건부 사용을 방지합니다. 이렇게 하면 페이지 렌더링 모드의 일관성이 보장됩니다. -> 하지만 동적 서버 값이 필수적으로 사용되어야해서 스킵
  2. 페이지를 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