[nextjs] 동적 경로 SSG 페이지 만들기

pds·2023년 4월 14일
0

TIL

목록 보기
51/60
post-thumbnail

nextjs SSG 연습기록!


GetStaticPaths

When exporting a function called getStaticPaths from a page that uses Dynamic Routes, Next.js will statically pre-render all the paths specified by getStaticPaths.

Nextjs의 동적 경로 페이지를 SSG로 처리하는데 사용한다.

일반적으로 페이지에 필요한 동적 경로가 정적으로 알려져있을 때 사용한다.

export const getStaticPaths: GetStaticPaths = async () => {
  const response = await todoApi.getList();
  const paths = response.map((res) => {
    return { params: { id: res.id.toString() } };
  });
  return { paths, fallback: false };
};

export const getStaticProps: GetStaticProps = async (context) => {
  const params = context.params;
  if (!params?.id || typeof params.id !== "string") {
    return {
      notFound: true,
    };
  }
  const queryClient = generateQueryClient();
  await queryClient.prefetchQuery(["todo", params.id], () =>
    todoApi.getById(Number(params.id))
  );
  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
};

만약 페이지가 동적 경로여서 [id].tsx 같은 형태일 때 getServerSideProps를 통해 SSR은 적용할 수 있지만

getStaticProps로 SSG를 적용할 수는 없다.

런타임 때 수행되는 getServerSideProps와는 달리 getStaticProps는 빌드 시점에 수행되기 때문에 path나 querystring같은 request값을 context로 받을 수 없다.

// getStaticPropsContext
/**
 * Context object passed into `getStaticProps`.
 * @link https://nextjs.org/docs/api-reference/data-fetching/get-static-props#context-parameter
 */
export type GetStaticPropsContext<
  Params extends ParsedUrlQuery = ParsedUrlQuery,
  Preview extends PreviewData = PreviewData
> = {
  params?: Params
  preview?: boolean
  previewData?: Preview
  locale?: string
  locales?: string[]
  defaultLocale?: string
}
  
// getServerSidePropsContext  
/**
 * Context object passed into `getServerSideProps`.
 * @link https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props#context-parameter
 */
export type GetServerSidePropsContext<
  Params extends ParsedUrlQuery = ParsedUrlQuery,
  Preview extends PreviewData = PreviewData
> = {
  req: IncomingMessage & {
    cookies: NextApiRequestCookies
  }
  res: ServerResponse
  params?: Params
  query: ParsedUrlQuery
  preview?: boolean
  previewData?: Preview
  resolvedUrl: string
  locale?: string
  locales?: string[]
  defaultLocale?: string
}
  

getStaticPaths는

SSG를 적용할만한 페이지이나 동적으로 관리되는 페이지일 때 사용해 사전에 존재하는 동적 경로에 대해 수집하고 이를 SSG로 빌드 시점에 생성하게 해주는 역할을 한다.

다음과 같은 필수적인 리턴값을 가진다.

paths

미리 렌더링할 동적 경로를 결정한다.

paths 배열의 params 객체에 들어갈 값은 페이지 이름 예) [id].tsx 값과 일치해야 한다.

만약 pages/posts/[postId]/[commentId] 라는 경로라면 postIdcommentId가 존재해야 한다.

대소문자도 구별되니 동적 라우트 경로 이름과 완전히 일치해야 한다.

fallback

동적 경로가 아직 미리 생성되지 않았거나 존재하지 않는 경우 어떻게 처리할지 결정할 수 있다.

false[default]: 사전에 생성되지 않은 경로에 대한 요청은 404페이지를 반환한다.

true: 미리 생성되지 않은 경로의 경우에도 정적 페이지를 반환한다.

blocking: 미리 생성되지 않은 경로에 대해 SSR 페이지가 생성된다.

fallback이 상당히 중요해보이는 옵션 같다. 하나하나 연습해보기로 했다.

todo 목록에 대해 세가지 옵션 모두에 대한 페이지를 만드는데 getStaticPaths에서 id가 1,2일 때에 대해서만 사전에 정적으로 생성하도록 하고 새로 추가했을 때 어떤식으로 동작하나 확인해보았다.

export const getStaticPaths: GetStaticPaths = async () => {
  const paths = [{ params: { id: "1" } }, { params: { id: "2" } }];
  return { paths, fallback: ???? };
};

Fallback: False

사전에 빌드해서 생성한 페이지는 SSG로 보여주고 그 외의 요청에 대해서는 404페이지가 리턴된다.

getStaticPaths가 반환한 경로에 대해서만 보여지는 것이다.

언제 사용할까?

  • 동적 경로의 페이지이나 경로 수가 적고 거의 새로 생성될 일이 없을 때

새로운 페이지가 추가되어야 한다면 빌드를 다시 해야한다.


Fallback: True

4번째 데이터를 생성하고 첫 요청을 했을 때 클라이언트 사이드 렌더링이 일어나듯 깜빡임이 발생하는 것을 볼 수 있다.

이렇게 처음 접근했을 때 페이지 소스에도 해당 데이터가 없어 진짜 클라이언트 사이드 렌더링이라고 봐도 될 것 같다.


어떻게 동작할까

getStaticPaths에 반환된 path가 아닌 요청이 들어왔을 경우 404페이지를 보여주는 것이 아니라 fallback 버전을 먼저 제공한다.

const FallbackTrue = ({ id }: { id: string }) => {
  const router = useRouter();
  const { data: todo } = useQuery(["todo", id], () =>
    todoApi.getById(Number(id))
  );
  if (router.isFallback) {
    return (
      <div style={{ backgroundColor: "red" }}>
        <br />
        fallback...
        <br />
      </div>
    );
  }
  return (
    <div>
      <h3>FallBack-True</h3>
      {todo && (
        <div>
          {todo.id} {todo.name}
        </div>
      )}
    </div>
  );
};

하지만 next/link 또는 next/router를 통해 탐색되는 경우웹 크롤러에게는 fallback 버전을 제공하는 것이 아니라 후술할 fallback: blocking으로 동작한다.

next/link로 접근했더니 클라이언트 사이드에 만들어둔 fallback 버전을 사용하지 않고 렌더링 되는 것을 알 수 있다.

fallback: blocking은 SSR처럼 동작하는데 크롤러에도 이런 방식으로 동작한다고 하니 그렇다면 검색엔진최적화에는 전혀 문제가 없을 것으로 보인다.

이렇게 추가적으로 요청된 path에 대해 정적 파일을 만들고 해당 페이지가 한 번 만들어지면

기존에 빌드 시점에 사전 렌더링된 페이지 목록에 추가되고 SSG로 동작하게 된다.


언제 사용할까?

  • 데이터에 기반하여 생성될 동적 페이지가 너무 많을 때 일부만 사전 렌더링하고 fallback:true 설정 => 빌드시간을 아낄 수 있다.

동적으로 페이지를 생성할 수 있으면서도 결국은 정적 페이지 요청에 대한 응답으로 동작하기 때문에 사용자 경험 수준 향상에 매우 좋을 것 같다!

다만 한 번 생성된 페이지는 진짜 정적 페이지가 되는 것으로

변경이 있다고 알아서 바뀌는 것은 아니기 때문에 ISR적용은 따로 해주어야 할 것이라고 한다.


Fallback: Blocking

If fallback is 'blocking', new paths not returned by getStaticPaths will wait for the HTML to be generated, identical to SSR (hence why blocking), and then be cached for future requests so it only happens once per path.

fallback: true와 비슷하지만 응답 전 fallback 버전을 보여주는 것이 SSR을 수행해 완성된 HTML을 보여준다.

fallback: true와 비교하면 사용자의 관점에서 브라우저가 페이지를 요청하고 있습니다가 아니라 전체 페이지가 로드되었습니다의 상태를 얻는다고 한다.

fallback: true와 마찬가지로 최초 로드 시 사전 렌더링 페이지 목록에 추가되고 SSG로 동작한다.


내가 생각한 주의할 점

fallback: true 또는 fallback: blocking 을 사용할 때 실제 서버에 존재하지 않는 데이터의 경우라도 따로 404 처리를 하지 않기 때문에 빈 화면을 볼 수 있다.
따라서 404처리를 잘 해줘야할 것 같다.

fallback: false여도 next/routernext/link를 통해 접근했을 때 사전 렌더링 페이지가 아니더라도 404 처리가 되지 않았다.

정상적인 사용자가 routerlink를 통해 잘못된 경로를 들어갈 일이 없겠지만 없는 자원에 대해 빈 화면을 보여주는 것은 안 좋은 경험이 될 수 있을 것 같으니 주의해야 할 것 같다.

그리고 fallback: true 또는 fallback: blockingSSG 페이지에서 드물게 데이터 추가,삭제가 되는 경우 기존 데이터 만료를 잘 시켜야 할 것 같다.

기존에 없었던 페이지였기에 404를 리턴했다가 만약 추가된다면 계속해서 404를 보여줄 것이다.

반대로 기존에 존재했던 이벤트성 페이지가 이벤트가 끝나 사라진다고 가정했을때는 더 이상 보여지지 않아야 하는데 페이지가 보일 것 같다.

게시글 처럼 계속해서 추가되고 삭제되는 페이지라면 사용자에 의해 페이지가 변하기 on-demand-revalidation등을 적용해 갱신하겠지만

오히려 거의 변하지 않는 페이지의 데이터를 외부에서 변경해야하는 경우 문제가 있을 수도 있지 않을까 생각한다.

빌드를 다시하거나 변경에 대한 기존 데이터 만료를 잘 고려해야할 것 같다.


References

profile
강해지고 싶은 주니어 프론트엔드 개발자

0개의 댓글