SEO를 위한 sitemap과 meta tag

dahyeon·2023년 1월 26일
1

본 글에서는 SEO를 위한 방법들을 소개하고, 이를 Next.js를 사용하고 있는 프로젝트에 어떻게 적용했는지 소개한다.

SEO 기본 가이드


Google의 SEO 기본 가이드에 따르면 다음과 같은 부분들을 개선할 수 있다.

  1. 사이트맵 등록하기
  2. 메타 태그 작성하기
  3. robots.txt

Sitemap


사이트맵이란?

웹사이트에서 구글이나 네이버와 같은 검색 엔진에 색인할 모든 페이지를 나열한 XML 파일

사이트맵이 필요한 이유

Google의 SEO 기본 가이드에 따르면…

내 사이트가 Google에서 검색되도록 하기 위한 첫 번째 단계는 Google이 사이트를 발견할 수 있도록 하는 것입니다. 이때는 사이트맵을 제출하는 것이 가장 좋습니다. 사이트맵은 사이트에 있는 파일로서 새 페이지나 변경된 페이지가 있을 때 이를 검색엔진에 알려 줍니다.

  • Googlebot 및 기타 웹 크롤러는 한 페이지에서 다른 페이지로 연결되는 링크를 따라 이동하여 웹을 크롤링하는데, 다른 사이트가 링크되어 있지 않으면 페이지를 찾지 못할 수 있다.
  • 사이트맵은 검색엔진 크롤러에 어떤 페이지가 있는지 알려주어, 검색엔진이 찾기 어려운 페이지도 문제없이 크롤링될 수 있게 한다.

🤔 Knoticle 프로젝트의 사이트맵을 생성하려면

Knoticle 프로젝트에서는 Next.js를 사용하고 있는데, Next.js의 SSR과 next-sitemap 라이브러리를 통해 동적으로 사이트맵을 생성할 수 있다.

  • 사용자가 작성한 글 페이지들을 모두 사이트맵에 등록해야한다. 글의 주소가 knoticle.app/viewer/{책 id}/{글 id} 와 같이 구성되어 있다.
  • 글들이 계속 업데이트 되기 때문에 sitemap을 동적으로 생성해야 한다. 존재하는 {책 id}/{글 id} 를 DB에서 일정 시간 간격으로 받아와서 sitemap 을 생성해야 한다. → next-sitemap이라는 라이브러리 사용(링크)

next-sitemap으로 dynamic/server-side sitemap 생성하기

  1. DB로부터 생성해야 하는 페이지 정보들을 받아온다.
  • 사용자가 작성한 글 페이지의 주소를 완성하기 위해서는 책 id글 id가 필요하다.
  • Knoticle 서비스에서는 scraps 테이블에 책 id글 id가 저장되어 있다. → 그 중 글이 원본인 아이템(즉, 원본 책 id글 id의 조합)만 받아오도록 했다. 검색엔진에 원본 글만 잡히는 것이 맞다고 생각했다.
  1. 받아온 정보들로 페이지마다 field를 생성한다.

    field는 다음과 같은 형태로 구성되어 있는데,

        loc: `${process.env.NEXT_PUBLIC_CLIENT_URL}/viewer/${scrap.book_id}/${scrap.article_id}`,
        changefreq: 'daily',
        priority: '1.0',
        lastmod,
    • changefreq: 사이트가 얼마나 자주 바뀌는지에 대한 정보. 검색 엔진한테 대략 어느 주기로 페이지의 변화를 체크해야 하는지 알려준다.
    • priority: 사이트 내에서 페이지들의 상대적인 우선 순위를 나타낸다.

    → 하지만 Google 검색 센터에 따르면, Google에서는 <priority> 및 <changefreq> 값을 무시한다고 한다.

  2. 생성된 fields로 sitemap.xml을 구성한다.

코드는 다음과 같다.

// pages/sitemap.xml.tsx 

import { GetServerSidePropsContext } from 'next';
import { getServerSideSitemap } from 'next-sitemap';

import { getScrapsApi } from '@apis/scrapApi';

export default function SiteMapXML() {
  return <></>;
}

export const getServerSideProps = async (context: GetServerSidePropsContext) => {
  const scraps = await getScrapsApi();

  const lastmod = new Date().toISOString();

  const defaultFields = [
    {
      loc: `${process.env.NEXT_PUBLIC_CLIENT_URL}`,
      changefreq: 'daily',
      priority: '1.0',
      lastmod,
    },
  ];

  const scrapFields = scraps.map((scrap: { book_id: number; article_id: number }) => ({
		loc: `${process.env.NEXT_PUBLIC_CLIENT_URL}/viewer/${scrap.book_id}/${scrap.article_id}`,
    changefreq: 'daily',
    priority: '1.0',
    lastmod,
  }));

  const fields = [...defaultFields, ...scrapFields];

  return getServerSideSitemap(context, fields);
};

사이트맵 제출하기

  • 검색엔진 크롤러가 사이트맵을 찾을 수 있도록 검색엔진별로 제출하는 것이 필요하다.

Meta tag

메타 태그란?

  • <meta> 태그는 해당 문서에 대한 정보인 메타데이터(metadata)를 정의할 때 사용한다 : <base>, <link>, <script>, <style>, <title> 요소와 같은 다른 메타데이터 관련 요소들이 나타낼 수 없는 다양한 종류의 메타데이터를 제공할 때 사용되며, 이렇게 제공된 정보는 브라우저나 검색 엔진, 다른 웹 서비스에서 사용하게 된다

메타 태그를 작성해야 하는 이유

  • SEO를 위해서는 메타 태그를 작성해주어야 한다. 구글의 SEO 기본 가이드에 다음과 같은 내용이 있다.

    메타 설명 태그는 중요합니다. Google에서 설명 메타 태그를 Google 검색결과에서 페이지의 스니펫으로 사용할 수 있기 때문입니다.

    • 여기서 스니펫이란 검색 결과가 표시되는 부분이다.
      : 타이틀은 <title>태그와 관련되어 있고, 그 아래 설명은 <meta name=”description”>과 관련되어 있다.

어떤 메타 태그를 포함시켜야 할까?

구글 공식 문서에 따르면, 구글에서 인식하는 메타 태그에는 다음이 있다.

  • <meta name="description" content="A description of the page" /> : 간단한 페이지 설명
  • <meta name="viewport" content="..."> : 휴대기기에서 페이지를 렌더링하는 방법을 브라우저에 알리는 태그. 이 태그를 사용하면 페이지가 모바일 친화적이라는 사실을 Google에 알릴 수 있다.
  • <meta http-equiv="Content-Type" content="...; charset=..." /> <meta charset="..." > : 페이지의 콘텐츠 유형과 문자 집합을 정의하는 태그. 가능한 경우 Unicode/UTF-8을 사용하는 것이 좋다.

따라서,

  • <title><meta name=”description”> 태그를 페이지마다 다르게 생성하자.
  • 그 외 open graph에 관련된 메타 태그도 페이지마다 다르게 지정해주자.

Open graph란?

  • 사이트를 공유했을 때 사이트에 대한 정보를 미리보기로 확인할 수 있도록, 미리보기에 담을 메타데이터를 표준화하기 위한 프로토콜

적용하기

  • 뷰어페이지에 적용하기
    1. getServerSideProps로 article 데이터 받아오기

      export const getServerSideProps: GetServerSideProps = async (context) => {
        const [bookId, articleId] = context.query.data as string[];
        const article = await getArticleApi(articleId);
      
        return { props: { article } };
      };
    • getServerSideProps는 서버에서 동작하는 코드이므로, 함수 내부에서 next/router를 사용할 수가 없다.
    • 공식 문서에 따르면 context를 사용해야 한다. context 객체 내의 query에서 dynamic route의 query parameter를 가져올 수 있다.
  1. 가져온 아티클 정보로 title 및 메타 태그 완성하기
import Head from 'next/head';

interface ViewerHeadProps {
  articleTitle: string;
  articleContent: string;
}

export default function ViewerHead({ articleTitle, articleContent }: ViewerHeadProps) {
  return (
    <Head>
      <title>{articleTitle}</title>
      <meta name="description" content={articleContent.slice(0, 150)} />
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
      <meta property="og:title" content={articleTitle} />
      <meta property="og:description" content={articleContent.slice(0, 150)} />
      <meta property="og:type" content="website" />
      <meta property="og:url" content="https://www.knoticle.app" />
      <meta property="og:image" content="https://kr.object.ncloudstorage.com/j027/knoticle.png" />
    </Head>
  );
}
  • open graph 적용 결과

아래와 같이 knoticle 서비스의 글 링크를 노션에 복사해서 붙여넣으면 다음과 같이 표현된다.

Robots.txt

robots.txt란?

  • robots.txt 파일은 크롤러가 사이트의 어느 부분에 액세스할 수 있는지에 관한 규칙이 포함된 간단한 텍스트 파일

robots.txt 파일의 역할

  • robots.txt 파일을 사용하여 크롤링 트래픽을 관리하거나(서버에 Google 크롤러의 요청으로 인한 과부하가 발생할 것으로 생각되는 경우) 사이트에서 중요하지 않은 페이지 또는 비슷한 페이지의 크롤링을 방지할 수 있다.
    • 웹페이지가 Google에 표시되는 것을 방지하기 위한 메커니즘이 아니다.
  • sitemap.xml의 위치를 등록하여 sitemap이 어딨는지 알려준다.

robots.txt 작성

  • Knoticle 서비스에서 글을 쓸 수 있는 에디터 페이지(editor)와 검색 페이지(search)는 검색 엔진에 노출되지 않았으면 했기에 아래와 같이 작성하였다.

결과

  • Lighthouse SEO 점수가 80 → 97까지 상승

이전

적용 후


참고 자료

사이트맵이란 무엇인가요? | Google 검색 센터 | 문서 | Google Developers
The Importance of The XML Sitemap Priority and Changefreq Tags
사이트맵 만들기부터 제출하기: 쉬운 단계별 가이드 | TBWA 데이터랩
Data Fetching: getServerSideProps | Next.js
Robots.txt Sitemap: Add Your Sitemap To Your Robots.txt File
What is Open Graph and how can I use it for my website?

profile
https://github.com/dahyeon405

0개의 댓글