react-helmet / react-snap 으로 메타데이터 최적화하기

최경락 (K_ROCK_)·2022년 7월 18일
7

react-helmet?

  • react-helmet 은 SPA의 메타데이터 설정을 돕는 라이브러리이다.
  • SPA의 경우 하나의 html 파일 내부에서 요소들을 렌더링하므로, 각각의 페이지에 맞는 메타데이터를 작성하기 힘들다는 단점이 있다.
    → CSR의 단점인 SEO 최적화 불리
  • react-helmet 을 이용하면 페이지 별 메타데이터를 설정 해 줄 수 있어, 위와 같은 단점을 보완 시킬 수 있다.

설치하기

npm install react-helmet-async

react-helmet-async ?
react-helmet-asyncreact-helmet을 개선한 라이브러리로, npm trend 를 참고했을때,
꾸준히 업데이트 되며, 사용자의 수 또한 기존 react-helmet 을 넘어섰으므로, 해당 라이브러리를 설치하여 사용합니다.

사용하기

  • 사용방법은 간단하다.
  • 사용하기 원하는 컴포넌트를 Provider 로 감싸고, Helmet 컴포넌트를 이용해서 메타데이터를 작성하면 된다.
// index.tsx

import React from 'react';
import { HelmetProvider } from 'react-helmet-async';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <HelmetProvider>
    <App />
  </HelmetProvider>
);
// App.tsx

import React from 'react';
import { Helmet } from 'react-helmet-async';

function App : JSX.Element () {
  <>
	  <Helmet>
      <meta property="og:site_name" content="사이트 이름" />
      <meta property="og:title" content="페이지 이름" />
      <meta property="og:url" content="사이트 주소" />
      <meta property="og:description" content="설명" />
      ...
	  </Helmet>
    {/* JSX 작성 */}
  </>
}
  • 이처럼 원하는 컴포넌트에 작성해주면 된다.
  • 만약 index.html 에 메타데이터가 이미 작성되어 있다면, 해당 내용은 삭제한다.
    → 중복작성 한 것이 되기 때문

메타데이터 관리하기

  • 여러 컴포넌트의 메타데이터를 관리할 때, 각 컴포넌트 마다 일일히 적어주는 것 보단, 하나의 컴포넌트에서 메타데이터를 관리하는 것이 더 편리해 보여 아래와 같이 작성해주었다.
// metadatas/metadatas.tsx

import React from 'react';
import { Helmet } from 'react-helmet-async';

export function Page1Metas(): JSX.Element {
  return (
    <Helmet>
      <meta property="og:site_name" content="사이트 이름" />
      <meta property="og:title" content="페이지 이름" />
      <meta property="og:url" content="사이트 주소" />
      <meta property="og:description" content="설명" />
    </Helmet>
  );
}

export function Page2Metas(): JSX.Element {
  return (
    <Helmet>
      <meta property="og:site_name" content="사이트 이름" />
      <meta property="og:title" content="페이지 이름" />
      <meta property="og:url" content="사이트 주소" />
      <meta property="og:description" content="설명" />
    </Helmet>
  );
}

export function Page3Metas(): JSX.Element {
  return (
    <Helmet>
      <meta property="og:site_name" content="사이트 이름" />
      <meta property="og:title" content="페이지 이름" />
      <meta property="og:url" content="사이트 주소" />
      <meta property="og:description" content="설명" />
    </Helmet>
  );
}

→ 각 메타데이터들을 Helmet 컴포넌트로 감싼 컴포넌트로 만들어 export 한다.

  • 위의 방법 외에도 하나의 템플릿을 만들어두고, props 를 이용하는 방법도 고려해 볼 수 있다.
// metadatas/metadatas.tsx

import React from 'react';
import { Helmet } from 'react-helmet-async';

interface IProps {
  siteName : string;
  title : string;
  siteUrl : string;
  desc : string;
}

export function Page1Metas({siteName, title, siteUrl, desc} : IProps): JSX.Element {
  return (
    <Helmet>
      <meta property="og:site_name" content={siteName} />
      <meta property="og:title" content={title} />
      <meta property="og:url" content={siteUrl} />
      <meta property="og:description" content={desc} />
    </Helmet>
  );
}

→ 뭐가 더 편한지는 본인 선택!

문제점

  • 메타데이터는 잘 변경되지만, 여전히 크롤러는 index.html 하나만 마주치게 되므로, og 가 정확하게 표시되지 않는 문제점이 발생한다.
    → 만약 목적이 단순히 메타태그 변경이라면 react-helmet-async 만 사용해도 무관 할 듯 하다.
  • 이 때 react-snap 을 이용하면 위의 문제를 해결 할 수 있다.

react-snap

  • 각 페이지 마다의 index.html 파일을 생성하고, 이를 크롤러에 제공 해주는 역할을 하는 라이브러리이다.

설치하기

npm install react-snap -D

package.json 수정

"scripts": {
    ...,
    "postbuild" : "react-snap"
  },
  • 위의 내용을 추가한다.
  • npm script의 문법으로, prebulild 는 빌드 전에 실행하고, postbuild 는 빌드 후에 실행된다.
"reactSnap": {
    "include": [
      // 프리렌더링 되기 원하는 path 를 작성한다.
    ],
    "exclued": [
      // 프리렌더링에서 제외할 path 를 작성한다.
    ]
  },
  • 기본적으로는 자동으로 root 부터 시작하여 모든 링크를 크롤링해주지만, 위의 설정을 통해 원하거나, 제외하고 싶은 링크를 설정 할 수 있다.

index.tsx 수정

// index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';

// 일부 생략

const container = document.getElementById('root') as HTMLElement;
const root = ReactDOM.createRoot(container);

if (container.hasChildNodes()) {
  ReactDOM.hydrateRoot(
    container,
    <HelmetProvider>
      <Provider store={store}>
        <Router>
          <App />
        </Router>
      </Provider>
    </HelmetProvider>
  );
} else {
  root.render(
    <HelmetProvider>
      <Provider store={store}>
        <Router>
          <App />
        </Router>
      </Provider>
    </HelmetProvider>
  );
}
  • hydrate 를 이용하기 위해 기존 index.tsx 작성내용에서 일부를 변경했다.
  • 참고한 블로그의 코드가 더 직관적이지만, 리액트 18버전 이상부터는 renderhydrate 가 각각 createRoot, hydrateRoot 로 변경되었다.
    → 둘이 전달받는 인자와 사용방법이 묘하게 달라 깔끔하게 작성하기가 힘들다…..

결과

  • 빌드가 끝나면 postbulid 에 따라 위의 사진처럼 파일 내부의 path를 자동으로 크롤링한다.
  • 이후 확인해보면, 정상적으로 og 가 표시되는 것을 볼 수 있다.

  • 다만, 곧바로 리다이렉트 시키는 경우, 해당 컴포넌트의 메타데이터가 아닌 리다이렉트 된 컴포넌트의 메타데이터가 표시되는 듯 하다.
    → 가장 아래의 og 카드

+

  • 현재 프로젝트는 간단한 CSR 이므로, SEO 가 크게 중요하지 않아 위의 라이브러리로 해결하는 방식을 선택했지만, 정말 SEO 가 중요하다면 SSR 방식의 Next.js 를 사용하는 것이 좋을 것 같다는 생각을 해본다.

참고 글
https://velog.io/@miyoni/noSSRyesSEO
https://velog.io/@byseop/SPA에서-서버사이드-렌더링을-구축하지-않고-SEO-최적화하기
https://www.nashu.dev/blogs/react-snap

1개의 댓글

comment-user-thumbnail
2022년 10월 27일

동작하나요...? 아무리해도 동적으로 된 url들이 안되네요...ㅠ
public에 있는 요소들만 가져오고 helmet으로 변경해준 태그 들은 변경이 안되네용

답글 달기