Next.js 13의 SEO

Next.js 13버전에서는 App Router라는 개념이 추가되었고, 13.4 버전부터는 해당 기능이 안정화되었고 기본값으로 활성화되었다.

가장 크게 달라진 점은 아무래도 서버 컴포넌트가 생겼다는 점이다. 문제는 이 점 덕분에 다른 써드파티 패키지들에 이슈가 많아졌다. 꽤 큰 규모의 디자인 시스템인 chakra ui도 Next.js 13의 App Router를 쓰는 경우 모든 컴포넌트에 ‘use client’를 사용하여 클라이언트 컴포넌트로 강제해야 했다. (기본적으로 모든 컴포넌트는 서버 컴포넌트로 생성된다)

emotion 패키지도 마찬가지로 지원하지 않고, 지원하려면 굉장히 불편한 방법으로 작성해야 한다. (사실상 emotion을 사용하는 장점이 없어진다)

안타깝게도 현재 곧 오픈 예정인 서비스가 App Router를 사용했고 chakra ui를 사용했다. 당연히 emotion도 사용했다. 앞서 설명한 이슈는 전부 알고 있던 이슈였기 때문에 클라이언트 컴포넌트로 구성하고 개발이 마무리되고 있었다.

그런데 놓치고 있던 것이 바로 SEO였다. SEO는 무조건 서버에서 렌더링해야 하기 때문에 방법을 찾아야 했다. chakra ui에서 해당 이슈를 찾아보면 https://github.com/chakra-ui/chakra-ui/discussions/7822 이런 슬픈 내용만 볼 수 있다.

여러 삽질을 거치고 결국 해결했다.

1. <Head>를 사용하지 않는다.

import { Head } from "next/head";

...

<Head>
  <title>페이지 제목이에용<title>
  <meta name="og:title" content="페이지 제목이에용">
</Head>

원래라면 Head로 감싸서 사용했겠지만, 오히려 Head를 사용하니 클라이언트 컴포넌트에서 해당 영역이 무시되었다. 그래서 Head 없이 추가하게 되면 바로 페이지에 반영 되었다.

2. API 호출은 서버에서.

헤드에 들어갈 내용들은 서버 컴포넌트로 만들고 api를 async/await으로 호출하여 서버 컴포넌트로 만든다. SEO를 적용할 때에는 일반적으로 고정된 메타 데이터가 아닌, 콘텐츠를 담기 때문에 이러한 분리 작업이 필요하다.

3. Suspense를 활용한다.

헤드에 들어갈 내용이 컴포넌트에 적용되면 Next.js는 해당 페이지 컴포넌트를 다시 렌더링하게 된다. (몰랐던 사실이다..)
따라서 페이지에 처음 접속할 때 꽤 여러번 렌더링이 일어나게 된다. 그리고 서버 컴포넌트가 await으로 준비되기까지 대기가 필요하다. 따라서 리액트의 Suspense로 감싸서 글로벌 Loading을 추가하였다. 덕분에 페이지 전환시 약간의 대기가 필요하지만 현 상황에서는 가장 나은 선택이 되었다.

애초에 chakra ui를 사용하지 않고, Next.js 13의 App Router를 사용하지 않았다면, 페이지 컴포넌트에 근접한 컴포넌트는 서버 컴포넌트로 구성하고 인터렉션 등이 필요한 일부만 클라이언트 컴포넌트로 구성해서 구조를 만들면 좋았겠지만, 미숙한 탓에 첫 단추부터 잘못 채웠다.

조금 다르긴 하지만 Nuxt3에서도 비슷한 경험을 했던 것 같은데, 우선 오픈 후 내부적으로 계획 중인 피보팅 시점에 코드의 기술 스택을 어떻게 전환할지 고민해 봐야겠다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다