CSR에서 동적 OG 적용하기

2024-07-23 · 3 min read

개요

최근 회사에서 가볍고 빠른 성능의 사이트를 개발해야 하는 요구가 생겨 Vite와 Svelte를 사용한 SPA를 만들게 되었습니다. 이 사이트는 동적 라우팅을 통해 경로에 따라 백오피스에서 설정한 커스텀 페이지를 보여주어야 했습니다.

문제는 동적 경로에 따라 OG 메타태그를 적용해야 한다는 사실이었습니다. 그동안 "CSR = SEO가 어렵다"라고 거의 공식처럼 알고 있었기 때문에, 처음에는 이를 해결할 방법이 없다고 생각했습니다.

하지만 뜻밖에도 이 문제를 해결한 포스팅을 발견하고 이를 참고하여 무사히 해결할 수 있었습니다. 비슷한 고민을 하고 있는 분들께도 해당 글이 큰 도움이 될 것입니다.

이 글에서는 그 해결 과정에서 겪은 문제와 추가로 알게 된 사항들을 정리하려 합니다.

CSR에서 동적 OG 메타태그를 적용하는 방법

cloudfront-events-that-trigger-lambda-functions.png

<span style={{textAlign: 'center', margin: '0 auto', fontStyle: 'italic', color: 'var(--color-text-footer)'}}>출처: AWS 공식문서

AWS CloudFront에서는 위 그림처럼 네 가지 구간에 엣지 함수를 설정할 수 있습니다.

핵심 아이디어는 Viewer request 이벤트에 엣지 함수를 설정해서, 요청자가 봇인지 아닌지를 판별해 봇일 경우 OG 메타태그만을 포함한 HTML을 생성해 응답으로 보내는 것입니다.

즉, 봇의 요청일 때는 Viewer request 단계에서 바로 OG 메타태그를 포함한 HTML을 반환하고, 실사용자가 요청할 때는 정상적으로 CloudFront 원본(제 경우에는 S3)을 거쳐 응답을 보냅니다.

이때 주의해야 할 점은 클라이언트 사이드 라우팅이 hash 기반일 경우입니다. 해시 값은 서버로 전달되지 않으므로, 어떤 페이지를 요청하든 CloudFront는 동일한 페이지로 인식해 페이지별로 적절한 OG 태그를 적용하는 것이 불가능해집니다. 저 역시 이 문제로 인해 라우팅 라이브러리를 변경해야 했습니다.

CloudFront Functions vs Lambda@Edge

CloudFront Functions와 Lambda@Edge는 엣지 함수를 위한 주요 옵션이며, 공식 문서에서 좀더 상세한 내용을 확인할 수 있습니다. 제가 고려했던 주요 차이점은 다음과 같습니다.

CloudFront Functions Lambda@Edge
특징 간단한 작업에 적합 복잡한 작업 가능
적용 가능한 이벤트 Viewer request/response Viewer request/response,
Origin request/response
네트워크 통신 X O
외부 라이브러리 사용 X O
비용 요청당 비용 청구 요청 및 함수 지속 시간당
비용 청구

저의 경우, OG 태그를 동적으로 생성하기 위해 API 호출이 필요했기 때문에 Lambda@Edge를 선택할 수밖에 없었습니다.

람다 계층 사용

처음에는 Fetch API를 사용해 API 호출을 시도했지만, fetch 함수가 존재하지 않는다는 에러를 만났습니다. 람다 함수는 노드 환경이고 Fetch API는 웹 브라우저가 제공하는 API이기 때문입니다.

저는 그 대안으로서 Aixos를 사용하고자 했습니다. Axios의 기반이 되는 XHR 역시 브라우저 API지만, Axios는 서버 사이드에서도 동작하는 라이브러리인 만큼 노드 환경에 필요한 dependency가 갖춰져 있을 거라 생각했습니다.

람다 함수에서 외부 라이브러리를 사용하는 방법은 크게 두 가지였습니다.

  • 외부 라이브러리를 포함하는 .zip 파일을 생성해 람다 함수에 업로드하기

  • 람다 계층(Lambda layer) 을 이용해 외부 종속성을 추가하기

람다 계층은 여러 람다 함수에서 동일한 종속성을 재사용할 수 있기 때문에, 라이브러리 사용 시 권장되는 방법입니다. 저도 이 방법을 선택했고, Axios 종속성을 포함한 .zip 파일을 업로드해 계층을 생성했습니다.

계층을 구성하는 방법은 간단한데, 우선 아래와 같은 형태로 구성되도록 .zip 파일을 만든 후 AWS 콘솔에서 람다 계층을 추가할 때 업로드하면 됩니다.

axios-layer.zip
└ nodejs
  └ axios
    └ ...

람다 함수의 런타임 버전이 계층의 호환 런타임 버전에 포함되어야 한다는 점만 주의하면 계층 생성에서 크게 어려운 점은 없었습니다.

저는 Axios 종속성을 위한 계층을 생성하고 이를 람다 함수에 추가했습니다. 그리고 Lambda@Edge에는 계층 사용이 불가능하다는 사실을 뒤늦게 알게 되었습니다. ()

네트워크 통신, 그리고 해결

람다 계층을 사용할 수 없다는 것을 알게 된 후, 노드의 HTTPS 모듈을 사용해 API 호출을 구현했습니다. Lambda@Edge 관련 문서에는 이 내용이 명확히 나와 있지 않았지만, AWS 블로그에서 관련 내용을 찾아 적용할 수 있었습니다. ()

HTTPS 모듈은 사용 방법이 다소 복잡해서, 아래와 같이 한번 래핑해서 사용했습니다.

const fetchData = (url) => {
  return new Promise((resolve, reject) => {
    https
      .get(url, function (res) {
        res.setEncoding('utf8');

        let rawData = '';

        res.on('data', (chunk) => {
          rawData += chunk.toString();
        });

        res.on('end', function () {
          try {
            const parsedData = JSON.parse(rawData);
            resolve(parsedData);
          } catch (e) {
            resolve({ error: true });
          }
        });
      })
      .on('error', function (e) {
        resolve({ error: true });
      });
  });
};

또한 함수가 제대로 동작하는지 확인하기 위해 테스트 데이터를 수집하는 과정이 필요했습니다. 다음과 같은 단계로 테스트 데이터를 구해 실제 요청을 처리할 수 있었습니다.

  1. Lambda@Edge 이벤트 구조 문서에서 일반적인 Viewer request 이벤트를 구해 테스트하고, 기본적인 함수를 구성합니다.

  2. 함수 예제를 참고하여 Viewer request에서 바로 응답을 생성하는 함수를 작성합니다. 이때, response body에는 handler 함수의 event 파라미터를 JSON.stringify()로 처리하여 넣습니다.

  3. 웹사이트의 CloudFront에 람다 함수를 연결합니다. 웹사이트에 접속하면 실제 Viewer request 이벤트를 본문으로 얻을 수 있습니다.

위와 같은 과정을 거쳐 테스트 데이터를 얻고, 무사히 원하는 기능을 구현할 수 있었습니다.

결론

처음에는 CSR 환경에서 동적 OG 태그 적용이 불가능하다고 생각했으나, 인프라를 활용하여 문제를 해결할 수 있었습니다. 이 과정에서 프론트엔드만으로는 해결하기 어려운 문제도 배포 환경에서 적절한 설정으로 해결할 수 있다는 점을 확인했습니다.

이 글이 같은 문제를 겪는 분들에게 실질적인 도움이 되기를 바랍니다.

Copyright 2022-2024.hanse-kimAll right reserved.