열심히 만든 웹사이트인데 구글에 검색해도 안 나온다면, HTML을 어떻게 작성해야 검색 엔진이 내 페이지를 제대로 이해하고 노출시켜 줄까?

SEO란 — 검색 엔진 최적화의 목적과 기본 원리

SEO(Search Engine Optimization)는 검색 엔진이 웹 페이지를 잘 크롤링 하고, 정확하게 인덱싱 하고, 높은 순위로 랭킹 시키도록 최적화하는 작업입니다.

검색 엔진이 페이지를 처리하는 흐름을 간단히 정리하면 이렇습니다.

  1. 크롤링(Crawling) — 봇(Googlebot 등)이 웹을 돌아다니며 페이지를 발견
  2. ** 인덱싱(Indexing)** — 페이지의 콘텐츠를 분석하고 데이터베이스에 저장
  3. ** 랭킹(Ranking)** — 검색어와의 관련성, 페이지 품질 등을 기준으로 순위 결정

여기서 HTML이 직접적으로 영향을 주는 부분이 1번과 2번입니다. 봇은 결국 HTML을 읽는 것이기 때문에, 잘 구조화된 HTML = 검색 엔진이 이해하기 쉬운 페이지라고 볼 수 있습니다.


메타 태그 심화

<head> 안에 들어가는 메타 태그들은 검색 엔진에게 페이지의 핵심 정보를 전달하는 역할을 합니다.

title 태그

검색 결과에서 가장 눈에 띄는 파란색 링크 텍스트입니다.

HTML
<!-- 권장: 60자 이내, 핵심 키워드를 앞에 배치 -->
<title>SEO 최적화 가이드 — 메타 태그와 구조화 데이터 | 내 블로그</title>
  • 각 페이지마다 ** 고유한 title**을 지정해야 합니다
  • 너무 길면 검색 결과에서 잘립니다 (영문 60자, 한글 30자 내외)
  • 키워드 나열보다는 ** 자연스러운 문장 **이 효과적입니다

meta description

검색 결과에서 title 아래에 나오는 설명 텍스트입니다.

HTML
<!-- 권장: 155자 이내 -->
<meta name="description" content="메타 태그, Open Graph, JSON-LD 등 검색 엔진 최적화를 위한 HTML 작성법을 정리합니다.">

직접적인 랭킹 요소는 아니지만, ** 클릭률(CTR)**에 큰 영향을 줍니다. 사용자가 검색 결과를 보고 클릭할지 말지를 결정하는 요소이기 때문입니다.

robots 메타 태그

검색 엔진 봇의 행동을 제어합니다.

HTML
<!-- 인덱싱 허용, 링크 따라가기 허용 (기본값) -->
<meta name="robots" content="index, follow">

<!-- 인덱싱 금지 — 관리자 페이지 등에 사용 -->
<meta name="robots" content="noindex, nofollow">

<!-- 인덱싱은 하되 검색 결과에 캐시된 스냅샷 표시 금지 -->
<meta name="robots" content="index, follow, noarchive">
설명
index검색 결과에 포함 허용
noindex검색 결과에서 제외
follow페이지 내 링크를 따라감
nofollow페이지 내 링크를 따라가지 않음
noarchive캐시된 페이지 표시 금지

canonical URL

같은 콘텐츠가 여러 URL로 접근 가능할 때, ** 대표 URL**을 지정합니다.

HTML
<!-- 중복 콘텐츠 문제 해결 -->
<link rel="canonical" href="https://example.com/blog/seo-guide">

공부하다 보니 이 부분이 생각보다 실무에서 중요하더라고요. 예를 들어 같은 페이지에 쿼리 파라미터가 붙는 경우(?utm_source=..., ?page=1)가 많은데, canonical을 지정하지 않으면 검색 엔진이 중복 페이지로 인식해서 랭킹이 분산됩니다.


Open Graph 태그 — SNS 공유 미리보기

카카오톡, 슬랙, 페이스북 등에서 링크를 공유하면 미리보기 카드가 뜨는데, 이게 바로 Open Graph 태그를 읽어서 보여주는 것입니다.

HTML
<head>
  <!-- 기본 Open Graph 태그 -->
  <meta property="og:type" content="article">
  <meta property="og:title" content="SEO 최적화 가이드">
  <meta property="og:description" content="메타 태그부터 구조화 데이터까지 SEO 핵심 정리">
  <meta property="og:image" content="https://example.com/images/seo-thumbnail.png">
  <meta property="og:url" content="https://example.com/blog/seo-guide">
  <meta property="og:site_name" content="내 블로그">

  <!-- 이미지 권장 크기 -->
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
</head>

** 자주 실수하는 포인트:**

  • og:image의 URL은 ** 절대 경로 **여야 합니다 (상대 경로 X)
  • 이미지 권장 크기는 1200×630px — 이보다 작으면 플랫폼에 따라 안 나올 수 있습니다
  • og:url은 canonical URL과 동일하게 맞춰주는 게 좋습니다

Twitter Card 태그

트위터(X)에서 링크 공유 시 보이는 카드를 제어합니다. Open Graph와 비슷하지만 별도로 지정해야 합니다.

HTML
<!-- 큰 이미지 카드 -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="SEO 최적화 가이드">
<meta name="twitter:description" content="메타 태그부터 구조화 데이터까지">
<meta name="twitter:image" content="https://example.com/images/seo-thumbnail.png">
<meta name="twitter:site" content="@myblog">
card 타입설명
summary작은 썸네일 + 제목/설명
summary_large_image큰 이미지 + 제목/설명
player동영상/오디오 플레이어

실무에서는 Open Graph와 Twitter Card를 ** 둘 다** 넣는 게 일반적입니다. 트위터는 자체 태그가 없으면 OG 태그를 폴백으로 사용하긴 하지만, 카드 타입 지정을 위해 최소한 twitter:card는 넣어주는 게 좋습니다.


구조화 데이터(JSON-LD)

구조화 데이터는 검색 엔진에게 페이지의 콘텐츠를 ** 기계가 읽을 수 있는 형식 **으로 알려주는 방법입니다. 검색 결과에 별점, FAQ, 레시피 시간 같은 ** 리치 결과(Rich Result)**가 뜨는 건 전부 구조화 데이터 덕분입니다.

Google은 JSON-LD 형식을 권장합니다.

Article 스키마

블로그 글이나 뉴스 기사에 사용합니다.

HTML
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "SEO 최적화 가이드",
  "description": "메타 태그부터 구조화 데이터까지 SEO 핵심 정리",
  "author": {
    "@type": "Person",
    "name": "홍길동"
  },
  "datePublished": "2026-03-28",
  "dateModified": "2026-03-28",
  "image": "https://example.com/images/seo-thumbnail.png"
}
</script>

FAQ 스키마

자주 묻는 질문 형식의 콘텐츠에 사용합니다. 검색 결과에 질문-답변이 펼쳐져서 노출됩니다.

HTML
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "SEO란 무엇인가요?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "검색 엔진 최적화로, 웹 페이지가 검색 결과에 잘 노출되도록 최적화하는 작업입니다."
      }
    },
    {
      "@type": "Question",
      "name": "구조화 데이터는 왜 필요한가요?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "검색 엔진이 콘텐츠를 더 정확하게 이해하고, 리치 결과로 표시할 수 있게 해줍니다."
      }
    }
  ]
}
</script>

BreadcrumbList 스키마

페이지의 계층 구조(경로)를 검색 결과에 표시합니다.

HTML
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "홈",
      "item": "https://example.com"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "블로그",
      "item": "https://example.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "SEO 최적화 가이드"
    }
  ]
}
</script>

구조화 데이터를 추가한 후에는 Google 리치 결과 테스트로 유효성을 확인하는 습관을 들이면 좋습니다.


시맨틱 마크업과 SEO

검색 엔진은 HTML 태그의 ** 의미(semantics)**를 파악합니다. <div>로 도배된 페이지보다 시맨틱 태그를 적절히 사용한 페이지를 더 잘 이해합니다.

heading 계층 구조

HTML
<!-- 좋은 예 — 논리적 계층 구조 -->
<h1>SEO 최적화 가이드</h1>
  <h2>메타 태그</h2>
    <h3>title 태그</h3>
    <h3>meta description</h3>
  <h2>구조화 데이터</h2>
    <h3>Article 스키마</h3>

<!-- 나쁜 예 — 계층 건너뛰기 -->
<h1>SEO 최적화 가이드</h1>
  <h4>메타 태그</h4>  <!-- h2를 건너뛰었음 -->
  <h2>구조화 데이터</h2>
  • h1은 페이지당 하나 만 사용하는 것이 권장됩니다
  • heading 레벨을 건너뛰지 않습니다 (h1 → h3 X, h1 → h2 → h3 O)

랜드마크 태그

HTML
<body>
  <header><!-- 사이트 헤더 --></header>
  <nav><!-- 네비게이션 --></nav>
  <main>
    <article>
      <!-- 본문 콘텐츠 — 검색 엔진이 핵심 콘텐츠로 인식 -->
      <section>
        <h2>섹션 제목</h2>
        <p>내용...</p>
      </section>
    </article>
    <aside><!-- 사이드바, 관련 글 --></aside>
  </main>
  <footer><!-- 푸터 --></footer>
</body>

<main>, <article>, <nav> 같은 랜드마크 태그는 검색 엔진이 페이지 구조를 파악하는 데 도움을 줍니다.

이미지 alt 텍스트

HTML
<!-- 좋은 예 — 이미지를 설명 -->
<img src="seo-diagram.png" alt="검색 엔진 크롤링-인덱싱-랭킹 흐름도">

<!-- 나쁜 예 -->
<img src="seo-diagram.png" alt="이미지">
<img src="seo-diagram.png">  <!-- alt 속성 누락 -->

alt 텍스트는 이미지 검색 SEO에도 영향을 주고, 접근성(스크린 리더) 측면에서도 필수입니다.


robots.txt와 sitemap.xml

robots.txt

사이트 루트에 위치하며, 크롤러의 접근을 제어합니다.

TXT
# /robots.txt
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /api/

# 사이트맵 위치 알려주기
Sitemap: https://example.com/sitemap.xml

주의할 점은, Disallow는 크롤링을 막을 뿐 **인덱싱을 막지는 않습니다 **. 다른 페이지에서 링크가 걸려 있으면 인덱싱될 수 있습니다. 인덱싱 자체를 막으려면 noindex 메타 태그를 사용해야 합니다.

sitemap.xml

사이트의 모든 페이지 목록을 검색 엔진에 알려줍니다.

XML
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2026-03-28</lastmod>
    <changefreq>weekly</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://example.com/blog/seo-guide</loc>
    <lastmod>2026-03-28</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

Next.js에서는 app/sitemap.ts를 만들면 자동으로 생성할 수 있어서 편리합니다.


Core Web Vitals와 SEO

Google은 2021년부터 Core Web Vitals를 ** 랭킹 시그널 **로 사용하고 있습니다. 콘텐츠 품질이 비슷하다면, 성능이 좋은 페이지가 더 높은 순위를 받습니다.

세 가지 핵심 지표

지표의미기준
LCP (Largest Contentful Paint)가장 큰 콘텐츠 요소가 렌더링되는 시간2.5초 이내
INP (Interaction to Next Paint)사용자 상호작용 후 다음 화면 업데이트까지 시간200ms 이내
CLS (Cumulative Layout Shift)레이아웃이 예기치 않게 밀리는 정도0.1 이하

참고로 FID(First Input Delay)는 2024년 3월부터 INP로 대체되었습니다.

HTML에서 할 수 있는 최적화

HTML
<!-- LCP 개선: 핵심 이미지에 fetchpriority 지정 -->
<img src="hero.jpg" alt="히어로 이미지" fetchpriority="high" width="1200" height="600">

<!-- CLS 개선: 이미지에 width/height 명시하여 레이아웃 시프트 방지 -->
<img src="photo.jpg" alt="사진" width="800" height="400">

<!-- LCP 개선: 크리티컬 리소스 프리로드 -->
<link rel="preload" as="image" href="hero.jpg">
<link rel="preload" as="font" href="/fonts/main.woff2" type="font/woff2" crossorigin>

<!-- CLS 개선: 폰트 로딩 중 레이아웃 시프트 방지 -->
<style>
  /* font-display: swap으로 폰트 로딩 전에도 텍스트 표시 */
  @font-face {
    font-family: 'MyFont';
    src: url('/fonts/main.woff2') format('woff2');
    font-display: swap;
  }
</style>

SPA의 SEO 문제와 해결법

React, Vue 같은 SPA(Single Page Application)는 태생적으로 SEO에 불리합니다.

왜 문제가 되는가

HTML
<!-- SPA의 초기 HTML — 봇이 보는 것 -->
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>

JavaScript가 실행되기 전까지 <div id="root">는 텅 비어 있습니다. Googlebot은 JavaScript를 실행할 수 있지만, 리소스가 많이 들기 때문에 모든 페이지를 완벽하게 렌더링하지는 않습니다. 다른 검색 엔진(네이버, 빙 등)은 JS 렌더링 지원이 더 제한적입니다.

해결 방법

방식설명적합한 경우
SSR (Server-Side Rendering)요청마다 서버에서 HTML 생성실시간 데이터가 필요한 페이지
SSG (Static Site Generation)빌드 시점에 HTML 미리 생성블로그, 문서 등 정적 콘텐츠
ISR (Incremental Static Regeneration)SSG + 주기적 재생성자주 바뀌지만 실시간은 아닌 콘텐츠
Dynamic Rendering봇에게만 서버 렌더링된 HTML 제공레거시 SPA에서 임시 방편

Next.js를 사용하면 이 문제를 자연스럽게 해결할 수 있습니다.

TSX
// Next.js App Router — 기본이 서버 컴포넌트(SSR)
// 별도 설정 없이도 SEO 친화적인 HTML이 생성됨

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await getPost(slug);

  return {
    title: post.title,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      images: [post.thumbnail],
    },
  };
}

// SSG — 빌드 시점에 모든 경로 미리 생성
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

정리

SEO 최적화를 위한 HTML 체크리스트를 정리하면 이렇습니다.

  • ** 메타 태그 **: 각 페이지마다 고유한 titledescription 작성
  • canonical URL: 중복 페이지 문제 방지
  • Open Graph / Twitter Card: SNS 공유 시 미리보기 최적화
  • ** 구조화 데이터(JSON-LD)**: 리치 결과 노출을 위한 스키마 마크업
  • ** 시맨틱 HTML**: heading 계층, 랜드마크 태그, alt 텍스트
  • robots.txt / sitemap.xml: 크롤링 제어와 페이지 목록 제공
  • Core Web Vitals: LCP, INP, CLS 최적화
  • ** 렌더링 전략 **: SPA라면 SSR/SSG로 크롤러가 읽을 수 있는 HTML 보장

결국 SEO의 핵심은 "검색 엔진도 사용자와 마찬가지로 HTML을 읽는다"는 사실을 인식하는 것입니다. 잘 구조화된 HTML, 명확한 메타 정보, 빠른 로딩 — 이 세 가지를 갖추면 기본적인 SEO는 충분히 커버됩니다.

댓글 로딩 중...