G-2: SEO 기초 — 검색 엔진과 SNS에게도 잘 보이는 페이지 만들기
목차 33
안녕하세요, 홍순구 튜터입니다. 지난 시간(G-1)에 우리는 같은 피드를 눈·소리·키보드·색 네 갈래로 다시 보면서, <button>·<nav>·<article> 같은 뜻을 가진 태그(시맨틱 마크업)와 aria-label 같은 이름표를 챙겼어요. 사람이 잘 쓰게 만드는 작업이었죠.
그런데 제가 그때 마지막에 슬쩍 흘린 말이 있어요. "구조가 뚜렷한 페이지를 좋아하는 게 한 무리 더 있다" 고요. 바로 검색 엔진이에요. 구글 같은 검색 엔진은 우리 페이지를 사람처럼 눈으로 보지 못해요. 대신 HTML 태그를 읽어서 "여기가 제목이구나, 여기가 메뉴구나" 하고 구조를 파악해요. 우리가 접근성을 위해 만든 그 구조가, 검색 엔진에게도 그대로 선물이 되는 거예요.
같은 프로필 페이지, 두 종류의 '독자'
사람 눈 → 사진·색·레이아웃을 본다 ("아, 홍순구님 프로필이네")
기계 눈 → HTML 태그·텍스트를 읽는다 (제목은? 요약은? 대표 이미지는?)
→ 지금까진 사람 눈만 챙겼다면, 오늘은 기계 눈에게도 또렷하게
오늘은 이 "기계 눈"에게 우리 페이지를 잘 설명하는 법을 배워요. 검색 결과에 어떻게 비칠지, 친구에게 링크를 보냈을 때 미리보기가 어떻게 뜰지(Open Graph), 검색 엔진이 우리 사이트를 어떻게 돌아다닐지(robots.txt·sitemap)를 다뤄요. 어려운 프로그래밍은 거의 없어요. 대부분 <head> 안에 정보 태그 몇 줄을 더하는 일이에요.
💡 오늘 수업의 핵심 — "사람 눈에 더해 기계 눈(검색 엔진·SNS 크롤러)에게도 페이지의 제목·요약·대표 이미지를 또렷이 알려, 검색 결과와 공유 미리보기에 제대로 비치게 한다." 🎯
🎯 학습 목표
- 검색 엔진이 페이지를 다루는 세 단계(크롤링·인덱싱·랭킹) 를 이해합니다.
meta description으로 검색 결과에 뜨는 요약 문구를 직접 설계하고,canonical로 중복 주소를 정리합니다.- 이제는 안 쓰는 죽은 태그(
keywords) 와 지금도 쓰는 살아 있는 태그(theme-color) 를 구분합니다. - Open Graph 태그로 SNS 공유 미리보기 카드를 만듭니다.
- robots.txt·sitemap.xml 로 크롤러에게 길을 안내합니다.
- JSON-LD 로 기계가 읽는 구조화된 데이터를 더해, 프로필을 또렷한 '명함'으로 만듭니다.
Step 1: 검색 엔진의 눈 — 크롤링·인덱싱·랭킹
SEO(Search Engine Optimization, 검색 엔진 최적화)는 말 그대로 "검색 엔진이 우리 페이지를 잘 찾고, 잘 이해하고, 검색 결과에 잘 올려 주도록 다듬는 일"이에요. 그런데 다듬으려면 먼저 검색 엔진이 어떻게 일하는지 알아야겠죠. 구글이 검색창 뒤에서 하는 일은 크게 세 단계예요.
페이지 한 장이 검색 결과에 오르기까지
검색 엔진이 한 페이지를 다루는 3단계
① 크롤링(crawling) 봇(크롤러)이 링크를 타고 돌며 페이지를 수집
│ "이 사이트에 어떤 페이지들이 있나?"
▼
② 인덱싱(indexing) 수집한 내용을 분석해 거대한 색인(index)에 저장
│ 제목·본문·메타 정보·구조를 읽어 정리
▼
③ 랭킹(ranking) 검색어가 들어오면 색인에서 골라 순서를 매김
"얼마나 잘 맞고, 얼마나 믿을 만한가"
여기서 봇(bot) 또는 크롤러(crawler)는 사람 대신 웹을 돌아다니며 페이지를 읽어 오는 프로그램이에요. 도서관 사서가 새로 들어온 책을 한 권씩 살펴 분류 카드를 만드는 모습을 떠올리면 비슷해요. 책 내용이 부실하거나 표지에 제목이 없으면 사서가 분류하기 어렵겠죠. 우리가 오늘 하는 일은 이 사서(크롤러)가 분류하기 좋게 표지와 목차를 또렷하게 다는 것이에요.
우리는 이미 절반을 해뒀어요
좋은 소식이 있어요. 지난 시간에 우리가 접근성을 위해 단 <header>·<nav>·<main>·<article> 같은 뜻 있는 태그들이, 바로 이 크롤러가 구조를 읽는 단서예요. 스크린 리더에게 "여기가 메뉴, 여기가 본문"이라고 알려 주던 그 구조가, 크롤러에게도 똑같이 읽혀요. 접근성을 챙긴 페이지는 이미 SEO의 절반을 챙긴 셈이에요. 오늘은 그 위에 "기계 눈"을 위한 정보를 몇 겹 더 얹어요.
💡 SEO는 "검색 1등 만드는 마법"이 아니에요. 검색 엔진이 우리 페이지를 오해 없이 이해하도록 돕는 기본기예요. 콘텐츠가 좋아야 순위가 오르는 건 그다음 이야기고요.
Step 2: 시맨틱 마크업이 곧 SEO
방금 "접근성을 챙긴 페이지는 SEO의 절반을 챙긴 셈"이라고 했죠. 말로만 넘기지 말고, 우리 profile.html을 크롤러의 눈으로 직접 읽어 봐요. 코드를 새로 더하진 않아요. 이미 있는 구조가 왜 SEO에 좋은지를 확인하는 시간이에요.
<!-- instagram-clone-frontend/profile.html -->
<header class="topbar"> ... </header>
<nav class="sidebar" aria-label="주요 메뉴"> ... </nav>
<main>
<section class="profile-header">
<h2 class="profile-username">soongu_hong</h2>
...
</section>
<section class="profile-posts">
<h2 class="sr-only">내 게시물</h2>
<div class="post-grid">
<figure> ... </figure>
</div>
</section>
</main>
크롤러는 이 태그들을 보고 페이지의 뼈대를 이렇게 읽어요.
크롤러가 읽는 profile.html 의 구조
<header> → "사이트 상단 영역이구나"
<nav> → "메뉴구나. 본문이 아니라 탐색 링크 묶음"
<main> → "여기가 진짜 본문이구나" (제일 중요!)
<h2> → "이 페이지의 핵심 제목: soongu_hong"
<figure> → "사진들이 모여 있구나"
만약 전부 <div> 였다면?
→ 크롤러: "음... 다 똑같은 상자네. 어디가 제목이고 본문이지?"
똑같은 화면을 <div>만으로 만들 수도 있어요. 사람 눈엔 차이가 없죠. 하지만 크롤러에겐 <div> 더미는 "구분 없는 상자 무더기"일 뿐이에요. 반대로 <main> 안의 <h2>는 "이 페이지가 무엇에 관한 것인지"를 한 번에 알려 줘요. G-1에서 우리가 <button>을 고집하고 뜻 있는 태그를 쓴 게, 접근성과 SEO 양쪽 모두에게 보상으로 돌아온 거예요.
💡 새 태그를 외우기보다, "이 영역은 무슨 뜻이지?"를 먼저 묻고 그에 맞는 태그를 고르세요. 그 습관 하나가 접근성과 SEO를 동시에 끌어올려요.
Step 3: meta description 과 canonical — 검색 결과의 첫인상
이제 본격적으로 <head>에 정보 태그를 더해요. 첫 타자는 meta description이에요. 구글에서 뭔가 검색하면, 파란 제목 아래에 회색 두세 줄 설명이 따라오죠? 그 설명을 우리가 직접 쓰는 게 meta description이에요.
검색 결과 스니펫을 직접 디자인한다
구글 검색 결과 한 칸 (스니펫)
┌──────────────────────────────────────────────┐
│ Instagram - 프로필 ← <title> │
│ instagram-clone.example.com/profile.html │
│ soongu_hong(홍순구)님의 프로필. 풀스택 │
│ 개발자 · 강의 진행 중... ← description│
└──────────────────────────────────────────────┘
여기서 중요한 사실 하나. description은 검색 순위를 직접 올려 주지 않아요. 구글은 이 문구를 "랭킹 점수"로 쓰지 않아요. 그럼 왜 공들여 쓸까요? 바로 클릭률(CTR, Click-Through Rate) 때문이에요. 검색 결과에 열 개 페이지가 나란히 떴을 때, 설명이 매력적인 페이지를 사람들이 더 많이 눌러요. 순위는 같아도 클릭이 갈리는 거죠.
우리 세 페이지에 각각 어울리는 설명을 달았어요.
<!-- instagram-clone-frontend/profile.html -->
<title>Instagram - 프로필</title>
<meta name="description" content="soongu_hong(홍순구)님의 프로필. 풀스택 개발자 · 강의 진행 중. 게시물 49개, 팔로워 338명. 개발·강의·풀스택 일상을 공유합니다.">
<link rel="canonical" href="https://instagram-clone.example.com/profile.html">
길이는 150자 안팎이 적당해요. 너무 길면 구글이 뒤를 ...으로 잘라 버려요. 그리고 페이지마다 고유한 설명을 써야 해요. 세 페이지가 똑같은 설명을 달면, 크롤러는 "이 사이트는 페이지가 다 비슷한가?" 하고 갸웃해요.
canonical — "이게 진짜 주소예요"
설명 아래 한 줄, <link rel="canonical">이 보이죠. 이건 "이 페이지의 대표 주소는 이거예요" 라고 못 박는 태그예요. 왜 필요할까요? 같은 페이지가 여러 주소로 열릴 때가 많거든요.
같은 프로필인데 주소가 여러 개라면?
https://example.com/profile.html ← 진짜
https://example.com/profile.html?ref=kakao ← 카톡에서 들어온 흔적
https://example.com/profile.html?utm=ad ← 광고에서 들어온 흔적
크롤러: "어? 똑같은 내용이 세 개? 어느 걸 색인에 올리지?"
canonical: "전부 맨 위 주소가 대표예요. 그걸로 합치세요"
canonical이 없으면 크롤러가 같은 내용을 여러 페이지로 오해해서, 점수가 분산되거나 엉뚱한 주소가 검색에 뜰 수 있어요. 한 줄로 "대표는 이거"라고 정리해 주면 그런 혼란이 사라져요.
🙋 학생 질문은 토글로 정리해 뒀어요. 펼쳐서 확인해 보세요.
🙋 학생 질문 — "튜터님, description을 안 쓰면 검색 결과 설명은 어떻게 되나요?"
비워 두면 구글이 페이지 본문에서 적당한 문장을 골라 자동으로 채워요. 검색어와 관련된 부분을 알아서 뽑아 주죠. 그래서 "안 쓰면 빈칸"은 아니에요.
그래도 직접 쓰는 게 좋은 이유는, 자동으로 뽑힌 문장이 늘 매력적이진 않기 때문이에요. 메뉴 글자나 엉뚱한 문장이 뽑힐 때도 있어요. 우리가 직접 쓰면 "이 페이지를 눌러야 할 이유"를 첫 줄에 담을 수 있어요. 광고 카피를 직접 쓰는 것과 비슷해요.
Step 4: meta 태그의 명암 — 죽은 keywords, 살아 있는 theme-color
<head>에 들어가는 <meta> 태그가 전부 쓸모 있는 건 아니에요. 한때 유행했다가 지금은 아무도 안 쓰는 태그도 있어요. 대표가 meta keywords예요. 반대로, 요즘 더 챙기는 살아 있는 태그도 있고요. 둘을 나란히 보면 "왜 어떤 건 쓰고 어떤 건 안 쓰는지" 감이 잡혀요.
meta keywords — 2009년에 은퇴한 태그
옛날 SEO 강의를 보면 이런 태그가 꼭 나와요.
<meta name="keywords" content="인스타그램, 사진, 일상, 팔로우, 좋아요">
↑ 한때 "이 페이지의 핵심 단어들"을 적던 태그
예전엔 검색 엔진이 이 단어들을 보고 순위를 매겼어요. 그런데 문제가 생겼죠. 사람들이 페이지 내용과 상관없는 인기 단어를 잔뜩 욱여넣기 시작한 거예요. "유명 연예인, 무료, 할인" 같은 단어로 도배하면 검색에 걸리니까요.
결국 신뢰를 잃었고, 구글은 2009년에 "우리는 keywords를 무시한다"고 공식 발표했어요. 지금은 구글이 아예 안 읽고, 빙(Bing)은 오히려 스팸 신호로 의심해요. 그래서 우리 코드베이스엔 이 태그를 일부러 넣지 않았어요. 넣어 봤자 효과가 없거나 해로우니까요.
theme-color — 지금도 쓰는 살아 있는 태그
반대로, 우리가 새로 넣은 살아 있는 메타 태그가 있어요. theme-color예요. 이건 모바일 브라우저의 주소 표시줄 색을 바꿔 줘요. 게다가 지난 시간 B-8에서 만든 다크 모드와 짝을 맞췄어요.
<!-- instagram-clone-frontend/profile.html -->
<!-- 모바일 브라우저 주소 표시줄 색 — 라이트/다크 모드에 맞춰 바뀌어요 (B-8 다크 모드와 짝) -->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fafafa">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000">
media 속성이 눈에 익죠? B-8에서 prefers-color-scheme로 다크 모드를 감지했잖아요. 똑같은 조건을 여기서도 써요. 폰을 라이트 모드로 쓰면 주소창이 흰색(#fafafa)으로, 다크 모드면 검정(#000000)으로 바뀌어요. 페이지 배경색과 주소창이 자연스럽게 이어져서, 앱처럼 깔끔해 보여요.
keywords theme-color
────────── ──────────────
2009년 은퇴 ✗ 지금도 현역 ✓
효과 없음/해로움 모바일 외형 개선
→ 코드에 안 넣음 → 코드에 넣음
이 둘의 교훈은 분명해요. "옛날 강의에 나온다고 다 맞는 건 아니다." 기술은 늘 바뀌어요. "지금" 쓰는지 한 번 확인하는 습관이 중요해요.
Step 5: Open Graph — SNS 공유 미리보기 카드
이제 오늘의 하이라이트예요. 카톡이나 디스코드에 링크를 붙여넣으면, 주소만 덜렁 뜨지 않고 제목·설명·대표 이미지가 담긴 카드가 예쁘게 뜨죠? 그 카드를 만드는 게 Open Graph(오픈 그래프, 줄여서 OG) 태그예요.
미리보기 카드는 어떻게 만들어질까
먼저 원리부터요. 우리가 링크를 채팅창에 붙여넣는 순간, SNS는 그 주소를 직접 열어 본다는 점이 핵심이에요.
링크를 SNS 채팅창에 붙여넣는 순간
https://.../profile.html 붙여넣기
│
▼
SNS 크롤러가 그 주소를 열어 <head> 의 og: 태그를 읽음
│ og:title · og:image · og:description ...
▼
┌────────────────────────────────┐
│ [ 대표 이미지 1200×630 ] │
│ 홍순구 (soongu_hong) │ ← 미리보기 카드 완성
│ 풀스택 개발자 · 강의 진행 중 │
└────────────────────────────────┘
크롤러는 본문을 일일이 읽는 대신, <head>에 우리가 미리 차려 둔 og: 태그만 쏙 뽑아가요. 그러니 카드를 우리가 원하는 모습으로 직접 설계할 수 있어요.
프로필 페이지에 OG 태그 달기
profile.html에 OG 태그를 달았어요. 필수는 다섯 개예요.
<!-- instagram-clone-frontend/profile.html -->
<!-- Open Graph — SNS에 공유했을 때 뜨는 미리보기 카드 (프로필 페이지) -->
<meta property="og:type" content="profile">
<meta property="og:title" content="홍순구 (soongu_hong) · Instagram 클론">
<meta property="og:description" content="풀스택 개발자 · 강의 진행 중. 게시물 49개, 팔로워 338명. 개발·강의·풀스택 일상을 공유합니다.">
<meta property="og:url" content="https://instagram-clone.example.com/profile.html">
<meta property="og:image" content="https://picsum.photos/seed/avatar1/1200/630">
다섯 개의 뜻을 하나씩 볼게요.
og:type— 페이지 종류예요. 프로필이니profile. (로그인·피드는 일반website로, 다음 Step에서 구분해요.)og:title— 카드에 굵게 뜨는 제목.<title>과 달라도 돼요. SNS용으로 더 매력적으로 써도 좋아요.og:description— 카드 설명 줄. 200자 안쪽이 안전해요.og:url— 이 콘텐츠의 대표 주소. canonical과 같은 주소를 적어요.og:image— 카드에 뜨는 대표 이미지. 이게 카드의 8할이에요. 사람들은 이미지를 먼저 보거든요.
여기에 한 가지 중요한 규칙. og:image는 가로 1200 × 세로 630 픽셀(약 1.9:1 비율) 이 표준이에요. 페이스북·카톡·디스코드가 모두 이 비율로 카드를 그려요. 정사각형 프로필 사진을 그냥 넣으면 양옆이 잘리거나 여백이 생겨 어색해져요. 그래서 우리는 같은 사진을 1200×630 크기로 가리켰어요.
우리 코드엔 이 다섯 개 외에 보강 태그도 몇 개 더 있어요.
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:site_name" content="Instagram 클론">
<meta property="og:locale" content="ko_KR">
<meta property="profile:username" content="soongu_hong">
<!-- 트위터(X)는 og: 태그를 그대로 읽어요. 카드 종류만 따로 지정해요 -->
<meta name="twitter:card" content="summary_large_image">
og:image:width/height는 이미지 크기를 미리 알려 줘서 카드가 더 빨리 떠요. og:site_name은 카드 구석에 뜨는 사이트 이름, og:locale은 언어(한국어)고요. profile:username은 og:type이 profile일 때 쓸 수 있는 전용 태그예요.
맨 아래 twitter:card 한 줄도 봐 두세요. 예전엔 트위터가 자기만의 twitter: 태그를 따로 요구했는데, 지금은 트위터(X)도 대부분 og: 태그를 그대로 읽어요. 다만 카드를 큰 이미지로 띄울지(summary_large_image) 작게 띄울지만 이 한 줄로 정해 줘요. 그래서 og:를 잘 갖추면 트위터는 거의 공짜로 따라와요.
Step 6: Open Graph 실전 — 페이지마다 다른 카드
OG 태그는 페이지마다 고유하게 달아야 해요. 프로필 카드와 로그인 카드가 똑같으면 안 되겠죠. 로그인(index.html)과 피드(feed.html)에도 각자 어울리는 OG를 달았어요. 여기서 핵심은 og:type의 구분이에요.
<!-- instagram-clone-frontend/index.html -->
<!-- Open Graph — SNS 공유 미리보기 (로그인/홈 = website) -->
<meta property="og:type" content="website">
<meta property="og:title" content="Instagram 클론 — 로그인">
<meta property="og:description" content="친한 친구의 일상 속 순간들을 확인해 보세요. 순수 HTML·CSS·JavaScript로 만든 인스타그램 클론.">
<meta property="og:url" content="https://instagram-clone.example.com/">
<meta property="og:image" content="https://picsum.photos/seed/heroapp/1200/630">
프로필은 og:type="profile"이었는데, 로그인은 og:type="website"예요. 일반 페이지라는 뜻이죠. 피드도 마찬가지로 website로 달았어요.
<!-- instagram-clone-frontend/feed.html -->
<meta property="og:type" content="website">
<meta property="og:title" content="Instagram 클론 — 피드">
<meta property="og:description" content="팔로우한 친구들의 새 게시물과 스토리를 한눈에. 좋아요와 댓글로 소통하는 타임라인.">
<meta property="og:url" content="https://instagram-clone.example.com/feed.html">
<meta property="og:image" content="https://picsum.photos/seed/insta1/1200/630">
세 페이지의 OG를 한눈에 비교하면 차이가 분명해요.
페이지 og:type 대표 이미지 seed 성격
────── ─────── ────────────── ──────
index.html website heroapp 앱 소개 (로그인)
feed.html website insta1 피드 타임라인
profile.html profile avatar1 한 사람의 프로필
이렇게 페이지마다 제목·설명·이미지·종류를 다르게 주면, 어느 페이지를 공유해도 그 페이지에 딱 맞는 카드가 떠요. 만약 모든 페이지가 같은 OG를 공유했다면, 프로필을 보내도 로그인 화면 카드가 떠서 받는 사람이 헷갈렸겠죠.
⚠️ OG 카드가 바뀌었는데 SNS에서 옛날 카드가 계속 뜰 때가 있어요. SNS가 카드를 캐시(저장) 해 두기 때문이에요. 이럴 땐 페이스북·카카오가 제공하는 "공유 디버거"에 주소를 넣고 새로고침하면 최신 카드로 갱신돼요.
Step 7: robots.txt — 크롤러에게 건네는 안내문
지금까진 <head> 안에 태그를 더했어요. 이번엔 페이지 바깥, 사이트 전체에 한 장씩 두는 파일을 만들어요. 첫 번째가 robots.txt예요.
크롤러가 우리 사이트에 도착하면, 본문을 읽기 전에 제일 먼저 robots.txt부터 확인해요. "여기 들어가도 되나요? 어디까지 봐도 되죠?"를 묻는 거죠. 가게 입구의 "영업 중 / 직원 외 출입금지" 안내문 같은 역할이에요.
# instagram-clone-frontend/public/robots.txt
# 모든 검색 엔진 크롤러에게 (User-agent: * = 전부)
User-agent: *
# 사이트 전체를 크롤링해도 좋아요
Allow: /
# 단, 비공개·관리자 영역은 크롤링하지 마세요 (예시 경로)
Disallow: /private/
# 사이트 지도 위치 — 크롤러가 여기서 전체 페이지 목록을 받아가요
Sitemap: https://instagram-clone.example.com/sitemap.xml
지시어 네 개의 뜻이에요.
User-agent: *— 누구에게 하는 말인지.*는 "모든 크롤러". 특정 봇만 콕 집을 수도 있어요(과제에서 해 봐요).Allow: /—/(사이트 전체)를 봐도 좋다.Disallow: /private/— 단,/private/로 시작하는 주소는 들어오지 마라. 로그인 후에만 보이는 개인 영역, 관리자 페이지 같은 곳을 막을 때 써요.Sitemap:— 사이트 지도가 어디 있는지 알려 줘요. 이건 다음 Step에서 만들어요.
public/ 에 두면 빌드가 알아서 옮겨 줘요
파일을 둔 위치가 public/이라는 점에 주목하세요. 우리는 E-3에서 빌드 도구(Vite) 를 도입했죠. Vite는 public/ 폴더에 둔 파일을 손대지 않고 그대로 결과물 폴더(dist/)의 맨 위로 복사해요.
instagram-clone-frontend/
├── public/
│ ├── robots.txt ─┐ npm run build
│ └── sitemap.xml │ 하면
└── dist/ ▼
├── robots.txt ← 그대로 복사 (주소: example.com/robots.txt)
└── sitemap.xml ← 그대로 복사
크롤러는 robots.txt를 반드시 사이트 맨 위 주소(example.com/robots.txt)에서 찾아요. public/에 두면 빌드가 정확히 그 위치로 옮겨 주니, 우리는 위치 걱정 없이 내용만 신경 쓰면 돼요.
⚠️
robots.txt의Disallow는 "부탁"이지 "자물쇠"가 아니에요. 예의 바른 크롤러(구글 등)는 지키지만, 악의적인 봇은 무시할 수 있어요. 진짜 비밀번호가 필요한 영역은robots.txt가 아니라 서버의 로그인·권한으로 막아야 해요. (이건 다음 과목들에서 배워요.)
Step 8: sitemap.xml — 우리 사이트의 지도
robots.txt가 "출입 안내문"이라면, sitemap.xml(사이트맵)은 "우리 사이트 전체 지도" 예요. "우리 사이트엔 이런 페이지들이 있고, 언제 바뀌었고, 얼마나 중요해요"를 한 장에 정리해 크롤러에게 건네요.
크롤러는 링크를 타고 다니며 페이지를 찾지만, 링크가 잘 안 걸린 페이지는 놓칠 수 있어요. 사이트맵은 그런 누락을 막아 줘요. "혹시 못 찾을까 봐, 전체 목록을 여기 적어 뒀어요" 하는 친절이죠.
<?xml version="1.0" encoding="UTF-8"?>
<!-- instagram-clone-frontend/public/sitemap.xml -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://instagram-clone.example.com/</loc>
<lastmod>2026-06-11</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://instagram-clone.example.com/feed.html</loc>
<lastmod>2026-06-11</lastmod>
<changefreq>hourly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://instagram-clone.example.com/profile.html</loc>
<lastmod>2026-06-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
처음 보는 .xml 형식이 낯설 수 있는데, HTML과 사촌이에요. 여는 태그·닫는 태그로 내용을 감싸는 결이 똑같죠. 구조를 뜯어보면 이래요.
<?xml ... ?>— "나는 XML 문서예요"라는 첫 줄 선언.<urlset>— 전체 목록을 감싸는 바깥 상자.<url>— 페이지 하나. 우리 사이트는 페이지가 셋이라<url>이 세 개예요.<loc>— 그 페이지의 주소(location).<lastmod>— 마지막으로 바뀐 날짜. 크롤러가 "새로 볼지" 판단할 때 참고해요.<changefreq>— 얼마나 자주 바뀌나(daily/hourly/weekly). 피드는 자주 바뀌니hourly, 프로필은 가끔이니weekly로 줬어요.<priority>— 사이트 안에서의 상대적 중요도(0.0~1.0). 홈을 1.0으로 제일 높게 줬어요.
여기서 실제로 존재하는 페이지만 적는 게 중요해요. 없는 페이지를 적으면 크롤러가 찾아갔다가 빈손으로 돌아와 신뢰를 잃어요. 그래서 우리 사이트맵엔 진짜 있는 세 페이지만 넣었어요.
robots.txt sitemap.xml
─────────── ─────────────
"어디 들어가도 "이런 페이지들이
되나요?" 있어요 (지도)"
출입 안내문 전체 목록표
Step 9: 구조화된 데이터(JSON-LD) — 기계가 읽는 명함
오늘의 마지막 한 겹이에요. 조금 더 깊지만, 한번 보면 "아하" 하게 되는 주제예요. 구조화된 데이터(structured data)라고 불러요.
지금까지 우리가 단 description이나 OG는 자연스러운 문장이었어요. "풀스택 개발자 · 강의 진행 중" 같은. 사람은 이 문장을 읽고 "아, 개발자구나" 알지만, 기계에겐 그냥 글자 덩어리예요. "홍순구"가 사람 이름인지, "338"이 팔로워 수인지 나이인지 기계는 확신하지 못해요.
구조화된 데이터는 이걸 기계가 헷갈리지 않게 항목별로 또박또박 적어 주는 명함이에요. "이름: 홍순구 / 종류: 사람 / 팔로워: 338" 처럼요.
JSON-LD — 구글이 권하는 방식
구조화된 데이터를 적는 형식은 세 가지(microdata, RDFa, JSON-LD)가 있는데, 구글은 JSON-LD를 권장해요. 본문 HTML을 건드리지 않고 <script> 블록 한 곳에 따로 모아 쓸 수 있어 깔끔하거든요. 우리 profile.html의 <head> 끝에 이렇게 넣었어요.
<!-- instagram-clone-frontend/profile.html -->
<!-- 구조화된 데이터(JSON-LD) — 사람 눈엔 안 보이지만 검색 엔진이 읽는 '명함' -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ProfilePage",
"dateModified": "2026-06-11",
"mainEntity": {
"@type": "Person",
"name": "홍순구",
"alternateName": "soongu_hong",
"description": "풀스택 개발자 · 강의 진행 중",
"image": "https://picsum.photos/seed/avatar1/300/300",
"url": "https://instagram-clone.example.com/profile.html",
"sameAs": [
"https://github.com/soongu"
],
"interactionStatistic": {
"@type": "InteractionCounter",
"interactionType": "https://schema.org/FollowAction",
"userInteractionCount": 338
}
}
}
</script>
C-4에서 다룬 객체({ })와 닮았죠? 실제로 JSON 형식이에요. 항목 하나씩 읽어 봐요.
@context— "이 명함의 단어들은schema.org사전을 따라요." schema.org는 전 세계가 약속한 항목 이름 사전이에요.@type: "ProfilePage"— 이 페이지의 종류: 프로필 페이지.mainEntity— 이 페이지의 주인공. 프로필 페이지의 주인공은 한 사람이니Person이에요.name/alternateName— 본명(홍순구)과 다른 이름(아이디 soongu_hong).image/url— 프로필 사진과 대표 주소.sameAs— "이 사람의 다른 곳 계정." 깃허브 주소를 넣었어요. 검색 엔진이 "이 홍순구 = 깃허브의 그 사람"으로 연결해요.interactionStatistic— 여기가 재밌어요. 화면에 보이던 "팔로워 338" 을 기계가 읽도록 숫자로 또박또박 적었어요.FollowAction(팔로우) 수가338이라고요.
화면에 사람 눈으로 보이던 "팔로워 338"이, 여기선 기계가 오해 없이 읽는 숫자로 한 번 더 적힌 거예요. 사람과 기계, 두 독자에게 같은 정보를 각자의 언어로 건넨 셈이죠.
제대로 적었는지 확인하는 법
JSON은 쉼표 하나만 틀려도 깨져요. 그래서 구글이 검증기(Rich Results Test, schema.org validator)를 제공해요. 배포한 주소나 코드를 붙여넣으면 "이 명함이 올바른지" 짚어 줘요. 우리 코드는 형식과 구조를 미리 확인해 두었어요. JSON-LD가 올바르면, 운이 좋을 땐 검색 결과에 별점·팔로워 수 같은 풍부한 결과(rich result)가 함께 떠요.
🙋 학생 질문을 토글로 정리했어요.
🙋 학생 질문 — "사람 눈엔 안 보이는데, 거짓으로 적으면 어떻게 되나요? 팔로워를 100만이라고 쓰면요?"
좋은 질문이에요. 기술적으론 아무 숫자나 적을 수 있어요. 하지만 검색 엔진은 바보가 아니에요. 화면에 보이는 내용과 JSON-LD가 어긋나면 의심해요. 구글은 "구조화된 데이터는 페이지에 실제로 보이는 내용과 일치해야 한다"는 규칙을 명시해 두었고, 거짓이 적발되면 풍부한 결과를 안 띄우거나 페이지에 불이익을 줘요.
그래서 원칙은 단순해요. 화면에 338이 보이면 JSON-LD도 338. 구조화된 데이터는 "있는 사실을 기계가 읽기 쉽게 다시 적는 것"이지, "없는 사실을 지어내는 도구"가 아니에요.
마무리
오늘 우리는 같은 인스타그램 페이지를 "기계 눈"의 관점으로 다시 봤어요. 사람을 위해 다진 시맨틱 구조 위에, 검색 엔진과 SNS를 위한 정보를 한 겹씩 얹었죠.
- 검색 엔진의 일: 크롤링 → 인덱싱 → 랭킹. 우리가 다듬는 건 앞의 두 단계(잘 찾고 잘 이해하게).
description+canonical: 검색 결과 요약을 직접 쓰고(클릭률↑), 대표 주소를 못 박았어요.- 죽은 태그 vs 산 태그:
keywords는 빼고,theme-color는 다크 모드와 짝지어 넣었어요. - Open Graph: SNS 미리보기 카드를 페이지마다 다르게. 이미지는 1200×630.
- robots.txt + sitemap.xml: 출입 안내문과 전체 지도를
public/에 두어 빌드가 옮기게 했어요. - JSON-LD: "팔로워 338"을 기계가 읽는 명함으로 다시 적었어요.
배포 전 SEO 체크리스트
언젠가 이 사이트를 진짜 인터넷에 올린다면, 이 다섯 가지를 점검하세요.
배포 전 한 번씩 짚어 보기
□ description 이 세 페이지 모두 고유하게 들어갔는가
□ og:image 주소가 진짜 열리는 이미지인가 (1200×630)
□ robots.txt · sitemap.xml 이 빌드 후 dist 맨 위에 있는가
□ JSON-LD 가 검증기를 통과하는가 (쉼표·괄호)
□ 화면에 보이는 정보와 메타·구조화 데이터가 일치하는가
접근성과 SEO, 그리고 다음 시간
오늘 가장 자주 나온 말이 "G-1에서 한 게 SEO에도 좋다"였죠. 우연이 아니에요. 뜻이 분명한 구조는 스크린 리더에게도, 크롤러에게도 똑같이 친절해요. 좋은 마크업 하나가 접근성과 SEO 두 보상을 동시에 주는 거예요. 이게 우리가 A-1부터 시맨틱 태그를 고집해 온 진짜 이유예요.
이렇게 접근성(G-1)과 SEO(G-2)로 "표준을 잘 지키는 페이지" 까지 다듬었어요. 지금까진 브라우저가 기본으로 주는 기능 위에서 작업했죠.
다음 시간(H-1)부터는 브라우저가 한 겹 더 깊이 숨겨 둔 강력한 기능들을 꺼내 써요. 무거운 계산을 화면이 멈추지 않게 백그라운드 일꾼(Web Worker)에게 맡기고, 인터넷이 끊겨도 동작하는 오프라인 저장소(Service Worker·Cache)로 앱처럼 든든한 웹을 만들어요. 지금까지 다진 기초가 그 깊은 기능들을 받쳐 줄 거예요. 오늘도 정말 고생 많으셨어요.
과제
오늘 만든 메타 정보들을 직접 손보고, 검증 도구로 확인해 보는 과제예요. npm run build와 npm run preview로 결과물을 띄워 두고 진행하세요.
[구현] 로그인 페이지 미리보기 카드를 내 식대로 바꾸기
index.html의 OG 태그를 손봐서, 로그인 페이지를 공유했을 때 뜨는 카드를 더 매력적으로 바꿔 보세요.
og:title과og:description을 여러분이 직접 쓴 문구로 바꿔 보세요. "친구를 초대하고 싶어지는" 카피를 고민해 보세요.og:image의 seed 값(heroapp)을 다른 단어로 바꾸면 대표 이미지가 달라져요. 마음에 드는 이미지를 골라 보세요.- 바꾼 뒤, 카드 미리보기를 확인하는 온라인 도구(예: opengraph.xyz 같은 OG 검증기)에 대해 알아보고, 실제 배포 후엔 어떻게 확인할지 정리해 보세요.
[구현] robots.txt에 특정 크롤러만 막는 규칙 추가하기
지금 우리 robots.txt는 모든 크롤러(User-agent: *)에게 같은 말을 해요. 특정 봇 하나만 막아 보세요.
public/robots.txt맨 아래에 아래 묶음을 추가하세요. 광고 수집 봇 하나를 통째로 막는 예시예요.
User-agent: BadBot
Disallow: /
npm run build후dist/robots.txt에 이 규칙이 그대로 복사됐는지 확인하세요.User-agent: *규칙과User-agent: BadBot규칙이 함께 있을 때,BadBot은 어느 규칙을 따를지 생각해 보세요. (힌트: 자기 이름이 적힌 규칙이 우선이에요.)
[탐구] description 길이를 바꿔 가며 잘림 관찰하기
profile.html의 meta description을 일부러 아주 길게(200자 넘게) 늘려 보세요.
- 검색 결과 미리보기를 보여 주는 온라인 도구(SERP preview tool)를 찾아 그 설명을 붙여넣어 보세요. 어디서
...으로 잘리나요? - 반대로 아주 짧게(20자) 줄이면 어떤 느낌인가요? "정보가 부족하다"는 인상이 드나요?
- 여러분이 생각하는 "딱 좋은 길이"는 몇 자인지, 그 이유와 함께 적어 보세요. 확인 뒤엔 원래 문구로 되돌리세요.
생각해볼 주제
정답을 적는 문제가 아니에요. 오늘 배운 것의 "왜"를 곱씹어 보는 질문들이에요. 스스로 답을 만들어 본 뒤, 예시답안과 비교해 보세요.
1. 순위를 올려 주지도 않는 description을, 왜 그렇게 공들여 쓸까?
구글은 meta description을 검색 순위 계산에 쓰지 않는다고 분명히 밝혔어요. 그렇다면 "어차피 순위에 영향 없으니 대충 쓰자"가 합리적일까요? 검색 결과에 열 개 페이지가 나란히 떴을 때, 순위가 같더라도 사람들의 손가락이 어디로 향할지 떠올려 보세요. "순위(노출)"와 "클릭(선택)"이 어떻게 다른 일인지, 그리고 description이 둘 중 무엇을 움직이는지 정리해 보세요.
2. 사람 눈엔 안 보이는 JSON-LD를, 왜 사실대로 적어야 할까?
JSON-LD는 화면에 한 글자도 안 보여요. 그러니 팔로워를 100만이라 적어도 방문자는 모르죠. 그런데도 우리는 "화면의 338과 똑같이" 적어야 한다고 배웠어요. 만약 모두가 구조화된 데이터에 거짓을 적기 시작하면, 검색 엔진과 사용자에게 무슨 일이 생길까요? 검색 엔진이 "보이는 내용과 일치해야 한다"는 규칙을 둔 이유, 그리고 그 규칙이 결국 누구를 지키는지 생각해 보세요.
3. 접근성(G-1)과 SEO(G-2)는 왜 이렇게 닮았을까?
오늘 우리는 "접근성을 챙긴 페이지는 SEO의 절반을 챙긴 셈"이라는 말을 여러 번 했어요. 스크린 리더와 검색 크롤러는 전혀 다른 목적을 가진, 전혀 다른 프로그램이에요. 그런데 둘 다 똑같이 시맨틱 마크업을 좋아하죠. 이 둘의 공통점은 무엇일까요? "사진을 눈으로 볼 수 없다"는 한 가지 조건이, 어떻게 접근성과 SEO를 같은 해법(뜻 있는 구조·대체 텍스트)으로 이끄는지 연결해 보세요.
✅ 예시 답안정답 보기
오늘 과제는 "우리가 단 메타 정보를 직접 손보고, 검증 도구로 기계의 눈에 어떻게 비치는지 확인하는" 거예요. 정답을 외우기보다, 값을 바꿔 보고 결과가 어떻게 달라지는지 직접 관찰하는 게 핵심이에요. npm run build로 빌드한 뒤 npm run preview로 결과물을 띄워 두고 진행하세요.
과제 예시답안
🎯 [과제 1 예시답안] 로그인 페이지 미리보기 카드를 내 식대로 바꾸기
핵심 접근
OG 태그는 "코드"가 아니라 "카피"에 가까워요. 같은 페이지라도 og:title·og:description을 어떻게 쓰느냐에 따라 클릭하고 싶은 카드가 되기도, 그냥 지나치는 카드가 되기도 해요. 문구를 직접 고민해 보는 게 이 과제의 진짜 목적이에요.
답안
index.html의 OG 태그를 이렇게 바꿔 볼 수 있어요. 정답은 없고, "친구를 초대하고 싶어지는" 방향이면 돼요.
<!-- instagram-clone-frontend/index.html -->
<meta property="og:title" content="지금 친구들은 뭐 할까? · Instagram 클론">
<meta property="og:description" content="로그인 한 번이면 친한 친구들의 오늘이 한눈에. 사진과 순간을 나눠요.">
<meta property="og:image" content="https://picsum.photos/seed/friends/1200/630">
og:image의 seed 값(heroapp → friends)을 바꾸면 대표 이미지가 통째로 달라져요. picsum은 같은 단어엔 늘 같은 사진을 주니, 마음에 드는 단어를 찾을 때까지 바꿔 보면 돼요.
카피만 바꿔도 카드 인상이 달라져요
Before "Instagram 클론 — 로그인"
친한 친구의 일상 속 순간들을 확인해 보세요...
After "지금 친구들은 뭐 할까? · Instagram 클론"
로그인 한 번이면 친한 친구들의 오늘이 한눈에...
↑ 질문형 제목이 호기심을 자극
바꾼 카드가 실제로 어떻게 뜨는지는, OG 미리보기를 보여 주는 온라인 도구(예: opengraph.xyz)에 배포된 주소를 넣어 확인해요. 아직 배포 전이라면, 배포 후 페이스북·카카오의 "공유 디버거"에 주소를 넣어 점검하면 된다는 흐름만 정리해 두면 충분해요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| 고유한 카피 | og:title·og:description을 자기 문구로 바꿨는가 |
상 |
| 이미지 교체 | og:image seed를 바꿔 대표 이미지가 달라짐을 확인했는가 |
상 |
| 검증 흐름 이해 | 배포 후 공유 디버거로 확인하는 흐름을 설명했는가 | 중 |
| 길이 감각 | description을 200자 안쪽으로 유지했는가 | 중 |
흔한 실수
name=과property=를 헷갈림 — OG 태그는<meta property="og:...">예요.description·theme-color는<meta name="...">이고요. OG만property라는 걸 기억하세요.- 이미지 주소가 안 열림 —
og:image에 적은 주소를 브라우저에 그대로 붙여넣어 진짜 이미지가 뜨는지 먼저 확인하세요. 안 열리는 주소면 카드에 이미지가 빠져요. - 카드가 안 바뀜 — SNS가 옛 카드를 캐시해 둬서 그래요. 공유 디버거에서 새로고침해야 최신 카드로 갱신돼요.
실무 개선 포인트 (심화)
- 실무에선
og:image를 위해 공유 전용 카드 이미지를 따로 만들어요. 페이지 사진을 그대로 쓰기보다, 제목 글자를 큼직하게 얹은 1200×630 카드를 미리 준비해 두면 어떤 환경에서도 또렷하게 떠요. - 페이지가 수백 개인 서비스는 OG 태그를 사람이 일일이 안 적어요. 페이지를 만들 때 제목·요약·대표 이미지를 받아 OG를 자동 생성하죠. 오늘 우리가 손으로 한 걸 도구가 대신해 주는 거예요. 그 도구를 다음 과목에서 만나요.
🎯 [과제 2 예시답안] robots.txt에 특정 크롤러만 막는 규칙 추가하기
핵심 접근
robots.txt는 모든 크롤러에게 똑같은 말을 할 수도, 특정 봇만 콕 집어 다른 말을 할 수도 있어요. User-agent가 "누구에게 하는 말인지"를 정하는 손잡이라는 걸 직접 써 보며 익히는 과제예요.
답안
public/robots.txt 맨 아래에 봇 하나를 통째로 막는 묶음을 더해요.
# instagram-clone-frontend/public/robots.txt
User-agent: BadBot
Disallow: /
Disallow: /는 "/(사이트 전체)에 들어오지 마라"는 뜻이에요. 위쪽 User-agent: *의 Allow: /와 정반대죠. 그럼 BadBot은 둘 중 뭘 따를까요? 자기 이름이 적힌 규칙이 우선이에요. BadBot은 User-agent: BadBot 묶음을 따라 사이트 전체를 못 봐요. 반면 이름이 안 적힌 다른 크롤러들은 여전히 User-agent: *의 Allow: /를 따라 자유롭게 봐요.
같은 robots.txt, 크롤러마다 다른 길
구글봇(이름 없음) → User-agent: * 규칙 적용 → Allow: / (다 봄)
BadBot(이름 있음) → User-agent: BadBot 규칙 적용 → Disallow: / (못 봄)
↑ 자기 이름이 적힌 규칙이 * 보다 우선
npm run build 후 dist/robots.txt를 열어 이 규칙이 그대로 복사됐는지 확인하면 끝이에요. public/에 둔 파일은 빌드가 손대지 않고 옮기니, 우리가 적은 그대로 들어가 있어요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| User-agent 지정 | 특정 봇 이름으로 묶음을 추가했는가 | 상 |
| 우선순위 이해 | 이름이 적힌 규칙이 *보다 우선임을 설명했는가 |
상 |
| 빌드 복사 확인 | dist/robots.txt에 반영됐는지 확인했는가 |
중 |
| Disallow 의미 | Disallow: /가 전체 차단임을 이해했는가 |
중 |
흔한 실수
- 한
User-agent에 다 욱여넣음 —User-agent: *와User-agent: BadBot은 별도 묶음이에요. 빈 줄로 나눠 각자의Disallow를 적어야 해요. Disallow:를 비워 둠 —Disallow:뒤를 비우면 "막을 게 없다 = 다 허용"이라는 정반대 뜻이 돼요. 전체를 막으려면 꼭Disallow: /라고/를 적어야 해요.- robots.txt로 비밀을 지키려 함 — 막고 싶은 주소를
robots.txt에 적는 건 오히려 그 주소를 공개하는 꼴이에요(누구나 robots.txt를 볼 수 있으니까). 진짜 비밀은 서버 권한으로 막아야 해요.
실무 개선 포인트 (심화)
- 요즘은 AI 학습용 크롤러를 막을지 말지가 실무 화두예요.
User-agent에 그런 봇 이름을 적어Disallow로 막는 사이트가 늘었어요.robots.txt가 "검색 노출"을 넘어 "내 콘텐츠를 누가 가져가나"를 정하는 문서로 쓰이는 거죠. - 단
robots.txt는 어디까지나 "부탁"이에요. 예의 바른 크롤러만 지켜요. 반드시 막아야 하는 건 서버에서 그 봇의 접속 자체를 거절하는 식으로 처리해요. 다음 과목들에서 서버를 배우면 이 차이가 더 또렷해져요.
🎯 [과제 3 예시답안] description 길이를 바꿔 가며 잘림 관찰하기
핵심 접근
"150자 안팎"이라는 권장 길이를 글로만 외우지 말고, 직접 늘이고 줄여 보며 어디서 잘리는지 눈으로 확인하는 탐구 과제예요. 적당한 길이의 감각을 몸으로 익히는 거죠.
답안
profile.html의 meta description을 일부러 길게 늘여 봐요.
<!-- instagram-clone-frontend/profile.html (실험) -->
<meta name="description" content="soongu_hong(홍순구)님의 프로필입니다. 풀스택 개발자이자 강의를 진행하고 있으며, 게시물 49개와 팔로워 338명을 두고 있고, 개발과 강의와 풀스택에 관한 다양한 일상을 사진과 글로 꾸준히 공유하는 계정으로, 더 많은 이야기를 보려면 팔로우해 주세요.">
이 설명을 검색 결과 미리보기 도구(SERP preview tool)에 붙여넣으면, 대략 150~160자 부근에서 ...으로 잘려요. 뒷부분 "팔로우해 주세요"는 검색 결과에 아예 안 보이는 거죠. 반대로 20자로 너무 줄이면("개발자 프로필이에요") 카드가 휑하고 "이 페이지를 눌러야 할 이유"가 안 담겨요.
description 길이별 인상 (검색 스니펫 기준)
20자 "개발자 프로필이에요" → 휑함, 정보 부족
150자 "soongu_hong(홍순구)님의 프로필. → ✓ 딱 다 보임
풀스택 개발자 · 강의 진행 중..."
250자 "...다양한 일상을 사진과 [...]" → 뒤가 잘려 메시지 손실
그래서 딱 좋은 길이는 핵심을 앞 150자 안에 다 담되, 그 안을 알차게 채우는 정도예요. 제일 중요한 말을 맨 앞에 두는 것도 요령이에요. 뒤는 잘릴 수 있으니까요. 확인했으면 원래 문구로 되돌리세요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| 잘림 관찰 | 긴 설명이 150~160자 부근에서 잘리는 걸 확인했는가 | 상 |
| 앞 배치 이해 | 핵심을 앞에 둬야 하는 이유를 설명했는가 | 상 |
| 짧음의 문제 | 너무 짧으면 정보가 부족함을 느꼈는가 | 중 |
| 되돌리기 | 실험 후 원래 문구로 원복했는가 | 중 |
흔한 실수
- 글자 수를 띄어쓰기 빼고 셈 — 잘림은 화면 너비 기준이라 공백·기호도 길이에 들어가요. "한글 몇 자"가 아니라 "보이는 폭"으로 가늠하세요.
- 한 줄로만 생각 — 검색 결과 설명은 보통 두 줄이에요. 기기·화면에 따라 보이는 양이 조금씩 달라, "정확히 몇 자"보다 "앞쪽에 핵심"이 안전해요.
- 실험값을 안 되돌림 — 250자짜리 실험 설명을 그대로 두면 실제 검색 결과가 잘린 채 노출돼요. 꼭 원복하세요.
실무 개선 포인트 (심화)
- 검색 엔진은 우리가 적은 description을 늘 그대로 쓰진 않아요. 검색어에 따라 본문에서 더 맞는 문장을 골라 바꿔 끼우기도 해요. 그래서 description은 "이렇게 보였으면" 하는 제안에 가깝고, 본문 자체도 검색어 친화적으로 잘 써 두는 게 결국 중요해요.
- 페이지가 많은 서비스는 description을 사람이 다 못 써서, 본문 첫 문단이나 요약 데이터로 자동 생성하는 틀을 둬요. 다만 자동 생성도 "앞 150자에 핵심"이라는 오늘의 원칙 위에서 동작해요.
생각해볼 주제 예시답안
1. 순위를 올려 주지도 않는 description을, 왜 그렇게 공들여 쓸까?
[문제 상황 요약]
구글은 meta description을 검색 순위 계산에 쓰지 않는다고 분명히 밝혔어요. 그렇다면 "어차피 순위에 영향 없으니 대충 쓰자"가 합리적일까요?
[튜터의 가이드 및 해설]
여기서 두 가지 일을 갈라야 해요. 노출(검색 결과에 뜨는 것)과 클릭(사람이 그걸 누르는 것)은 다른 일이에요. description은 노출 순위는 못 바꾸지만, 일단 노출된 다음 클릭을 좌우해요.
상상해 보세요. 같은 검색어에 열 개 페이지가 나란히 떴어요. 순위는 검색 엔진이 정했으니 우리 힘 밖이에요. 하지만 사람의 손가락은 제목과 설명을 슥 훑고 "이게 내가 찾던 거 같다" 싶은 걸 눌러요. 5등이어도 설명이 매력적이면 3등보다 더 눌릴 수 있어요. 이 "눌리는 비율"이 클릭률(CTR)이에요.
그래서 description은 "순위표"가 아니라 "가게 간판"이에요. 줄 세우기엔 영향이 없지만, 지나가던 사람을 멈춰 세우는 건 간판이거든요. 게다가 클릭이 잘 일어나는 페이지는 "사람들이 원하는 결과"라는 신호가 되어, 길게 보면 검색 엔진의 평가에도 간접적으로 도움이 돼요. 직접 순위 요소가 아니라고 대충 쓰는 건, 공들여 1등으로 노출시켜 놓고 간판을 백지로 두는 셈이에요.
🎯 면접관을 홀리는 핵심 멘트
"description은 랭킹 요소가 아니라 CTR 요소예요. 노출 순위는 못 바꾸지만, 노출된 뒤 클릭을 좌우하죠. 같은 순위라도 설명이 매력적인 페이지가 더 눌려요. 저는 description을 '검색 결과 속 광고 카피'로 보고, 핵심 가치를 앞 150자에 담아요. 직접 순위 요소가 아니란 이유로 방치하면, 1등 노출을 클릭으로 전환하지 못하고 흘려보내는 거예요."
2. 사람 눈엔 안 보이는 JSON-LD를, 왜 사실대로 적어야 할까?
[문제 상황 요약]
JSON-LD는 화면에 한 글자도 안 보여요. 팔로워를 100만이라 적어도 방문자는 모르죠. 그런데도 "화면의 338과 똑같이" 적어야 한다고 배웠어요. 왜일까요?
[튜터의 가이드 및 해설]
기술적으론 아무 숫자나 적을 수 있어요. 막는 문법은 없어요. 하지만 그 너머에 두 가지가 있어요 — 들킨다는 것과 신뢰가 공공재라는 것이에요.
먼저 들켜요. 검색 엔진은 화면에 실제로 보이는 내용과 구조화된 데이터를 대조해요. 구글은 "구조화된 데이터는 페이지에 보이는 내용과 일치해야 한다"는 규칙을 명시해 뒀고, 어기면 풍부한 결과를 안 띄우거나 불이익을 줘요. 화면엔 338인데 데이터엔 100만이면, 그 불일치 자체가 거짓 신고예요.
더 깊은 이유는 신뢰예요. 구조화된 데이터가 쓸모 있는 건, 검색 엔진이 그걸 "믿고" 검색 결과에 별점·팔로워 수 같은 풍부한 결과로 보여 주기 때문이에요. 그런데 모두가 거짓을 적기 시작하면? 검색 엔진은 더 이상 누구의 데이터도 못 믿어서, 결국 그 기능 자체를 안 쓰게 돼요.
한 명의 거짓이 모두의 풍부한 결과를 망가뜨리는 구조예요. 그래서 "보이는 것과 일치하라"는 규칙은 검색 엔진을 위한 게 아니라, 정직하게 적은 다른 모두를 지키는 약속이에요.
결국 구조화된 데이터의 본질은 "있는 사실을 기계가 읽기 쉽게 다시 적는 것" 이지, "없는 사실을 지어내는 도구"가 아니에요. 화면에 338이 보이면 데이터도 338. 단순하지만 이게 전부예요.
🎯 면접관을 홀리는 핵심 멘트
"구조화된 데이터는 '보이는 사실의 기계 번역'이지 마케팅 과장의 통로가 아니에요. 검색 엔진은 화면 내용과 JSON-LD를 대조해서, 불일치가 있으면 리치 결과를 회수하거나 페널티를 줘요. 더 본질적으론, 모두가 거짓을 적으면 검색 엔진이 구조화 데이터 자체를 불신하게 돼 기능이 무너져요. 그래서 저는 화면에 보이는 값과 JSON-LD를 항상 일치시켜요. 정직이 곧 리치 결과를 받을 자격입니다."
3. 접근성(G-1)과 SEO(G-2)는 왜 이렇게 닮았을까?
[문제 상황 요약]
"접근성을 챙긴 페이지는 SEO의 절반을 챙긴 셈"이라는 말을 여러 번 했어요. 스크린 리더와 검색 크롤러는 전혀 다른 프로그램인데, 둘 다 시맨틱 마크업을 좋아하죠. 이 공통점은 무엇일까요?
[튜터의 가이드 및 해설]
둘의 공통점은 한 문장으로 요약돼요. "화면을 눈으로 볼 수 없다."
스크린 리더는 시각 장애인을 위해 화면을 소리로 바꿔요. 검색 크롤러는 페이지를 수집해 색인에 넣어요. 목적은 정반대지만, 둘 다 사람의 눈으로 화면을 보지 못한다는 똑같은 조건에 놓여 있어요. 그래서 둘 다 "사진이 무엇인지", "어디가 제목인지"를 픽셀이 아니라 코드에서 알아내야 해요.
이 조건이 같으니 해법도 같아져요. 사진엔 alt(대체 텍스트)를 달아야 둘 다 사진의 뜻을 알고, <main>·<nav>·<h1> 같은 시맨틱 태그를 써야 둘 다 구조를 파악해요. <div> 더미는 스크린 리더에게도 "구분 없는 상자"고, 크롤러에게도 똑같이 "구분 없는 상자"예요. 한쪽을 위해 다진 구조가 자동으로 다른 쪽에도 통하는 거죠.
그래서 시맨틱 마크업은 한 번 일하고 두 번 보상받는 투자예요. 접근성을 위해 <button>을 고집하면 SEO가 따라오고, SEO를 위해 구조를 또렷이 하면 접근성이 따라와요. 이게 우리가 A-1부터 "겉모습이 아니라 뜻에 맞는 태그를 쓰자"고 고집해 온 진짜 이유예요. 좋은 마크업 하나가 사람과 기계 모두에게 친절한 거죠.
🎯 면접관을 홀리는 핵심 멘트
"접근성과 SEO가 닮은 건 우연이 아니에요. 스크린 리더와 검색 크롤러는 목적은 정반대지만 '화면을 눈으로 못 본다'는 조건이 같아요. 그래서 둘 다 픽셀이 아니라 마크업에서 의미를 읽고,
alt와 시맨틱 태그라는 똑같은 해법을 좋아하죠. 저는 시맨틱 HTML을 '한 번 일하고 두 번 보상받는 투자'로 봐요. 접근성을 위해 다진 구조가 SEO로 그대로 돌아오니까요."