main-logo

블로그 개편 그리고 1년후

검색 최적화를 위한 여정

profile
강영민
2024년 09월 04일 · 0 분 소요

들어가며

XE그룹의 블로그가 새단장을 한 지도 어느새 약 1년이 지났습니다.

개편을 진행하면서 가장 고려했던 부분은 그동안 검색엔진에 수집된 저희 블로그의 데이터였습니다. 상황상 기존 블로그의 도메인과 URL을 그대로 가져오기 어려웠기에, 검색엔진에 수집된 데이터 대부분을 잃을 수밖에 없었습니다 그래서 개편 이후의 또 다른 목표는 새단장을 마친 저희 블로그가 빠르게 검색엔진에 수집되어 게제순위를 높이는 일이었습니다

그러나 SEO와 웹사이트 성능 등 여러 부분을 최적화를 했음에도 불구하고, 검색엔진에서 블로그의 게재 순위나 페이지 노출의 실적이 좋지 않았죠… 단순히 시간에 모든 것을 맡길 수 없었기에, 문제를 파악하고 개선해 보기로 했고 그이후의 결과를 공유하려고 합니다.

상태

기존의 그룹 블로그는 Gatsby를 활용하여 md 파일로 콘텐츠를 작성하고 빌드하여 페이지를 생성하는 방식이었습니다. 이는 정적 데이터로 제공되는 구조였죠. 하지만 이 방식은 콘텐츠의 실시간 업데이트가 어렵고, 다양한 포맷의 콘텐츠를 효과적으로 관리하는 데 한계가 있었습니다. 그래서 "좀 더 편리하고 유연한 블로그면 좋겠다"는 의견에 따라, 그룹원들이 어드민을 통해 콘텐츠를 데이터로 저장할 수 있도록 개편 방향을 잡게 되었습니다.

어드민은 CMS를 활용하여 페이지에서 콘텐츠를 작성하고, 이 데이터를 API로 내보내어 프론트에서 가공된 콘텐츠로 보여질수 있는 방식으로 변경했습니다.

이 방식은 콘텐츠의 실시간 업데이트가 용이하고, 다양한 포맷의 콘텐츠를 관리하는 데 유리했습니다. 그러나 이러한 변화가 검색 최적화에 어떤 영향을 미칠지에 대해서는 당시에 미처 생각하지 못했습니다.

물론 기존 블로그의 검색엔진에 수집된 데이터를 이어가지 못하고 새로운 도메인으로 시작할 수밖에 없었던 상황이었습니다. 그래서 초기에는 여러모로 검색 순위가 좋지 않을 것으로 예상할 수 있었죠.

그러나 진짜 문제는 그 이후였습니다. 개편된 블로그는 약 6개월이 지난 후에도 검색 실적이 제자리걸음이었죠

1.png

블로그의 상태는 매우좋았습니다 구글 크롬의 라이트하우스를 사용해 성능을 측정해 보니 대부분의 지표에서 90점 이상의 높은 점수를 받았습니다. 페이지 로딩 속도, 접근성, SEO 등 모든 면에서 우수한 결과를 보여주었죠. 그럼에도 불구하고 검색 순위가 개선되지 않는 이유를 찾기 위해 더 생각해보게 되었습니다

무엇을 했나?

우선 저희 블로그 프론트는 Next.js로 개발되어 있어서 SSG(Static Site Generation)를 고려해볼 수 있었습니다. 하지만 SSG는 빌드 시점에만 데이터를 미리 렌더링할 수 있기 때문에 제가 생각하는 이상적인 해결책은 아니라고 판단했습니다.

블로그 콘텐츠가 빈번하게 업데이트되지는 않지만, 불규칙한 간격으로 갱신되며 때로는 실시간성이 중요한 경우도 있다고 생각하기에 콘텐츠 관리의 유연성을 위해 CMS를 도입한 만큼, 이를 최대한 활용하고자 했습니다.

그래서 동적 데이터를 사용하면서도 검색엔진이 그룹 블로그의 데이터를 최대한 쉽게 수집할 수 있는 방법을 찾던 중 사이트맵(sitemap)이 떠올랐습니다. 확인해보니 당시 그룹 블로그는 사이트맵을 루트페이지를 제외하곤 하나도 생성하지 못하고 있는 상태였었죠…

여러가지 방법이 있겠지만 저는 next-sitemap을 이용해 빌드시점에 api에 데이터를 호출해 동적 페이지의 모든경로를 사이트맵에 생성할수 있는 방법을 시도해 보기로 했습니다.

/** @type {import('next-sitemap').IConfig} */
// next-sitemap.config.js
module.exports = {
  siteUrl: '<https://tech.pxd.co.kr>',
  generateRobotsTxt: true,
  sitemapSize: 5000,
  autoLastmod: true,
  outDir: './public',
  // 정적 페이지 및 동적 페이지 경로 추가를 위한 transform
  transform: async (config, path) => {
    return {
      loc: path, // 페이지 위치
      changefreq: 'daily',
      priority: 0.7,
      lastmod: config.autoLastmod ? new Date().toISOString() : undefined
    };
  },
  additionalPaths: async (config) => {
    const fetch = (await import('node-fetch')).default;
    return fetch('https://{api호출url}')
      .then((res) => res.json())
      .then((paths) => {
        if (paths && Array.isArray(paths)) {
          // `data` 배열이 존재하는지 확인
          return paths.map((item) => {
            // `data` 배열의 각 항목을 처리
            const encodedTitle = encodeURIComponent(item.title).replace(/%20/g, '-');
            return {
              loc: `/post/${encodedTitle}-${item.id}`, // `slug` 필드를 사용하여 URL 구성
              lastmod: new Date().toISOString()
            };
          });
        } else {
          console.error('API response is not valid: ', paths);
          return []; // 유효하지 않은 응답인 경우 빈 배열 반환
        }
      });
  }
};

저희 그룹블로그의 상세페이지 url은 블로그의 타이틀에 따라서 생성되기에 블로그의 타이틀이 생성되는 규칙을 따라서 사이트맵 url도 마찬가지로 똑같이 생성하기 위한 코드가 포함됩니다.

위와 같이 next-sitemap.config.js 파일을 생성한후 빌드한 결과

<url><loc><https://tech.pxd.co.kr/post/Sass-%EB%A7%9B%EB%B3%B4%EA%B8%B0-%231-6></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/Jotai-7></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/%EC%9B%B9%EC%84%9C%EB%B2%84%EC%99%80-WAS(Web-Application-Server)-8></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/%EB%A9%94%ED%83%80%EB%B2%84%EC%8A%A4%2C-%EC%9D%B4%EB%AF%B8-%EC%8B%9C%EC%9E%91%EB%90%9C-%EB%AF%B8%EB%9E%98-10></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/%E2%80%98FED%E2%80%99%EB%A1%9C-UX%ED%95%98%EA%B8%B0-11></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/FED%EB%A1%9C-UX%ED%95%98%EA%B8%B0-%3A-%EC%8B%A4%EC%A0%84%ED%8E%B8-12></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/%EC%99%B8%EB%B6%80%EC%97%90%EC%84%9C-%EB%A1%9C%EC%BB%AC-%EA%B0%9C%EB%B0%9C-%EC%84%9C%EB%B2%84%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0-13></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/CSS-Overview-14></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/Cookie%EB%8A%94-%EB%AD%90%EC%A7%80-%EB%A8%B9%EB%8A%94%EA%B1%B4%EA%B0%80-15></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/%EC%83%81%ED%99%A9%EC%97%90-%EB%A7%9E%EB%8A%94-%ED%82%A4%ED%8C%A8%EB%93%9C%EB%A5%BC-%EB%B3%B4%EC%97%AC%EC%A3%BC%EA%B8%B0-16></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/%EB%A6%AC%EC%95%A1%ED%8A%B8%EA%B0%80-%EC%89%AC%EC%9B%8C%EC%A7%80%EB%8A%94-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-17></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/Generator%EB%9E%80-%EB%AD%98%EA%B9%8C%3F-18></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>
<url><loc><https://tech.pxd.co.kr/post/WEB-3.0-19></loc><lastmod>2024-09-04T05:16:46.032Z</lastmod></url>

위처럼 블로그의 동적인 페이지들의 url이 들어있는 sitemap.xml 파일을 얻을수 있었고 적용할 수 있었습니다 그런데 sitemap 또한 빌드시에만 업데이트된 포스트의 url을 반영할 수 있습니다.

그래서 github action을 활용해 보기로 했습니다

name: Monthly Build

on:
  schedule:
    - cron: '0 0 1,15 * *'  # 매달 1일과 15일 자정에 실행

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v2
      
    - name: Install dependencies
      run: yarn install
      
    - name: Build
      run: yarn build
      
    - name: Deploy to Vercel
      run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
      env:
        VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
        VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

루트 디렉토리에 .github/workflows/auto-build.yml 를 생성해주고 위와같이 작성했습니다 매월 1일과 15일에 갱신된 url을 sitemap.xml에 포함하여 자동으로 프로젝트를 빌드하고 배포할수 있도록 작성했고

2.png

결과는 현재 까지도 꾸준히 진행중입니다.

결과

그래서 검색 최적화를 위해 sitemap.xml을 생성하고 주기적으로 업데이트하여 어떤 유의미한 결과를 얻을수 있었는지에 대해서 얘기 해보려 합니다.

블로그 개편을 진행하고 정식적으로 도메인을 적용하고 오픈한 시기는 2023년 10월 이었습니다 그리고 SEO를 개선하기 위해 작업이 진행된 시기는 2024년 5월입니다.

아래는 수치 비교를 위해 구글의 서치콘솔의 데이터차트를 캡쳐해왔습니다.

3.png

4.png

물론 시간이 지나며 검색엔진에 수집된 데이터가 늘어나기에 점진적으로 검색에대한 실적은 올라갈수 있다고 생각합니다만 위 비교된 수치를 확인해 봤을때 그룹블로그가 개편된 이후 그리고 사이트 맵을 정상적으로 제출한이후의 기간의 비교에 있어서 검색 실적이 비약적으로 상승했다고 봐도 무방할 정도로 차이는 분명하게 나타난다고 생각합니다. 이유는 사이트맵 제출이후 부터 검색엔진에서의 크롤링 빈도인덱싱 속도가 크게 개선 되었기 때문이죠 자연스럽게 페이지 노출 빈도와 순위가 상승한걸로 보여집니다.

5.png

위 차트를 보면 노출 빈도가 높은만큼 방문자수도 높아질수 있다는 결론이 나옵니다 그만큼 검색엔진에 페이지가 자주 노출된다는건 중요한 부분인것 같습니다.

sitemap.xml 개선 후 나타난 주요 변화

  • 검색 엔진 노출 빈도 증가: 특히 [키워드 예시]와 같은 주요 키워드에 대한 검색 결과 상위 노출이 눈에 띄게 증가했습니다. 이는 sitemap.xml을 통해 검색 엔진이 블로그의 새로운 콘텐츠를 빠르게 인식하고 색인했기 때문이라고 판단됩니다.
  • 특정 페이지 방문자 수 증가: sitemap.xml에 포함된 특정 페이지의 방문자 수가 유의미하게 증가했습니다. 이는 해당 페이지가 검색 결과에서 더 높은 순위를 차지하며 유입이 증가했음을 의미합니다.
  • 전체적인 검색 트래픽 증가: sitemap.xml 개선 이후 전체적인 검색 트래픽이 증가하는 효과를 보였습니다. 이는 블로그의 가시성이 높아지고 더 많은 사용자가 블로그를 방문하게 되었음을 의미합니다.

sitemap.xml 개선이 가져온 의미

sitemap.xml은 검색 엔진에게 블로그의 구조와 콘텐츠를 효과적으로 알려주는 지도와 같은 역할을 합니다. 특히 동적 데이터로 구성된 블로그의 경우, sitemap.xml을 통해 검색 엔진이 콘텐츠를 정확하게 파악하고 색인하는 것이 더욱 중요하다고 이번계기로 느끼게 되었습니다.

마치며

6.png

이전에는 sitemap이 검색에 있어서 크게 영향을 줄수 있을지에 대해서 생각해본적이 없었던것 같습니다.

그룹블로그의 검색 최적화에 대해서 고민해보고 sitemap을 개선해본 이후, 검색 결과에 대한 데이터를 분석하며 그 중요성을 실감하게 되었습니다.

물론 아직도 검색최적화를 위해 개선되야 하는 부분은 많을 것 같습니다. 위 페이지 색인 생성 차트를 보면 지금도 141개의 색인이 생성되지 않은 페이지들이 존재합니다.

sitemap을 제출하기 전과 비교하면 많은 페이지들이 색인되었지만, 여전히 개선할 여지가 있다는 것을 의미하기에 향후 지속적으로 모니터링 및 미색인 페이지 분석을 통해서 모든페이지의 색인 생성을 마칠수 있도록 지속 관리할 예정입니다.

이번 포스팅은 그룹블로그에 개편후 약 1년이 되는 시점이라서 주제로 선정하고 작성해 봤습니다 개편을 진행하면서도 가장 걱정되는 부분이었고 검색최적화에 대해서 고민하며 느낀점은 검색 엔진 최적화는 단기간에 성과를 내기 어려운 작업이라 생각했습니다.

장기적인 관점에서 꾸준히 노력해야하며 검색 엔진 알고리즘은 지속적으로 변화하기 때문에, 최신 SEO 트렌드를 꾸준히 학습하고 적용할수 있도록 노력을 해야 할것 같습니다.

그리고 제가 위 작업을 진행할 당시 그리고 현재도 Nextjs에 대해서 모르는 부분이 많아 위와 같은 방식으로 동적 데이터를 가지고 sitemap을 생성하도록 했지만 이보다 더 효율적인 방법을 알고있다면 언제든지 피드백을 주시면 수용하겠습니다.

읽어주셔서 감사합니다.