민한의 블로그

SEO관련 - 구글 Job Posting, 검색노출, 구조화된 데이터 본문

기타 정리/기타

SEO관련 - 구글 Job Posting, 검색노출, 구조화된 데이터

minhan2 2021. 9. 10. 18:27
728x90
반응형

SEO관련웹사이트

네이버 Search Advisor : https://searchadvisor.naver.com/

구글 SEO 가이드 : https://support.google.com/webmasters/answer/7451184?hl=ko&ref_topic=9460495

네이버 키워드 랭킹 조회 : http://surffing.net/

title 태그와 meta 태그의 description이 중요한 이유 : https://sexyr.tistory.com/entry/%EA%B5%AC%EA%B8%80-SEO-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B3%B8-%EA%B0%80%EC%9D%B4%EB%93%9C-1-title-%EA%B3%BC-description?category=857914

robots.txt설정법

우선 client에서 public 폴더에 robots.txt를 다음과 같이 설정하자.

*모든 검색엔진의 로봇에 대하여 수집 허용으로 설정합니다.

User-agent: *
Allow: /
Sitemap: https://miniintern.com/sitemap.xml //검색엔진에게 사이트맵을 제공

사이트맵작성법

검색엔진의 원활한 크롤링을 도와주기 위해 사이트맵을 작성하자. 미니인턴 웹은 컨텐츠가 자주 변화하는 사이트이므로 정적인 사이트맵이 아닌 동적인 사이트맵으로 제작하였다.

우선 client에서 scripts 폴더에 다음과 같은 파일을 생성하고 아래 코드를 입력했다.

scripts/sitemap-common.js

const fs = require('fs');
const globby = require('globby');
const prettier = require('prettier');

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

const YOUR_AWESOME_DOMAIN = process.env.CLIENT_URL;

const formatted = sitemap => prettier.format(sitemap, { parser: 'html' });

(async () => {
  const pages = await globby([
    // include
    'src/pages/**/*.tsx',
    'src/pages/*.tsx',
    // exclude
    '!src/pages/_*.tsx',
  ]);

  const pagesSitemap = `
    ${pages
      .map(page => {
        const path = page
          .replace('src/pages/', '')
          .replace('.tsx', '')
          .replace(/\/index/g, '');
        const routePath = path === 'index' ? '' : path;
        return `
          <url>
            <loc>${YOUR_AWESOME_DOMAIN}/${routePath}</loc>
            <lastmod>${getDate}</lastmod>
          </url>
        `;
      })
      .join('')}
  `;

  const generatedSitemap = `
    <?xml version="1.0" encoding="UTF-8"?>
    <urlset
      xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
    >
      ${pagesSitemap}
    </urlset>
  `;

  const formattedSitemap = [formatted(generatedSitemap)];
  fs.writeFileSync('public/sitemap.xml', formattedSitemap, 'utf8');
})();

해당 코드는 pages에 _app,_documents 등 필요없는 페이지들을 제외하고 sitemap.xml을 생성하는 코드이다.
해당 코드 실행시 public폴더에 sitemap.xml 파일이 아래와 같이 생성된다.

<?xml version="1.0" encoding="UTF-8"?>
<urlset
  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
>
  <url>
    <loc>https://miniintern.com/404</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/about</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/preview</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/sample</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/sample2</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/withus</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/auth/email-authentication</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/auth/login</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/auth/password</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/company/[urlSlug]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/company</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/event</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter/privacy</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter/qna</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter/refund</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter/terms</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/account</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/compensation</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/event</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/head-hunting</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/project</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/projects</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/recruitment/[urlSlug]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/recruitment</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/auth/signup/form</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/auth/signup</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/event/[urlSlug]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/event/participate/[urlSlug]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter/notices/[id]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/helpcenter/notices</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/applications</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/documents</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/projects/[urlSlug]/apply</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/projects/[urlSlug]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/event/[urlSlug]/apply/fail</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/event/[urlSlug]/apply/success</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/projects/[urlSlug]/apply/success</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/projects/[urlSlug]/survey/complete</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/projects/[urlSlug]/survey</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc
      >https://miniintern.com/me/applications/apply-settle-account/[urlSlug]/[applicationId]</loc
    >
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/documents/resume/create</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>

  <url>
    <loc>https://miniintern.com/me/documents/resume/update/[id]</loc>
    <lastmod>2020-07-29T07:32:09.775Z</lastmod>
  </url>
</urlset>

다음으로, 빌드가 될때마다 xml파일을 동적으로 업데이트 해주기 위해 next.config.js에 다음 코드를 넣어주었다.

 module.exports = {
  webpack: (config, { isServer }) => {
    if (isServer) {
      require('./scripts/generate-sitemap');
    }

    return config;
  }
};

next server가 build될때마다 sitemap.xml이 업데이트 됨을 알 수 있다.

구조화된데이터

구글에서 채용관련 검색을 하다보면 다음과 같은 검색결과가 나타날때가 있다.


이런 검색결과를 노출시키고 싶다면, 페이지에 구조화된 데이터를 추가하면 된다.
구글에서 설명하는 구조화된 데이터에 대한 설명은 다음과 같다.


구조화된 데이터 작동 방식 이해
Google 검색은 페이지의 콘텐츠를 파악하기 위해 노력합니다. 페이지에 구조화된 데이터를 포함하면 Google에 페이지의 의미에 관한 확실한 단서를 제공하여 내용을 파악하는 데 도움이 됩니다. 구조화된 데이터는 페이지에 관한 정보를 제공하고 페이지 콘텐츠를 분류하기 위한 표준화된 형식으로 예를 들어 레시피 페이지의 경우 재료, 조리 시간, 온도, 칼로리 등이 여기에 해당합니다.

Google에서는 웹에서 찾은 구조화된 데이터를 사용하여 페이지의 콘텐츠를 파악할 뿐 아니라 웹 및 전반적인 세상에 관한 정보를 수집합니다. 예를 들어 다음은 레시피 페이지에 표시될 수 있으며 레시피 제목, 레시피 작성자 및 기타 세부정보를 설명하는 구조화된 JSON-LD 데이터 스니펫입니다.

<html>
  <head>
    <title>Party Coffee Cake</title>
    <script type="application/ld+json">
    {
      "@context": "https://schema.org/",
      "@type": "Recipe",
      "name": "Party Coffee Cake",
      "author": {
        "@type": "Person",
        "name": "Mary Stone"
      },
      "datePublished": "2018-03-10",
      "description": "This coffee cake is awesome and perfect for parties.",
      "prepTime": "PT20M"
    }
    </script>
  </head>
  <body>
  <h2>Party coffee cake recipe</h2>
  <p>
    This coffee cake is awesome and perfect for parties.
  </p>
  </body>
</html>

자세한 설명이 필요하다면 구글 구조화된데이터를 참고하자.

구조화된 데이터의 기능은 다음과 같다.


미니인턴 채용관의 채용정보에 구조화된 데이터 추가를 위해 채용정보 웹페이지에 구조화된 JobPosting 데이터를 추가해 보자.
head태그를 공용컴포넌트화 하기 위해 만들어진 WithHead 컴포넌트에 다음과 같이 코드를 삽입한다.

import React from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import useRecruitment from '../../lib/hooks/useRecruitment';

const WithHead = ({
  title,
  description = '',
  image,
}: {
  title: string;
  description?: string;
  image?: string;
}) => {
  const { pathname, asPath } = useRouter();
  const {
    recruitment: {
      recruitmentDetail: { notice, company },
    },
  } = useRecruitment();
  const noticeDescriptions = notice ? [...notice.descriptions] : [];

  const noticeDescription = noticeDescriptions.map(
    des => des.content && `${des.title}\n\n${des.content.replace(/["']/g, '')}\n\n\n`,
  );

  return (
    <Head>
      <title>{title}</title>
      <meta name="description" content={description} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta
        property="og:image"
        content={image || 'http://miniintern.com/static/images/meta/metaImg.jpg'}
      />
      //* 구조화된 채용정보 데이터
      {pathname === '/recruitment/[id]' && notice && company && notice.status !== 'finished' && (
        <script type="application/ld+json">
          {`{
          "@context" : "https://schema.org/",
          "@type" : "JobPosting",
          "title" : "${notice.name}",
          "description" : "${`기업 소개\n\n${company.description}\n\n\n${noticeDescription.join(
            '',
          )}`}",
          "url":"${`${process.env.CLIENT_URL}${asPath}`}",
          "datePosted" : "${new Date(notice.createdAt)}",
          "industry": "${company.industrialField}",
          "occupationalCategory": "${notice.categories.join(',')}",
          "validThrough" : "${notice.endDate ? new Date(notice.endDate) : null}",
          "employmentType" : "${
            notice.employmentType === '정규직'
              ? 'FULL_TIME'
              : notice.employmentType === '계약직'
              ? 'CONTRACTOR'
              : 'INTERN'
          }",
          "hiringOrganization" : {
            "@type" : "Organization",
            "name" : "${company.name}",
            "sameAs" : "${`${process.env.CLIENT_URL}${asPath}`}",
            "logo" : "${company.logoImage?.url}"
          },
          "jobLocation": {
          "@type": "Place",
            "address": {
            "@type": "PostalAddress",
            "streetAddress": "${`${notice.workAddress.roadName
              .split(' ')
              .splice(1, -1)
              .join(' ')} ${notice.workAddress.detail}`.trim()}",
            "addressRegion": "${notice.workAddress.roadName.split(' ')[0]}",
            "addressCountry": "KR"
            }
          }
        }`}
        </script>
      )}
    </Head>
  );
};

export default WithHead;

다음과 같이 Head 태그 안에 JSON-LD 코드가 삽입되어있는걸 볼 수 있다.


모든 준비가 끝났다.
이제 sitemap을 최신화 시켜 구글이 크롤링 할 수 있게만 하면, 검색엔진에 다음과 같이 표시될 것이다.
밑에 스크린샷은 채용관의 채용정보가 잘 적용되어 나오는 것을 볼 수 있다.


미니인턴의 공고가 잘 표시되어 올라오는것을 확인 할 수 있다.

상세페이지는 다음과 같이 구성되어있다.

이상으로 구조화된 데이터 설명을 마친다.

728x90
반응형
Comments