홈으로 돌아가기

SPA를 위한 SSR: 인덱싱 설정

이 기사는 검색 엔진이 SPA 애플리케이션을 인덱싱하기 위한 서버 사이드 렌더링 설정을 다룹니다. Next.js, Nuxt.js, Angular Universal 코드 예제를 다룹니다. Core Web Vitals 및 유기 트래픽 개선.

SPA 인덱싱을 위한 SSR: React, Vue, Angular
Advertisement 728x90

SPA의 서버 사이드 렌더링(SSR): 검색 엔진 인덱싱 문제 해결 가이드

구글봇(Googlebot)이나 얀덱스봇(YandexBot) 같은 검색 크롤러는 CSR 기반 SPA를 크롤링할 때 종종 빈 HTML만 감지합니다. 서버 사이드 렌더링(SSR)이 없으면 크롤러는 자바스크립트 실행을 기다리지 않으며, 결과적으로 페이지가 인덱싱되지 않습니다. SSR은 클라이언트로 전송하기 전에 서버에서 완전히 렌더링된 HTML을 생성함으로써 이 문제를 해결합니다. 이를 통해 FCP, LCP, TTFB가 빨라지고 코어 웹 바이탈스(Core Web Vitals)와 유기적 검색 노출률이 크게 향상됩니다.

렌더링 방식 비교

| 항목 | CSR | SSR | 프리렌더링 | 정적 사이트 생성(SSG) |

|------|-----|-----|-------------|------------------------|

Google AdInline article slot

| 렌더링 위치 | 브라우저 | 서버(요청 시마다) | 사전 빌드(특정 URL 대상) | 사전 빌드(모든 페이지) |

| FCP/LCP 속도 | 느림 | 빠름 | 빠름 | 매우 빠름 |

| SEO 적합성 | 낮음 | 우수 | 양호 | 우수 |

Google AdInline article slot

| 데이터 최신성 | 실시간 | 실시간 | 오래됨 | 오래됨 |

| 서버 부하 | 최소 | 높음 | 낮음 | 최소 |

SSR은 제품 카탈로그, 뉴스 피드, 대시보드 등 동적 콘텐츠가 많은 페이지에 가장 적합합니다. 프리렌더링은 마케팅 페이지처럼 업데이트 빈도가 낮은 URL에 적합하며, SSG는 문서나 랜딩 페이지처럼 정적 콘텐츠 중심의 사이트에 이상적입니다.

Google AdInline article slot

React + Next.js: 단계별 구현 가이드

새 프로젝트 생성:

npx create-next-app@latest my-ssr-app
cd my-ssr-app
npm run dev

동적 페이지의 경우 getServerSideProps를 사용하세요:

// pages/product/[id].jsx
export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`https://api.example.com/products/${id}`);
  const product = await res.json();

  if (!res.ok) {
    return { notFound: true };
  }

  return {
    props: { product },
  };
}

export default function ProductPage({ product }) {
  return (
    <main>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </main>
  );
}

SEO 친화적인 메타 태그 추가:

import Head from 'next/head';

export default function ProductPage({ product }) {
  return (
    <> 
      <Head>
        <title>{product.name} | 쇼핑몰</title>
        <meta name="description" content={product.shortDescription} />
        <meta property="og:title" content={product.name} />
        <meta property="og:image" content={product.imageUrl} />
      </Head>
      <main>
        <h1>{product.name}</h1>
      </main>
    </>
  );
}

배포: Vercel, Netlify 또는 PM2 기반 Node.js 서버.

에러 없는 하이드레이션

하이드레이션은 클라이언트 측 React가 서버에서 렌더링된 HTML을 ‘살려내는’ 과정입니다. 렌더링 불일치를 피하려면 다음을 준수하세요:

  • 서버와 클라이언트의 데이터 상태를 항상 동기화하세요.
  • windowdocument 객체를 사용하기 전에 실행 환경을 반드시 확인하세요.
  • 브라우저 콘솔 경고를 주기적으로 모니터링하세요.
const isBrowser = typeof window !== 'undefined';

Vue + Nuxt.js: 서버 사이드 렌더링 구현

프로젝트 초기화:

npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev

서버 측 데이터 조회:

<!-- pages/product/[id].vue -->
<script setup>
const route = useRoute();
const { data: product, error } = await useFetch(
  `/api/products/${route.params.id}`
);

if (error.value) {
  throw createError({ statusCode: 404, message: '페이지를 찾을 수 없습니다' });
}

useHead({
  title: product.value?.name,
  meta: [
    { name: 'description', content: product.value?.shortDescription },
  ],
});
</script>

<template>
  <main>
    <h1>{{ product?.name }}</h1>
    <p>{{ product?.description }}</p>
  </main>
</template>

nuxt.config.ts 설정 예시:

export default defineNuxtConfig({
  ssr: true,
  app: {
    head: {
      htmlAttrs: { lang: 'ko' },
      link: [{ rel: 'canonical', href: 'https://example.com' }],
    },
  },
  nitro: {
    preset: 'node-server',
  },
});

Angular Universal: SSR 통합 방법

Angular 17+ 기준:

ng add @angular/ssr

server.ts에서 서버 엔드포인트 정의:

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
// ... 기타 import

const server = express();
// ...

server.get('*', (req, res, next) => {
  commonEngine
    .render({
      bootstrap: AppServerModule,
      documentFilePath: join(browserDistFolder, 'index.html'),
      url: req.originalUrl,
      publicPath: browserDistFolder,
      providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
    })
    .then((html) => res.send(html))
    .catch(next);
});

빌드 및 실행:

npm run build:ssr
npm run serve:ssr

플랫폼 안전하게 감지하기:

import { isPlatformBrowser } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';

const platformId = inject(PLATFORM_ID);

if (isPlatformBrowser(platformId)) {
  console.log(window.location.href);
}

헤드리스 CMS 연동 사례

Next.js + Contentful GraphQL 연동 예시:

export async function getServerSideProps({ params }) {
  const query = `
    query GetArticle($slug: String!) {
      articleCollection(where: { slug: $slug }) {
        items {
          title
          body
          seo {
            title
            description
          }
        }
      }
    }
  `;

  const res = await fetch(
    `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.CONTENTFUL_TOKEN}`,
      },
      body: JSON.stringify({ query, variables: { slug: params.slug } }),
    }
  );

  const { data } = await res.json();
  // ...
}

핵심 요약

  • SSR은 검색 크롤러에게 완전히 렌더링된 HTML을 제공해 CSR 앱의 근본적인 인덱싱 문제를 해결합니다.
  • Next.js, Nuxt.js, Angular Universal은 실제 서비스 환경에서 검증된 프로덕션급 SSR 프레임워크입니다.
  • 하이드레이션 불일치는 전체 재렌더링을 유발하므로, 런타임 환경을 항상 검사해야 합니다.
  • SSR 도입 후 FCP, LCP, TTFB 등 코어 웹 바이탈스 지표는 50–80% 개선됩니다.
  • 트래픽 급증 시 TTFB를 200ms 이하로 유지하려면 Redis 캐싱을 반드시 적용하세요.

— Editorial Team

Advertisement 728x90

다음 읽기