React dynamic meta tag 설정해보기

이지·2024년 3월 20일
0
post-thumbnail

요즘 Next.js 강의를 듣는 중인데 동적 meta tag를 설정하는 방법에 대한 내용이 있었습니다.

생각해보니 다른 웹 사이트를 이용할 때 쇼핑의 경우 상품 페이지를 공유하면 어떤 상품인지 드러나는 링크를 공유하게 되는데

현재 제 React 프로젝트의 경우 index.html 정적 페이지 하나에서만 meta tag를 설정하고 있기 때문에 상품링크를 공유해도 상품에 맞는 og 태그가 나타나지 않겠구나라고 깨닫게 되었습니다.

그래서 리액트에 동적 meta tag를 설정할 수 있는 react-helmet-async 를 사용하는 방법과 페이지를 pre-rendering할 수 있는 react-snap에 대해 알아보고 직접 프로젝트에 적용해보았습니다!

react-helmet-async

  • React에서 동적 meta tag를 설정할 수 있는 라이브러리

react-helmet과 react-helmet-async의 차이점
react-helmet-async
This package is a fork of React Helmet. usage is synonymous, but server and client now requires to encapsulate state per request.

react-helmet relies on react-side-effect, which is not thread-safe. If you are doing anything asynchronous on the server, you need Helmet to encapsulate data on a per-request basis, this package does just that.

react-helmet-async는 react-helmet를 기반으로 만들어진 라이브러리로 react-helmet의 주요 기능을 모두 포함하면서 동시에 비동기 방식으로 작동합니다.
또한, react-helmet과 다르게 react-side-effect에 의존하지 않고 안전한 비동기 처리를 지원합니다.
서버에서 비동기식 작업을 수행하는 경우 요청별로 데이터를 캡슐화하는 것을 react-helmet-async에서 지원한다는 의미입니다.

설치

npm i react-helmet-async

사용법

  • <App />HelmetProvider로 감싸줍니다.

index.tsx

  import { HelmetProvider } from "react-helmet-async";

  const container = document.getElementById("root")!;
  const root = createRoot(container);
  root.render(
    <BrowserRouter>
      <RecoilRoot>
        <ScrollToTop />
        <HelmetProvider>
          <App />
        </HelmetProvider>
      </RecoilRoot>
    </BrowserRouter>
  );
  • 사용하고자 하는 컴포넌트에서 <Helmet>안에서 title, meta tag 등을 작성해주면됩니다.
// example
<Helmet>
	<title>My Title</title>
	<meta name="description" content="Helmet application" />
</Helmet>

저의 경우 쇼핑몰 페이지이기 때문에 상품페이지에 대한 설명을 props로 전달받기 위해 MetaTag.tsx를 따로 생성하여 필요한 페이지에서 불러와 사용하는 방식을 선택했습니다.

MetaTag.tsx

import { Helmet } from "react-helmet-async";

interface MetaTagProps {
  title?: string;
  description?: string;
  imgSrc?: string;
  url?: string;
}

const DEPLOY_URL = "배포 URL";

export default function MetaTag(props: MetaTagProps) {
  const url = props.url ? `${DEPLOY_URL}${props.url}` : DEPLOY_URL;

  return (
    <Helmet>
      <title>{(props.title && `default title - ${props.title}`) || "default title"}</title>
      <meta name="description" content={props.description || "default description"}/>
      <meta property="og:type" content="website" />
      <meta property="og:title" content={props.title || "default title"} />
      <meta property="og:site_name" content="default title" />
      <meta property="og:description" content={props.description || "default description"} />
      <meta property="og:image" content={props.imgSrc || "default image"} />
      <meta property="og:url" content={url} />
    </Helmet>
  );
}

Default로 적용하기위해서 App에서 MetaTag를 사용하였습니다.

App.tsx

  import MetaTag from "./components/common/MetaTag";
  
  function App() {
  return (
    <Wrap>
      <MetaTag />
      <GlobalStyle />
      <Layout>
        <Routers />
        <ModalContainer>
          <Modals />
        </ModalContainer>
      </Layout>
      <ToastContainer transition={Zoom} />
    </Wrap>
  );
}
export default App;

ProductDetail에서 적용한 모습

ProductDetail.tsx

export default function ProductDetail(){
  ...
  return (
  	<MetaTag title={product_name} description={product_info} imgSrc={image} url={path} />
  ...
  )
}



잘 적용되고 있는 모습을 확인할 수 있습니다.

하지만 여전히 크롤러는 index.html 하나만 탐색하기 때문에 SNS 공유를 하게 되면 바뀐 og 태그가 제대로 적용되지 않고 있습니다.

이를 해결하기 위해 react-snap 을 사용할 수 있습니다.

react-snap

  • react-snap은 앱의 각 페이지를 사전에 렌더링해줍니다.

설치

npm i react-snap

사용법

index.tsx를 다음과 같이 변경해주면됩니다.
index.tsx

import ReactDOM from "react-dom/client";
//..생략..
  
const container = document.getElementById("root")!;
const root = createRoot(container);

if (container.hasChildNodes()) {
  ReactDOM.hydrateRoot(
    container,
    <BrowserRouter>
      <RecoilRoot>
        <ScrollToTop />
        <HelmetProvider>
          <App />
        </HelmetProvider>
      </RecoilRoot>
    </BrowserRouter>
  );
} else {
  root.render(
    <BrowserRouter>
      <RecoilRoot>
        <ScrollToTop />
        <HelmetProvider>
          <App />
        </HelmetProvider>
      </RecoilRoot>
    </BrowserRouter>
  );
}

package.json

"script": {
  ...
  "postbuild": "react-snap" // 추가
},
"reactSnap": {
  "include": [
  	// 포함할 경로 작성하기
  	"/"
  ],
  "exclude": [
    // 제외할 경로 작성하기
  ],
}

그러고 나서 build를 진행해주면 각 path별로 index.html이 생성되게 됩니다.


❗️여기서 문제가 있는데 상품의 상세페이지의 경우 /detail/:productId로 동적 라우팅이 설정되어있는데, 맨 처음 렌더링되는 30개의 상품만 index.html이 만들어졌다는 것입니다.

react-snap을 통해 index.html이 만들어진 상품은 아래처럼 상품의 이미지, 제목, 설명이 잘 나타나지만 그렇지 않은 경우 default로 설정된 og 태그가 나타나게 됩니다.

동적 라우팅과 관련해서 해당 Issue를 살펴보았으나 react-snap의 경우 동적 라우팅을 처리하는 것은 어려운 것 같습니다.

결론은.. 쇼핑몰이나 SEO가 중요한 사이트의 경우 SSR을 진행하는 것이 맞을 것 같습니다 🤔


참고
react-snap
https://velog.io/@euisuk95/React-Helmet%EA%B3%BC-React-Snap%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-SEO
https://velog.io/@chl4842/react-helmet-react-snap-%EC%9C%BC%EB%A1%9C-%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0#react-snap
https://velog.io/@apro_xo/react-helmet-async-react-snap
https://velog.io/@miyoni/noSSRyesSEO

0개의 댓글

관련 채용 정보