SEO 대응을 위한 vite‐plugin‐ssr 도입 - clappingmin/asterum_traveler GitHub Wiki

문서 목적

이 문서는 기존 SPA 구조에서 발생한 SEO 최적화 문제를 해결하고자 vite-plugin-ssr을 도입하게 된 기술적 배경과 도입 과정을 정리한 기록이다.

도입 배경 및 목표

기존 프로젝트는 React 기반의 SPA(Single Page Application)로 구성되어 있어, 클라이언트 사이드에서만 렌더링이 이루어지고 있었다. 이로 인해 다음과 같은 문제가 발생했다.

  • 검색 엔진(특히 Google 이외)에서 페이지 내용을 제대로 크롤링하지 못함
  • SNS 공유 시 메타 태그가 반영되지 않아, 링크 미리보기에 문제가 생김
  • 초기 로딩 시 사용자 경험(UX)이 자연스러우나, SEO가 완전히 무시되는 구조

따라서 검색 엔진 최적화(SEO) 대응을 위해 SSR(Server Side Rendering) 방식의 도입이 필요했고, 이를 위해 선택한 도구가 vite-plugin-ssr 이었다.

1차 개선 목표

기존의 index.html에서 고정된 meta 태그가 아닌 페이지별로 다르게 메타 태그를 적용하여 SEO 최적화를 한다.

1차 선택 방안: react-helmet-async

초기에는 SPA 구조를 그대로 유지한 채 SEO 대응을 시도하고자, 클라이언트 사이드에서 동적으로 태그를 설정할 수 있는 라이브러리인 react-helmet-async를 도입했다.

import { Helmet } from 'react-helmet-async';

<Helmet>
  <title>페이지 제목</title>
  <meta name="description" content="설명 내용" />
</Helmet>

이 방식은 라우트 변경에 따라 메타태그도 즉시 반영 가능하고 React 컴포넌트 수준에서 손쉽게 메타태그를 설정할 수 있다. 그러나 검색 엔진 크롤러(특히 JS 실행을 지원하지 않는 봇)나 SNS 링크 공유에서는 여전히 다음과 같은 문제가 발생했다.

  • 크롤러는 JS가 실행되기 전의 HTML만 보기 때문에, <title>, 등이 반영되지 않음
  • SNS 공유 시에도 링크 미리보기에 설정한 이미지, 설명, 제목 등이 정상 반영되지 않음
  • 결과적으로 SEO와 오픈그래프(OG) 대응에는 실패

이러한 한계로 인해 정적 HTML 수준에서 메타태그가 삽입되는 SSR 구조가 필요하다는 판단에 도달했다.


2차 선택 방안: vite-plugin-ssr

vite-plugin-ssr은 Vite에 최적화되어 있으며, 기존 SPA 구조에서도 SSR 기능을 도입할 수 있도록 도와주는 라이브러리이다. 1차 구현 목표인 페이지별로 다른 메타 태그 제공과 js를 실행하지 않는 크롤러가 태그를 수집할 수 있게 할 수 있다. 우선 페이지별로 다르게 메타 데이터를 json으로 작성하고 이를 default.server.ts에서 불러와서 직접 html을 구성하고 이를 escapeInject로 페이지마다 이 html을 서버에서 받아와서 나머지는 클라이언트에서 렌더링 하는 방식을 사용했다.

const title = page === "" ? metaJson["base"].title : metaJson[page].title;

...

return escapeInject`<!DOCTYPE html>
        <html lang="ko">
      <head>
        <link rel="icon" type="image/svg+xml" href="/logo_icon.svg" />
        <meta name="viewport" content="width=1920" />
        <meta charset="UTF-8">

        <!-- 기본 -->
        <title>${title}</title>
...

위의 방식으로 LightHouse SEO 100점, SNS 공유시 설정한 제목, 이미지 반영을 확인했다. 1차 목표인 SEO 최적화는 달성했다. 하지만 위의 방식의 도입으로

  • 클라이언트 전환이 hydrate가 아닌 createRoot로 구성되어 있어, SPA의 부드러운 라우팅 경험이 사라짐
  • 다이나믹 라우터 (report/image/:reportId)에서 최적화된 메타 데이터 적용 안됨 과 같은 문제를 확인할 수 있다.

1차 목표인 SEO 최적화를 위해 vite-plugin-ssr을 사용했으나 빠른 수정을 위해 _default.client.ts에서는 createRoot로 SPA 방식으로 SSR로 변경하지 않았다.

결과적으로 현재 프로젝트는 초기 진입은 SSR처럼 동작하지만, 이후 라우팅은 CSR 방식이 적용되지 않아 SSR과 SPA의 장점을 모두 온전히 살리지 못하는 구조가 되었다.

2차 개선 목표: 동적 라우팅 메타 대응 + SSR Hydrate 전환

현재까지는 정적인 메타데이터 기반으로 SSR을 구성하고 있으며, 클라이언트 렌더링은 여전히 createRoot() 기반의 CSR 방식으로 유지되고 있다. 향후 개선 과제로는 동적 라우터(report/image/:reportId)에 맞춰 SSR 시점에서 메타태그를 유연하게 설정하고, 클라이언트에서도 hydrateRoot() 기반의 SSR + Hydration 구조로 전환하여, SEO와 UX 모두를 만족시키는 방향으로 점진적인 구조 개선을 진행할 계획이다.

이를 위해 각 페이지별로 page.server.ts와 클라이언트 전용 파일을 작성해 SSR 구성을 분리하고, 기존 CSR 기반 렌더링은 hydrate 방식으로 변경할 예정이다.

⚠️ **GitHub.com Fallback** ⚠️