블로그: 메타 태그 직접 관리하기 — React 19에서 SEO
블로그: 메타 태그 직접 관리하기에 대해 "왜?"라고 물으면 답할 수 있으신가요?
\n\n> 블로그: 메타 태그 직접 관리하기에 대해 "왜?"라고 물으면 답할 수 있으신가요?\n블로그를 만들면서 SEO를 완전히 무시할 수는 없었습니다. 아무리 좋은 글을 써도 검색에 안 잡히면 아무도 안 읽으니까요.
가장 기본적인 건 메타 태그입니다. <title>, og:title, description 같은 태그를 페이지마다 다르게 넣어줘야 검색 엔진이나 카카오톡 같은 데서 미리보기가 제대로 나옵니다.
react-helmet-async는 안 됩니다
리액트에서 메타 태그를 관리하는 가장 유명한 라이브러리는 react-helmet-async입니다. 당연히 이걸 쓰려고 했는데, 설치부터 막혔습니다.
npm install react-helmet-async
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
react-helmet-async 2.0.5
└── ✕ unmet peer react@"^16.6.0 || ^17.0.0 || ^18.0.0": found 19.0.0
React 19를 지원하지 않습니다. 찾아보니 React 19부터는 JSX 안에서 <title>이나 <meta> 같은 태그를 쓰면 프레임워크가 알아서 <head>로 올려주는 기능이 추가됐더라고요. 그래서 react-helmet-async 쪽에서 아직 React 19 대응을 안 한 것 같습니다.
그냥 직접 만들기로 했습니다.
관리할 메타 태그 정리
블로그에서 챙겨야 할 메타 태그를 정리하면 크게 세 종류입니다.
기본 메타 태그
title— 브라우저 탭, 검색 결과에 표시description— 페이지 설명theme-color— 모바일 브라우저 상단 색상article:published_time— 글 작성 시각article:modified_time— 글 수정 시각
Open Graph (OG)
og:type—website또는articleog:title,og:description— 카카오톡, 슬랙 등에서 링크 미리보기
Twitter Card
twitter:card—summary_large_imagetwitter:title,twitter:description
전부 비슷비슷한 값이 들어가는데, 각 플랫폼이 보는 태그가 달라서 중복이더라도 다 넣어야 합니다.
SeoHelper 컴포넌트
페이지마다 메타 태그를 일일이 쓰면 코드가 지저분해지니까, SeoHelper라는 컴포넌트로 감쌌습니다.
const SeoHelper = ({
title,
description,
path,
type = 'website',
publishedTime,
modifiedTime,
}) => {
const url = `https://bak-libra26.co.kr${path}`;
return (
<>
{/* 기본 */}
<title>{title}</title>
<meta name="author" content="bak-libra26" />
<meta name="description" content={description} />
<meta name="theme-color" content="#0a0a0f" />
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
{/* OG */}
<meta property="og:type" content={type} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={url} />
{/* Twitter */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
</>
);
};
React 19에서는 이렇게 JSX로 <title>이나 <meta>를 쓰면 자동으로 <head>에 들어갑니다. react-helmet-async가 없어도 되는 이유입니다.
페이지별 적용
각 페이지 컴포넌트의 return문 안에 <SeoHelper />를 넣으면 됩니다.
// 글 상세 페이지
const PostDetailPage = () => {
const post = ...;
return (
<>
<SeoHelper
title={`${post.title} - bak-libra26`}
description={post.summary}
path={`/posts/${post.path}`}
type="article"
publishedTime={post.createdDate.toISOString()}
modifiedTime={post.lastModifiedDate.toISOString()}
/>
<article>
...
</article>
</>
);
};
// 글 목록 페이지
<SeoHelper
title="Posts — sim.junghun"
description="개발 블로그 글 목록"
path="/posts"
/>
// 홈페이지
<SeoHelper
title="sim.junghun — backend engineer"
description="개발 블로그"
path="/"
/>
페이지마다 title이랑 description만 바꿔서 넣으면 되니까 사용은 간단합니다. 페이지 수가 적어서 이 정도 수동 작업은 부담이 없고요.
JSON-LD 구조화 데이터
메타 태그만으로도 기본적인 SEO는 되는데, 구글에서는 JSON-LD 형태의 구조화 데이터도 권장합니다. SeoHelper에 이것도 포함시켰습니다.
// article 타입일 때
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: title,
description: description,
datePublished: publishedTime,
dateModified: modifiedTime,
author: {
'@type': 'Person',
name: 'sim.junghun',
},
};
이걸 <script type="application/ld+json">으로 페이지에 삽입하면 구글이 글의 구조를 더 잘 이해합니다. 검색 결과에 발행일이 표시된다든지 하는 효과가 있습니다.
솔직한 소감
react-helmet-async가 안 돼서 처음엔 좀 당황했는데, 직접 만들어보니 오히려 더 깔끔합니다. React 19의 메타데이터 호이스팅 덕분에 별도 Provider를 감쌀 필요도 없고, 그냥 JSX 쓰듯이 쓰면 되니까요.
다만 이건 SPA 한계상, 자바스크립트가 실행되어야 메타 태그가 들어간다는 문제가 있습니다. 크롤러가 JS를 실행하지 않으면 빈 페이지로 인식할 수 있죠. 이 문제는 다음 글에서 프리렌더링으로 해결합니다.