[시리즈 4부] 고급 시나리오별 Canonical 전략|가나투데이
[시리즈 4부] 고급 시나리오별 Canonical 전략|가나투데이
"이커머스 천 개 상품, 다국어 사이트, AMP...
복잡한 상황일수록 Canonical이 더 중요합니다."
왜 고급 전략이 필요한가?
기본 canonical 설정으로 해결되는 것:
- ✅ 단순 중복 페이지
- ✅ 파라미터 URL 몇 개
- ✅ www/non-www 통합
하지만 이런 상황은?
❓ 상품 1,000개 × 색상 5개 × 사이즈 7개 = 35,000개 URL
❓ 한국어/영어/일본어/중국어 × 10개 국가 = 40가지 조합
❓ 데스크톱 + 모바일 + AMP = 3배 페이지
❓ React SPA로 URL은 같은데 콘텐츠가 JavaScript로 로딩
이 장에서 배울 것: 복잡한 상황을 체계적으로, 자동화하여 처리하는 실전 전략
Part 1: 다국어/다지역 사이트 - Hreflang × Canonical 마스터 조합
🌍 핵심 개념: Canonical ≠ Hreflang
많은 사람들이 혼동하는 부분:
| 기능 | 목적 | 사용 시점 |
|---|---|---|
| Canonical | 중복 콘텐츠 해결 | 같은 언어, 다른 URL |
| Hreflang | 언어/지역 타겟팅 | 다른 언어, 같은 콘텐츠 |
📐 시나리오별 설정법
케이스 1: 완전히 독립된 언어 버전 (권장)
사이트 구조:
한국어: https://site.com/ko/products
영어: https://site.com/en/products
일본어: https://site.com/ja/products
각 페이지는 자기 자신을 canonical로 지정:
한국어 페이지 (/ko/products):
<!-- Canonical: 자기 자신 -->
<link rel="canonical" href="https://site.com/ko/products" />
<!-- Hreflang: 언어 관계 표시 -->
<link rel="alternate" hreflang="ko" href="https://site.com/ko/products" />
<link rel="alternate" hreflang="en" href="https://site.com/en/products" />
<link rel="alternate" hreflang="ja" href="https://site.com/ja/products" />
<link rel="alternate" hreflang="x-default" href="https://site.com/en/products" />
영어 페이지 (/en/products): 동일 패턴 일본어 페이지 (/ja/products): 동일 패턴
핵심 규칙:
- ✅ 각 언어 페이지는 자기 자신을 canonical로
- ✅ 모든 언어 버전을 hreflang으로 연결
- ✅ x-default는 기본 언어 (보통 영어)
케이스 2: 국가별 도메인
사이트 구조:
한국: https://example.kr/products
미국: https://example.com/products
일본: https://example.jp/products
한국 사이트 (example.kr):
<link rel="canonical" href="https://example.kr/products" />
<link rel="alternate" hreflang="ko-KR" href="https://example.kr/products" />
<link rel="alternate" hreflang="en-US" href="https://example.com/products" />
<link rel="alternate" hreflang="ja-JP" href="https://example.jp/products" />
<link rel="alternate" hreflang="x-default" href="https://example.com/products" />
ISO 639-1 (언어) + ISO 3166-1 (국가) 형식:
ko-KR: 한국어, 한국en-US: 영어, 미국ja-JP: 일본어, 일본
케이스 3: 같은 언어, 다른 지역
사이트 구조:
미국 영어: /en-us/products
영국 영어: /en-gb/products
호주 영어: /en-au/products
미국 페이지:
<link rel="canonical" href="https://site.com/en-us/products" />
<link rel="alternate" hreflang="en-US" href="https://site.com/en-us/products" />
<link rel="alternate" hreflang="en-GB" href="https://site.com/en-gb/products" />
<link rel="alternate" hreflang="en-AU" href="https://site.com/en-au/products" />
<link rel="alternate" hreflang="en" href="https://site.com/en-us/products" />
주의: hreflang="en" (언어만) 추가 → 지역 미지정 사용자용
🚫 절대 하지 말아야 할 실수
실수 1: 모든 언어가 하나를 canonical로 지정
<!-- ❌❌❌ 대참사 코드 ❌❌❌ -->
<!-- 한국어 페이지 -->
<link rel="canonical" href="https://site.com/en/products" />
<!-- 일본어 페이지 -->
<link rel="canonical" href="https://site.com/en/products" />
결과:
- 한국어, 일본어 페이지가 인덱싱 안 됨
- 해당 국가에서 검색 결과 0건
- 글로벌 트래픽 -70%
실제 사례:
글로벌 SaaS 기업 B사는 이 실수로 6개월간 한국/일본 시장에서 트래픽 0건. 수정 후 3개월 만에 월 방문자 2만 회복.
실수 2: Hreflang 불일치
<!-- 한국어 페이지 -->
<link rel="alternate" hreflang="ko" href="https://site.com/ko/products" />
<link rel="alternate" hreflang="en" href="https://site.com/en/products" />
<!-- 영어 페이지 (일본어 누락!) -->
<link rel="alternate" hreflang="en" href="https://site.com/en/products" />
<link rel="alternate" hreflang="ko" href="https://site.com/ko/products" />
<!-- ja 누락 → 구글 혼란 -->
규칙: 모든 언어 페이지에 동일한 hreflang 세트 포함
🛠️ 자동화 도구 및 템플릿
WordPress + WPML 플러그인
// functions.php에 추가
function wpml_hreflang_canonical() {
global $sitepress;
// 현재 페이지 언어
$current_lang = ICL_LANGUAGE_CODE;
// Canonical: 자기 자신
echo '<link rel="canonical" href="' . get_permalink() . '" />';
// Hreflang: 모든 언어 버전
$languages = $sitepress->get_active_languages();
foreach ($languages as $lang) {
$url = apply_filters('wpml_permalink', get_permalink(), $lang['code']);
echo '<link rel="alternate" hreflang="' . $lang['code'] . '" href="' . $url . '" />';
}
// x-default
$default_url = apply_filters('wpml_permalink', get_permalink(), 'en');
echo '<link rel="alternate" hreflang="x-default" href="' . $default_url . '" />';
}
add_action('wp_head', 'wpml_hreflang_canonical');
Next.js (동적 생성)
// components/MultilingualHead.tsx
import Head from 'next/head'
import { useRouter } from 'next/router'
const languages = ['ko', 'en', 'ja']
export default function MultilingualHead() {
const router = useRouter()
const currentPath = router.asPath.replace(/^\/[a-z]{2}/, '') // 언어 코드 제거
return (
<Head>
{/* Canonical: 현재 언어 */}
<link rel="canonical" href={`https://site.com${router.asPath}`} />
{/* Hreflang: 모든 언어 */}
{languages.map(lang => (
<link
key={lang}
rel="alternate"
hreflang={lang}
href={`https://site.com/${lang}${currentPath}`}
/>
))}
{/* x-default */}
<link rel="alternate" hreflang="x-default" href={`https://site.com/en${currentPath}`} />
</Head>
)
}
📊 검증 도구
Google Search Console:
설정 → 국제 타겟팅 → 언어
경고 확인:
- "hreflang 태그에 반환 태그가 없습니다"
- "hreflang 값이 잘못되었습니다"
Hreflang Tags Testing Tool:
https://www.sistrix.com/hreflang-validator/
URL 입력 → 자동 검증
Part 2: 이커머스 대량 상품 자동화 전략
🛒 문제 정의: 조합 폭발
일반적인 이커머스 상황:
상품 수: 1,000개
변형 옵션:
- 색상: 5가지
- 사이즈: 7가지
- 재질: 3가지
총 URL: 1,000 × 5 × 7 × 3 = 105,000개
수동 설정 불가능 → 자동화 필수
🎯 전략 1: 마스터 SKU 방식 (권장)
개념
마스터: /products/tshirt (기본 옵션)
변형 1: /products/tshirt?variant=red-large
변형 2: /products/tshirt?variant=blue-medium
변형 3: /products/tshirt?variant=green-small
모든 변형이 마스터를 canonical로 지정:
<!-- 변형 페이지들 -->
<link rel="canonical" href="https://shop.com/products/tshirt" />
Shopify 구현 (Liquid)
<!-- theme.liquid 또는 product.liquid -->
{% if product %}
<link rel="canonical" href="{{ shop.url }}{{ product.url }}" />
{% endif %}
자동 효과:
- 모든 색상/사이즈 URL이 자동으로 기본 상품 페이지를 canonical로 지정
- 파라미터(?variant=...) 무시
- SEO 신호 통합
WooCommerce 구현 (PHP)
// functions.php
add_action('wp_head', 'woocommerce_canonical_variations', 1);
function woocommerce_canonical_variations() {
if (is_product()) {
global $post;
// 변형 상품이든 단일 상품이든 메인 URL로
$canonical = get_permalink($post->ID);
// 파라미터 제거
$canonical = strtok($canonical, '?');
echo '<link rel="canonical" href="' . esc_url($canonical) . '" />';
}
}
Magento 구현
<!-- app/design/frontend/[theme]/Magento_Catalog/layout/catalog_product_view.xml -->
<referenceBlock name="head.additional">
<block class="Magento\Framework\View\Element\Template" name="product.canonical">
<arguments>
<argument name="template" xsi:type="string">Magento_Catalog::product/canonical.phtml</argument>
</arguments>
</block>
</referenceBlock>
<!-- canonical.phtml -->
<?php
$product = $block->getProduct();
$canonicalUrl = $product->getUrlModel()->getUrl($product, ['_ignore_category' => true]);
?>
<link rel="canonical" href="<?= $block->escapeUrl($canonicalUrl) ?>" />
🎯 전략 2: 독립 SKU 방식 (차별화 콘텐츠 있을 때)
언제 사용?
각 색상마다:
- 다른 상세 설명
- 다른 리뷰
- 다른 이미지 세트
- 다른 가격
→ 실질적으로 다른 상품
이 경우:
<!-- 빨간 티셔츠 -->
<link rel="canonical" href="https://shop.com/products/tshirt-red" />
<!-- 파란 티셔츠 -->
<link rel="canonical" href="https://shop.com/products/tshirt-blue" />
각각 독립 페이지로 SEO
🎯 전략 3: 하이브리드 방식
주요 변형 (색상): 독립 페이지
부차 변형 (사이즈): canonical로 통합
예시:
/tshirt-red (독립)
↳ /tshirt-red?size=S → canonical: /tshirt-red
↳ /tshirt-red?size=M → canonical: /tshirt-red
↳ /tshirt-red?size=L → canonical: /tshirt-red
/tshirt-blue (독립)
↳ /tshirt-blue?size=S → canonical: /tshirt-blue
↳ /tshirt-blue?size=M → canonical: /tshirt-blue
📊 자동 검증 스크립트
Python (대량 상품 검증)
import requests
from bs4 import BeautifulSoup
import csv
def check_canonical(url):
try:
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
canonical = soup.find('link', {'rel': 'canonical'})
if canonical:
return canonical.get('href')
else:
return 'MISSING'
except Exception as e:
return f'ERROR: {e}'
# 상품 URL 리스트
products = [
'https://shop.com/products/tshirt-red',
'https://shop.com/products/tshirt-blue',
# ... 1,000개
]
# 결과 저장
with open('canonical_audit.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['URL', 'Canonical', 'Status'])
for url in products:
canonical = check_canonical(url)
status = 'OK' if canonical != 'MISSING' else 'MISSING'
writer.writerow([url, canonical, status])
print(f'{url} → {canonical}')
print('검증 완료: canonical_audit.csv 확인')
💡 Pro Tip: 필터 페이지 처리
문제:
/products?category=shoes&color=red&price=low&brand=nike&size=270
해결 1: 모든 필터를 기본 카테고리로
<link rel="canonical" href="https://shop.com/products?category=shoes" />
해결 2: SEO 가치 있는 조합만 독립
/products?category=shoes&color=red → 독립 (인기 조합)
/products?category=shoes&color=red&size=270 → canonical: 위로
Shopify Collection Canonical:
{% if collection %}
<link rel="canonical" href="{{ shop.url }}{{ collection.url }}" />
{% endif %}
Part 3: 페이지네이션 고급 테크닉
📄 기본 vs 고급
기본 방식 (3부에서 다룸)
<!-- 각 페이지는 자기 자신 -->
<link rel="canonical" href="https://site.com/blog/page/2/" />
고급 방식: View All 페이지 활용
문제: 10페이지 블로그 아카이브
→ 깊은 페이지의 글이 잘 노출 안 됨
해결: "전체 보기" 페이지 생성
구조:
페이지 1: /blog/ (20개 글)
페이지 2: /blog/page/2/ (20개 글)
...
페이지 10: /blog/page/10/ (20개 글)
전체: /blog/all (200개 글 한 번에)
모든 페이지네이션이 "전체"를 canonical로:
<!-- 페이지 1, 2, 3... 모두 -->
<link rel="canonical" href="https://site.com/blog/all" />
장점:
- 모든 글이 하나의 강력한 페이지에 통합
- 크롤 예산 절약
- 순위 집중
단점:
- 페이지 로딩 느릴 수 있음 (무한 스크롤로 해결)
- 사용자 경험 고려 필요
🔄 rel="prev/next" (2019년 폐지되었지만 여전히 유용)
Google 공식 입장 (2019년):
"We no longer use prev/next in our indexing."
하지만:
- Bing, Yandex 등 다른 검색엔진은 여전히 사용
- 사용자 경험 향상 (브라우저 prefetch)
- 해가 되지 않으므로 포함 권장
<!-- 2페이지 -->
<link rel="canonical" href="https://site.com/blog/page/2/" />
<link rel="prev" href="https://site.com/blog/" />
<link rel="next" href="https://site.com/blog/page/3/" />
📱 무한 스크롤 페이지 처리
문제:
사용자가 스크롤할 때마다 새 콘텐츠 로딩
→ URL은 변하지 않음
→ 크롤러는 처음 화면만 봄
해결 1: Pushstate로 URL 변경
// 스크롤 시 URL 업데이트
window.history.pushState({}, '', '/blog/page/2');
// 동시에 canonical도 업데이트
document.querySelector('link[rel="canonical"]').href =
'https://site.com/blog/page/2/';
해결 2: Paginated Component 사용
<!-- 각 로딩된 섹션에 표시 -->
<div data-page="2" data-canonical="https://site.com/blog/page/2/">
<!-- 콘텐츠 -->
</div>
Part 4: AMP, 모바일 버전, PDF 처리
⚡ AMP (Accelerated Mobile Pages)
구조
일반 페이지: https://site.com/article
AMP 페이지: https://site.com/article/amp 또는 https://amp.site.com/article
일반 페이지 설정
<!-- https://site.com/article -->
<link rel="canonical" href="https://site.com/article" />
<link rel="amphtml" href="https://site.com/article/amp" />
의미:
- canonical: 이 페이지가 원본
- amphtml: AMP 버전은 여기
AMP 페이지 설정
<!-- https://site.com/article/amp -->
<link rel="canonical" href="https://site.com/article" />
의미: AMP는 원본을 가리킴 (자기 자신 아님!)
검증
AMP Test Tool:
https://search.google.com/test/amp
URL 입력 → canonical 링크 확인
📱 모바일 별도 URL (M-dot)
2026년 현재 권장 안 함 (반응형 디자인 사용)
하지만 레거시 사이트는:
데스크톱: https://site.com/page
모바일: https://m.site.com/page
데스크톱 페이지:
<link rel="canonical" href="https://site.com/page" />
<link rel="alternate" media="only screen and (max-width: 640px)"
href="https://m.site.com/page" />
모바일 페이지:
<link rel="canonical" href="https://site.com/page" />
<link rel="alternate" media="only screen and (min-width: 641px)"
href="https://site.com/page" />
📄 PDF 및 다운로드 파일
HTTP 헤더 Canonical (HTML 태그 불가능할 때)
HTTP/1.1 200 OK
Content-Type: application/pdf
Link: <https://site.com/document>; rel="canonical"
서버 설정 (Apache .htaccess):
<FilesMatch "\.pdf$">
Header set Link '<https://site.com/documents>; rel="canonical"'
</FilesMatch>
서버 설정 (Nginx):
location ~* \.pdf$ {
add_header Link '<https://site.com/documents>; rel="canonical"';
}
HTML 래퍼 페이지 (더 나은 방법)
PDF 직접 URL: /files/report.pdf
HTML 페이지: /reports/annual-2025
HTML 페이지에서:
- PDF 소개
- 다운로드 버튼
- Canonical: 자기 자신
SEO 이점:
- HTML 페이지가 인덱싱 (PDF보다 유리)
- 메타 태그, 설명 추가 가능
- 사용자 경험 향상
Part 5: JavaScript 렌더링 사이트 (SPA) 특수 케이스
⚛️ 문제: CSR (Client-Side Rendering)
React, Vue, Angular 등:
- 초기 HTML: 거의 비어있음
- JavaScript로 콘텐츠 렌더링
- Canonical 태그도 JS로 추가
문제: 구글봇이 JS 실행 전에 크롤 → canonical 못 봄
🔧 해결책
방법 1: SSR (Server-Side Rendering) - 최선
Next.js (자동 SSR):
// pages/[id].tsx
export async function getServerSideProps({ params }) {
return {
props: {
canonicalUrl: `https://site.com/product/${params.id}`
}
}
}
export default function Product({ canonicalUrl }) {
return (
<Head>
<link rel="canonical" href={canonicalUrl} />
</Head>
)
}
방법 2: SSG (Static Site Generation)
Next.js (빌드 시 생성):
export async function getStaticProps({ params }) {
return {
props: {
canonical: `https://site.com/page/${params.slug}`
},
revalidate: 3600 // 1시간마다 재생성
}
}
방법 3: Prerendering 서비스
Prerender.io, Rendertron 등:
봇 감지 → 미리 렌더링된 HTML 제공
일반 사용자 → 정상 SPA
Nginx 설정 예시:
location / {
if ($http_user_agent ~* "googlebot|bingbot|yandex") {
proxy_pass http://prerender.io/https://yoursite.com$request_uri;
}
try_files $uri /index.html;
}
방법 4: Dynamic Rendering (Google 권장)
개념: 봇에게만 서버 렌더링, 사용자에게는 CSR
Puppeteer 활용:
const puppeteer = require('puppeteer');
async function renderForBot(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle0' });
const html = await page.content();
await browser.close();
return html; // Canonical 포함된 완전한 HTML
}
// Express.js 미들웨어
app.use((req, res, next) => {
const isBot = /googlebot|bingbot/i.test(req.headers['user-agent']);
if (isBot) {
renderForBot(req.url).then(html => res.send(html));
} else {
next(); // 일반 SPA 제공
}
});
검증: Fetch as Google
Google Search Console → URL 검사 → 실제 URL 테스트
"렌더링된 HTML" 탭에서 canonical 확인
→ 있으면 성공, 없으면 SSR/Prerendering 필요
Part 6: 실전 케이스 스터디
📈 Case 1: 글로벌 이커머스 (다국어 + 대량 상품)
회사: 패션 쇼핑몰
규모: 상품 5,000개 × 언어 5개 = 25,000 페이지
Before:
문제:
- 각 언어가 영어를 canonical로 지정 (❌)
- 상품 변형 URL 무분별 (색상별 독립 URL)
- 총 인덱스 페이지: 125,000개
결과:
- 한국/일본 트래픽: 거의 0
- 크롤 예산 초과
- 신상품 인덱싱: 평균 2주 소요
After:
해결:
1. 각 언어 페이지 → 자기 자신 canonical
2. Hreflang 정확히 설정
3. 상품 변형 → 마스터 SKU로 통합
4. 총 인덱스 페이지: 25,000개 (1/5 감소)
결과 (3개월 후):
- 한국 트래픽: 0 → 월 15,000명
- 일본 트래픽: 0 → 월 12,000명
- 크롤 예산: 80% 절약
- 신상품 인덱싱: 평균 2일
- 전체 매출: +45%
📈 Case 2: 뉴스 미디어 (페이지네이션 + AMP)
회사: 온라인 뉴스
규모: 하루 200개 기사 × AMP = 400 페이지/일
Before:
문제:
- 페이지네이션 모두 1페이지를 canonical로 (❌)
- AMP canonical 양방향 설정 안 됨
- 깊은 페이지 기사 인덱싱 안 됨
결과:
- 2페이지 이후 기사 검색 노출: 거의 없음
- AMP 트래픽: 전체의 5% (모바일 70% 시대에)
After:
해결:
1. 각 페이지네이션 → 자기 자신 canonical
2. AMP ↔ 일반 페이지 양방향 링크
3. rel="prev/next" 추가 (Bing 최적화)
결과 (2개월 후):
- 2페이지+ 기사 노출: 350% 증가
- AMP 트래픽: 5% → 42%
- 평균 체류 시간: +38% (AMP 속도 효과)
- 광고 수익: +52%
📈 Case 3: SaaS 플랫폼 (React SPA)
회사: 프로젝트 관리 툴
기술: React SPA (CSR)
Before:
문제:
- Canonical을 JS로만 추가
- 구글봇이 JS 실행 전 크롤
- Search Console: "Canonical 감지 안 됨"
결과:
- 블로그 글 50개 중 인덱싱: 8개만
- 오가닉 트래픽: 월 1,200명 (목표 대비 1/10)
After:
해결:
1. Next.js로 마이그레이션 (SSR)
2. 모든 페이지 getStaticProps로 canonical 생성
3. Sitemap에 canonical URL 명시
결과 (1개월 후):
- 블로그 인덱싱: 50개 전체
- 오가닉 트래픽: 월 1,200 → 9,800명
- 리드 전환: +420%
- 투자 대비 회수: 3주
Part 7: 고급 자동화 스크립트
🤖 대량 Canonical 감사 및 수정
Node.js 스크립트 (전체 사이트 크롤 + 검증)
const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
async function auditCanonical(url) {
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
const canonical = $('link[rel="canonical"]').attr('href');
const count = $('link[rel="canonical"]').length;
return {
url,
canonical: canonical || 'MISSING',
count,
status: canonical ? (count === 1 ? 'OK' : 'MULTIPLE') : 'MISSING'
};
} catch (error) {
return {
url,
canonical: 'ERROR',
count: 0,
status: 'ERROR'
};
}
}
// 사이트맵에서 URL 추출
async function getUrlsFromSitemap(sitemapUrl) {
const response = await axios.get(sitemapUrl);
const $ = cheerio.load(response.data, { xmlMode: true });
const urls = [];
$('url > loc').each((i, elem) => {
urls.push($(elem).text());
});
return urls;
}
// 메인 실행
(async () => {
const sitemapUrl = 'https://yoursite.com/sitemap.xml';
const urls = await getUrlsFromSitemap(sitemapUrl);
console.log(`${urls.length}개 URL 검증 시작...`);
const results = [];
for (const url of urls) {
const result = await auditCanonical(url);
results.push(result);
console.log(`${result.status}: ${url}`);
}
// CSV 저장
const csv = results.map(r =>
`"${r.url}","${r.canonical}",${r.count},"${r.status}"`
).join('\n');
fs.writeFileSync('canonical_audit.csv',
'URL,Canonical,Count,Status\n' + csv
);
// 통계
const stats = {
total: results.length,
ok: results.filter(r => r.status === 'OK').length,
missing: results.filter(r => r.status === 'MISSING').length,
multiple: results.filter(r => r.status === 'MULTIPLE').length,
error: results.filter(r => r.status === 'ERROR').length
};
console.log('\n=== 검증 완료 ===');
console.log(`총 페이지: ${stats.total}`);
console.log(`정상: ${stats.ok} (${(stats.ok/stats.total*100).toFixed(1)}%)`);
console.log(`누락: ${stats.missing} (${(stats.missing/stats.total*100).toFixed(1)}%)`);
console.log(`중복: ${stats.multiple} (${(stats.multiple/stats.total*100).toFixed(1)}%)`);
console.log(`오류: ${stats.error}`);
console.log('\n결과: canonical_audit.csv');
})();
사용법:
npm install axios cheerio
node canonical_audit.js
🔄 정기 모니터링 (Cron + Slack 알림)
const cron = require('node-cron');
const { WebClient } = require('@slack/web-api');
const slack = new WebClient(process.env.SLACK_TOKEN);
// 매일 오전 9시 실행
cron.schedule('0 9 * * *', async () => {
console.log('Canonical 모니터링 시작...');
// 위의 audit 함수 실행
const results = await runAudit();
const issues = results.filter(r =>
r.status === 'MISSING' || r.status === 'MULTIPLE'
);
if (issues.length > 0) {
await slack.chat.postMessage({
channel: '#seo-alerts',
text: `⚠️ Canonical 이슈 발견: ${issues.length}개\n` +
issues.slice(0, 5).map(i => `• ${i.url}`).join('\n')
});
} else {
console.log('✅ 모든 페이지 정상');
}
});
Part 8: 체크리스트 - 고급 시나리오별
✅ 다국어 사이트 체크리스트
- [ ] 각 언어 페이지는 자기 자신을 canonical로 지정
- [ ] 모든 언어 버전을 hreflang으로 연결
- [ ] x-default 지정 (기본 언어)
- [ ] ISO 639-1 + ISO 3166-1 형식 사용
- [ ] 모든 언어 페이지에 동일한 hreflang 세트
- [ ] Google Search Console "국제 타겟팅" 오류 0건
✅ 이커머스 체크리스트
- [ ] 상품 변형 전략 결정 (마스터 SKU vs 독립)
- [ ] 파라미터 URL canonical 설정
- [ ] 필터 페이지 canonical 정책
- [ ] 재고 없는 상품 처리 방침
- [ ] 계절 상품 canonical 관리
- [ ] 대량 검증 스크립트 구축
✅ AMP/모바일 체크리스트
- [ ] 일반 페이지 → amphtml 링크
- [ ] AMP 페이지 → 일반 페이지 canonical
- [ ] AMP Test 통과
- [ ] 모바일 별도 URL이면 alternate 설정
- [ ] 반응형이면 self-referencing만
✅ SPA 체크리스트
- [ ] SSR 또는 SSG 구현
- [ ] 봇 감지 및 prerendering
- [ ] Google Search Console "렌더링된 HTML" 확인
- [ ] 동적 canonical 생성 로직 검증
- [ ] sitemap에 canonical URL 포함
오늘 당장 할 일 (난이도별)
🟢 초급: 다국어 사이트 (30분)
- 현재 canonical 확인 (모든 언어 페이지)
- 잘못된 설정 수정 (자기 자신으로)
- Hreflang 추가
- Search Console 검증
🟡 중급: 이커머스 상품 (1시간)
- 상품 변형 전략 수립
- 템플릿에 canonical 로직 추가
- 100개 상품 샘플 검증
- 전체 롤아웃
🔴 고급: SPA 마이그레이션 (1일)
- Next.js/Nuxt 등 SSR 프레임워크 검토
- 핵심 페이지부터 SSR 적용
- Prerendering 서비스 설정
- 단계적 배포
다음 편 예고
[5부] Canonical 태그 검증 및 지속 관리
"설정했다고 끝이 아닙니다.
매주 10분으로 트래픽을 지키세요."
- Google Search Console 완전 정복
- Screaming Frog 고급 활용법
- 실시간 모니터링 시스템 구축
- 문제 발생 시 긴급 대응 매뉴얼
- 자동화된 주간 리포트 생성
📅 3일 후 공개 예정
핵심 요약: 3줄 정리
- 다국어 사이트: 각 언어는 자기 자신 canonical + hreflang 필수
- 이커머스: 마스터 SKU 방식으로 변형 통합, 자동화 스크립트 필수
- SPA: SSR/SSG 없이는 canonical 무용지물, 봇 감지 prerendering 필수
복잡할수록 canonical이 더 중요합니다. 자동화가 답입니다.
📌 이 글이 도움이 되셨다면:
- 💬 댓글: "우리는 __번 시나리오에 해당해요"
- 🔖 북마크: 다국어/이커머스 론칭 시 필수 참고
- 📧 알림 구독: 5부 검증편 놓치지 마세요
5부에서 만나요! 설정한 것을 지속적으로 관리하는 법(Canonical 태그 검증 및 지속 관리|올씽블로거)을 배웁니다.
작성: 2026년 1월 | 시리즈 4/7
참고: Google Search Central, Shopify Developer Docs, hreflang.org, Web.dev
#가나 투데이 #ganatoday
그린아프로




