Next.js SEO를 위한 next/head 탐구
React.js에서 타이틀, 설명 등 SEO 구색을 어느 정도 맞추기 위해서는 react-helmet 이란 라이브러리를 사용합니다.
Next.js에서도 그럼 헬멧을 사용해야하나 ? 라는 의문에서 조사를 시작했습니다.
구글에서는 이제 SPA방식의 웹사이트도 어느 정도 크롤링 잘해주지만 특정 페이지의 title, og image 등을 바꾸기 위해 스크립트를 통해서 수정합니다. (카톡, 페이스북 공유 등.)
Next.js는 조금 다릅니다.
SSR 방식이기 때문에 굳이 렌더링 이후, 스크립트를 통해서 meta tag들을 바꾸지 않아도 됩니다.
렌더링 할 때 meta tag를 넣어서 렌더링 해주면 되기 때문입니다.
Next.js에서는 편리하게 지정할 수 있도록 next/head 라고 지원해줍니다. 공식문서
아래 코드는 API에서 받아온 데이터를 이용해서 meta tag에 title을 동적으로 넣었습니다.
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
const result = await fetchPost(params?.id as string);
return {
props: result,
};
};
const Posts: NextPage<PostProps> = ({
contentHtml,
title,
date,
}) => {
return (
<>
<Head>
<title>{ title }</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<div>
<h2>{ title }</h2>
<p> { date }</p>
<span dangerouslySetInnerHTML={{ __html: contentHtml }}></span>
</div>
</>
);
};
이건 특정 페이지에 메타 태그를 수정한 방법입니다.
모든 페이지에 각각 넣어야한다면 매우 불편하겠죠???
방법이 다 있습니다.
custom document라는 것을 이용해서 전역 meta tag를 지정할 수 있습니다. 공식문서
(create-next-app으로 만들었는데, _document가 없어서 생성했습니다.)
_app 은 가장 먼저 실행되는 컴포넌트입니다.
- 공통 레이아웃, global css 등 페이지에 적용할 공통 속성을 적용하는 컴포넌트.
_document는 HTML Document를 커스텀할 때 사용합니다.
- 웹 접근성 관련 태그 설정(메타 태그 등)
- head, body 등을 커스터마이징 할 때 사용
주의사항으로는 head를 설정할 때 next/document의 Head를 사용해야 합니다.
_app.tsx -> 해당 페이지 -> _document.tsx 순서로 렌더링 됩니다. (_app 다음에 _document가 렌더링 되는 줄 알았는데..)
그러면 저희는 메타 태그를 설정하기 위해서 _document를 커스터마이징 해야겠죠??
import { Html, Head, Main, NextScript } from 'next/document'
const Document = () => {
return (
<Html lang="ko">
<Head>
<meta name="description" content="Document의 메타태그" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
export default Document;
전역 메타 태그가 잘 설정되어있는 것을 볼 수 있습니다.
여기서 궁금증 !
만약 전역에서 메타 태그를 선언하고, 각 페이지에서 해당 메타 태그를 중복으로 선언하면 어떻게 될까 궁금하지 않으신가요 ?
title을 전역에 한번 설정해보려고 했는데,,,
description을 중복으로 넣어봤습니다.
각 페이지 -> _document 순으로 렌더링 되기 때문에 전역의 description이 overide 될 것 같아 보입니다.
const Posts: NextPage<PostProps> = ({
id,
contentHtml,
title,
date,
}) => {
return (
<>
<Head>
<title>{ title }</title>
<meta name="description" content={`${title}이 들어가있어요`} />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<div>
<h2>{ title }</h2>
<p> { date }</p>
<span dangerouslySetInnerHTML={{ __html: contentHtml }}></span>
</div>
</>
);
};
하지만 예상을 깨고 그냥 두 번 선언되는 것을 볼 수 있습니다.
나중에 크롤링하게 되면 어떤 것을 보여줄지는 잘 모르겠네요. (먼저 선언된 것을 읽으려나...)
참고 글