July 10, 2025

๐Ÿš€ SEO ์นœํ™”์ ์ด์ง€ ์•Š์€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(CSR) ๋‚จ์šฉ: ๊ฒ€์ƒ‰ ์—”์ง„์˜ ์™ธ๋ฉด

SEO/์ ‘๊ทผ์„ฑ
์•„ํ‚คํ…์ฒ˜
๋ Œ๋”๋ง์ „๋žต
UX
JavaScript

Summary

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(CSR)๋งŒ์œผ๋กœ ์›น์‚ฌ์ดํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ๊ฒฝ์šฐ, ๊ฒ€์ƒ‰ ์—”์ง„ ํฌ๋กค๋Ÿฌ๊ฐ€ JavaScript๋ฅผ ์™„์ „ํžˆ ์‹คํ–‰ํ•˜์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜ ๋А๋ฆฌ๊ฒŒ ์ฒ˜๋ฆฌํ•˜์—ฌ SEO์— ์น˜๋ช…์ ์ธ ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค. SEO์— ์ค‘์š”ํ•œ ํŽ˜์ด์ง€์—๋Š” SSR, SSG ๋˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๋ Œ๋”๋ง ์ „๋žต์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰ ์—”์ง„ ์นœํ™”์ ์ธ ์ฝ˜ํ…์ธ ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Why Wrong?

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(CSR)์€ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ ๋นˆ HTML์„ ์ œ๊ณตํ•˜๊ณ  JavaScript๊ฐ€ ์‹คํ–‰๋œ ํ›„์—์•ผ ์ฝ˜ํ…์ธ ๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋ชจ๋˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”(SEO) ์ธก๋ฉด์—์„œ๋Š” ์น˜๋ช…์ ์ธ ์•ฝ์ ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ํŠนํžˆ Google ์™ธ์˜ ๋‹ค๋ฅธ ๊ฒ€์ƒ‰ ์—”์ง„ ํฌ๋กค๋Ÿฌ๋“ค์€ JavaScript๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์‹คํ–‰ํ•˜๊ณ  ํŒŒ์‹ฑํ•˜๋Š” ๋ฐ ํ•œ๊ณ„๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋งค์šฐ ๋А๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํฌ๋กค๋Ÿฌ๊ฐ€ ํŽ˜์ด์ง€์˜ ์‹ค์ œ ์ฝ˜ํ…์ธ ๋ฅผ ์ œ๋Œ€๋กœ ์ธ๋ฑ์‹ฑํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜์—ฌ, ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋…ธ์ถœ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋‚ฎ์€ ์ˆœ์œ„๋ฅผ ๊ธฐ๋กํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์‚ฌ์šฉ์ž๊ฐ€ ๋นˆ ํ™”๋ฉด์„ ๋ณด๋Š” ์‹œ๊ฐ„์ด ๊ธธ์–ด์ ธ Core Web Vitals(LCP, FID) ์ ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง€๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์—๋„ ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค. ์†Œ์…œ ๋ฏธ๋””์–ด ํ”Œ๋žซํผ ๋˜ํ•œ ์„œ๋ฒ„์—์„œ ์ œ๊ณต๋˜๋Š” HTML ์ฝ˜ํ…์ธ ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์—, CSR ๋ฐฉ์‹๋งŒ์œผ๋กœ๋Š” ์ ์ ˆํ•œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

How to Fix?

SEO์— ๋ฏผ๊ฐํ•œ ํŽ˜์ด์ง€๋‚˜ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๊ฐ€ ์ค‘์š”ํ•œ ํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ, ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR)์ด๋‚˜ ์ •์  ์‚ฌ์ดํŠธ ์ƒ์„ฑ(SSG)๊ณผ ๊ฐ™์€ ๋ Œ๋”๋ง ์ „๋žต์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. SSR์€ ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ HTML์„ ์ƒ์„ฑํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ๋ณด๋‚ด์ฃผ๋ฏ€๋กœ, ํฌ๋กค๋Ÿฌ๊ฐ€ ์ฆ‰์‹œ ์ฝ˜ํ…์ธ ๋ฅผ ํŒŒ์‹ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ๋„ ๋น ๋ฅธ ์ดˆ๊ธฐ ํ™”๋ฉด์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. SSG๋Š” ๋นŒ๋“œ ์‹œ์ ์— ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ HTML ํŒŒ์ผ๋กœ ์ƒ์„ฑํ•˜์—ฌ CDN์— ๋ฐฐํฌํ•จ์œผ๋กœ์จ, ๋งค์šฐ ๋น ๋ฅธ ์‘๋‹ต ์†๋„์™€ ๋›ฐ์–ด๋‚œ SEO ํšจ์œจ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. React ๊ธฐ๋ฐ˜์˜ Next.js๋‚˜ Vue ๊ธฐ๋ฐ˜์˜ Nuxt.js์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” SSR/SSG ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋ฉฐ, 'ํ•˜์ด๋“œ๋ ˆ์ด์…˜(Hydration)'์„ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML์„ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฒ€์ƒ‰ ์—”์ง„ ์นœํ™”์ ์ด๋ฉด์„œ๋„ ํ’๋ถ€ํ•œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋™์‹œ์— ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Before Code (Bad)

// src/pages/ProductList.jsx
import React, { useState, useEffect } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchProducts = async () => {
      try {
        const response = await fetch('/api/products');
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        setProducts(data);
      } catch (e) {
        setError(e);
      } finally {
        setLoading(false);
      }
    };
    fetchProducts();
  }, []); // ๋นˆ ์˜์กด์„ฑ ๋ฐฐ์—ด๋กœ ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰

  if (loading) return <div>์ƒํ’ˆ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</div>;
  if (error) return <div>์—๋Ÿฌ ๋ฐœ์ƒ: {error.message}</div>;

  return (
    <div>
      <h1>๐Ÿ›๏ธ ์ธ๊ธฐ ์ƒํ’ˆ ๋ชฉ๋ก</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            <h2>{product.name}</h2>
            <p>{product.description}</p>
            <span>๊ฐ€๊ฒฉ: {product.price}์›</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

// public/index.html (์ด ์˜ˆ์‹œ๋Š” CSR์˜ ์ผ๋ฐ˜์ ์ธ ์‹œ์ž‘์ ์ž„์„ ๋ณด์—ฌ์คŒ)
// <div id="root"></div>
// <script src="/src/index.js"></script> // JavaScript๊ฐ€ ๋กœ๋“œ๋˜๊ณ  ์‹คํ–‰๋˜์–ด์•ผ ์ฝ˜ํ…์ธ ๊ฐ€ ๋ณด์ž„

After Code (Good)

// pages/products/index.js (Next.js๋ฅผ ํ™œ์šฉํ•œ SSR/SSG ์˜ˆ์‹œ)

import React from 'react';

function ProductList({ products }) {
  // products๋Š” ์ด๋ฏธ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ props๋กœ ์ „๋‹ฌ๋œ ์ƒํƒœ
  return (
    <div>
      <h1>๐Ÿ›๏ธ ์ธ๊ธฐ ์ƒํ’ˆ ๋ชฉ๋ก</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            <h2>{product.name}</h2>
            <p>{product.description}</p>
            <span>๊ฐ€๊ฒฉ: {product.price}์›</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

// SSR (Server-Side Rendering)
// ์š”์ฒญ ์‹œ๋งˆ๋‹ค ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง
export async function getServerSideProps() {
  // ์ด ์ฝ”๋“œ๋Š” ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  const response = await fetch('http://localhost:3000/api/products'); // ์‹ค์ œ API ์—”๋“œํฌ์ธํŠธ
  const products = await response.json();

  return {
    props: {
      products, // props๋กœ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ
    },
  };
}

// ๋˜๋Š” SSG (Static Site Generation)
// ๋นŒ๋“œ ์‹œ์ ์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์ •์ ์ธ HTML ํŒŒ์ผ ์ƒ์„ฑ (์ž์ฃผ ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ์— ์ ํ•ฉ)
/*
export async function getStaticProps() {
  const response = await fetch('http://localhost:3000/api/products');
  const products = await response.json();

  return {
    props: {
      products,
    },
    revalidate: 60, // 60์ดˆ๋งˆ๋‹ค ISR(Incremental Static Regeneration) ์‹œ๋„
  };
}
*/

export default ProductList;

// ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” JS๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „๋ถ€ํ„ฐ ์™„์ „ํ•œ HTML ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
/*
<!-- ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML ์˜ˆ์‹œ (Next.js ๊ฒฐ๊ณผ) -->
<body>
  <div id="__next">
    <div>
      <h1>๐Ÿ›๏ธ ์ธ๊ธฐ ์ƒํ’ˆ ๋ชฉ๋ก</h1>
      <ul>
        <li>
          <h2>์ƒํ’ˆ 1</h2>
          <p>์ƒํ’ˆ 1์— ๋Œ€ํ•œ ์„ค๋ช…</p>
          <span>๊ฐ€๊ฒฉ: 10000์›</span>
        </li>
        <li>
          <h2>์ƒํ’ˆ 2</h2>
          <p>์ƒํ’ˆ 2์— ๋Œ€ํ•œ ์„ค๋ช…</p>
          <span>๊ฐ€๊ฒฉ: 20000์›</span>
        </li>
      </ul>
    </div>
  </div>
  <!-- ์ดํ›„ Next.js์˜ ํด๋ผ์ด์–ธํŠธ ์ธก JS๊ฐ€ ๋กœ๋“œ๋˜์–ด Hydration ์ง„ํ–‰ -->
</body>
*/