SPA 프로젝트에 다이나믹 og태그 삽입하기

Minsun
Written by Minsun on
SPA 프로젝트에 다이나믹 og태그 삽입하기

SPA 프로젝트에 다이나믹 og태그 삽입하기

SPA로 개발된 프로젝트에 동적으로 og 태그를 넣어야하는 이슈에 대응하였다. 프로젝트 오픈 시점에는 해당 부분이 크게 고려되지 않아 react-helmet 라이브러리를 사용하여 동적으로 브라우저 탭을 넣어주는 것에만 대응 되었는데 역시 seo나 소셜 링크 공유를 위해서 동적으로 og태그를 넣어줘야 했다. 취준할때에 spa로 설계된 프로젝트는 seo가 어렵다라는 것을 이론적으로만 알고 있었는데 이번에 spa 프로젝트에 다양한 방법으로 seo를 적용할 수 있단 것을 알게 되어 정리를 해보았다.

SPA

SPA란 싱글 페이지 어플리케이션이란 뜻으로 사용자와의 인터랙션이 증대된 오늘 날의 웹사이트에서 어플과 같은 자연스러운 인터랙션을 위해 하나의 html 파일로만 설계된 어플리케이션이다. 최초 한번만 html 파일을 불러온 후 자바스크립트로 렌더링 할 내용을 결정하기에 사용자와의 인터랙션 부분에선 빠른 처리속도를 내지만 초기 렌더가 느리고 seo대응에 어렵다는 단점이 있다.

라이브러리를 사용하여 동적으로 og태그를 넣어주는 방법으로 해결되지 않는 이유

react-helmet이나 react-meta-tags와 같은 다양한 라이브러리를 사용하여 페이지별로 동적으로 og태그를 넣어주는 것이 가능하다. 하지만 이는 결국 자바스크립트 내에서 동적으로 값을 할당하는 것으로 html 파일만을 바라보는 크롤링 봇은 여전히 자바스크립트 내의 동적 태그 내용을 읽을 수가 없다. 구글의 크롤링 봇은 자바스크립트 파일 내의 내용을 읽는 것도 가능하다고는 하지만 동적 og태그를 적용하는 가장 큰 목적인 소셜 내의 링크 공유시에 보여지기 위해서는 자바스크립트 내에서 주는 방법은 옳지 않았다.

SPA 프로젝트에서 SEO에 대응하는 방법

  • SSR

    가장 먼저 생각된 방법은 서버사이드렌더링으로 서버에서 렌더링을 먼저하여 완성된 html을 크롤링 봇이 읽을 수 있게하는 방법이다. 이미 SPA를 위한 SSR을 지원하는 프레임워크는 많지만 이미 완성된 프로젝트에 적용할 수 있는 방법은 아니다.

  • prerendering

    react-snap 라이브러리를 사용하여 페이지별 html을 생성해 주는 프리렌더링 방법으로도 dynamic og태그 대응이 가능하다.

  • AWS Lambda@Edge 이용하기

    해당 방법을 사용하기 위해서는 AWS Cloudfront를 사용하고 있어야 한다. 현재 배포된 사이트는 AWS를 사용하고 있기 때문에 해당 방법을 사용하였다!

  • 이외에도 다양한 방법이 있다!

Lambda@Edge 란?

SPA로 만든 웹사이트를 구축할 때 빌드한 페이지를 AWS S3에 올리고 Cloudfront를 이용하여 도메인을 연결하고 유저의 요청이 오면 S3에 올라간 파일을 찾아서 제공하게 된다. 이때 유저의 요청에 따라 파일을 제공하기 전에 특정 함수를 수행할 수 있는데 이때 수행되는 것이 Lambda@Edge 함수이다. 유저에 따라 파일을 변경을 변경하거나 특정 유저의 요청을 무시하는 등 다이나믹하게 동작할 수 있다.

Untitled

유저의 요청은 사진과 같이 4단계로 나눠지는데 적용한 방법은 viewer request에서 해당 요청의 유저가 크롤러인지 판단을 한 후 origin response 단계에서 크롤러일 경우 다이나믹 og태그를 적용한 응답을 아닌 경우에는 아무런 응답을 주지 않는 방법을 적용하였다.

AWS lambda@edge 적용하는 방법

  1. S3, Cloudfront, lambda function

    람다함수를 사용하기 위해 세 부분에서 필요한 설정은 https://github.com/mikaelvesavuori/lambda-dynamic-prerenderer 를 참고하여 동일하게 설정해주었다.

  2. Lambda 함수 내 설정

  1. Lambda 함수 코드
//viewer request
const bot =
  /googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|kakaotalk-scrap|yeti|naverbot|kakaostory-og-reader|daum/g;

exports.handler = (event, context, callback) => {
  const request = event.Records[0].cf.request;
  const user_agent = request.headers["user-agent"][0]["value"].toLowerCase();

  if (user_agent) {
    const found = user_agent.match(bot);
    request.headers["is-crawler"] = [
      {
        key: "is-crawler",
        value: `${!!found}`,
      },
    ];
  }
  callback(null, request);
};
//origin response
"use strict";
const axios = require("axios");
const UrlPattern = require("url-pattern");

exports.handler = async (event, context, callback) => {
  const { request, response } = event.Records[0].cf;
  const { headers, uri } = request || {};

  //분기 처리해줄 uri 값을 UrlPattern에 전달해 주어 변수로 선언해준다.
  const pattern = new UrlPattern("/store/collection/dogfood-collection");
  const match = pattern.match(uri);

  let is_crawler = undefined;
  if ("is-crawler" in headers) {
    is_crawler = headers["is-crawler"][0].value.toLowerCase();
  }

  if (is_crawler === "true") {
    response.status = 200;
    response.headers["content-type"] = [
      {
        key: "Content-Type",
        value: "text/html",
      },
    ];
    // 꼭 캐시를 사용하지 않도록 설정해야 합니다.
    //캐시무효화 처리를 해주지 않으면 크롤러가 아닌 일반유저가 이미 생성된 캐시 콘텐츠 때문에 동일한 응답을 받을 수 있다.
    response.headers["cache-control"] = [
      {
        key: "cache-control",
        value: "no-cache, no-store, must-revalidate",
      },
    ];

    //위에서 선언한 분기 처리할 변수를 사용하여 원하고자하는 og태그 내용을 넣어준다.
    response.body = `<html><head>
                        <meta property="og:url" content="https://www.baconbox.co" />
                        <meta property="og:type" content="website" />
                        <meta property="og:locale" content="ko-KR" />
                        <meta property="og:description" content="한 달에 한 번, 알아서 찾아가는 반려견 선물 상자"/>
                        <meta property="og:site_name" content="베이컨박스" />
                        <meta name="title" property="og:title" content=${
                          match ? `BACON Store | 사료 정기배송` : "베이컨박스"
                        } />
                      </head></html>`;
  }

  callback(null, response);
};

적용 되었는지 테스트하는 법

적용 후 크롤러에 대응된 다이나믹 og태그가 삽입되었는지 테스트하는 법은 아래 사이트를 방문하여 개발 웹사이트의 url을 전달하면 된다.

cards-dev.twitter.com/validator
developers.facebook.com/tools/debug

Reference

Minsun

Minsun

Developer | Traveler | Thinker

Comments

comments powered by Disqus