SOYOYU
블로그로 돌아가기기술 SEO

Edge SEO 실전: 리다이렉트, 헤더 인젝션, HTML 변환 코드 예제

Cloudflare Workers로 구현하는 Edge SEO 실전 코드. 301 리다이렉트, KV 대량 리다이렉트, HSTS/CSP/X-Robots-Tag 헤더 인젝션, HTMLRewriter 메타 태그 삽입, 캐노니컬 인젝션, hreflang 인젝션, A/B 테스트까지.

소요유2026년 7월 2일8 min read
Edge SEOCloudflare Workers리다이렉트HTMLRewriter기술 SEO

TL;DR: 복사해서 바로 쓸 수 있는 Edge SEO 코드 7가지

  • 단일 리다이렉트: 경로 기반 301 리다이렉트 (가장 기본)
  • KV 대량 리다이렉트: 수만~수백만 URL을 서브밀리초 조회로 처리
  • HSTS 헤더: HTTPS 강제 적용으로 리다이렉트 홉 제거
  • X-Robots-Tag: 특정 경로에 noindex 적용 (관리자, 스테이징)
  • HTMLRewriter 메타 태그: OG 태그, 캐노니컬 태그 동적 삽입
  • Hreflang 인젝션: 다국어 사이트의 hreflang 태그 일괄 관리
  • A/B 테스트: 타이틀 태그를 엣지에서 분할 테스트

이 글은 Edge SEO 개념편의 후속으로, 실제 Cloudflare Workers 코드를 제공합니다. 모든 코드는 Cloudflare Workers의 ES 모듈(Module Worker) 형식으로 작성되었으며, 복사 후 바로 배포할 수 있습니다.


1. 단일 경로 301 리다이렉트

가장 기본적인 Edge SEO 활용입니다. 특정 경로 요청을 새 URL로 301 리다이렉트합니다.

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === '/old-page') {
      return Response.redirect('https://example.com/new-page', 301);
    }
    return fetch(request);
  }
};

이 코드는 /old-page 요청만 리다이렉트하고, 나머지 요청은 오리진 서버로 그대로 전달합니다. 리다이렉트 대상이 몇 개 안 되면 이 방식으로 충분합니다.

출처: Cloudflare Workers — Redirect Examples


2. KV 스토어 기반 대량 리다이렉트

URL이 수천~수만 개라면 코드 안에 매핑을 하드코딩할 수 없습니다. Workers KV(Key-Value 스토어)에 리다이렉트 맵을 저장하고, 서브밀리초 조회로 리다이렉트를 처리합니다.

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const destination = await env.REDIRECTS.get(url.pathname);
    if (destination) {
      return Response.redirect(destination, 301);
    }
    return fetch(request);
  }
};

env.REDIRECTS는 Workers KV 네임스페이스입니다. Cloudflare 대시보드나 Wrangler CLI로 KV에 키-값 쌍을 업로드합니다.

KV 성능 데이터: 실제 테스트에서 20,000개의 리다이렉트를 Cuckoo Filter로 128KB에 압축하여, 100,000개 활성 URL에 대해 0.5~1% 오긍정률(false-positive rate)로 검증했습니다. KV 자체는 수백만 개 항목을 지원합니다.

출처: Cloudflare Workers — Bulk Redirects

Cloudflare Community — KV Redirect Map


3. HTTP 헤더 인젝션: HSTS

HSTS(HTTP Strict Transport Security) 헤더는 브라우저에게 "이 사이트는 항상 HTTPS로 접속하라"고 지시합니다. 첫 접속 이후 HTTP에서 HTTPS로의 리다이렉트 홉이 제거되어 TTFB가 개선됩니다.

export default {
  async fetch(request) {
    const response = await fetch(request);
    const newHeaders = new Headers(response.headers);
    newHeaders.set(
      'Strict-Transport-Security',
      'max-age=31536000; includeSubDomains; preload'
    );
    return new Response(response.body, {
      status: response.status,
      headers: newHeaders,
    });
  }
};

max-age=31536000은 1년(365일)입니다. includeSubDomains는 모든 서브도메인에도 HSTS를 적용합니다. preload는 Chrome의 HSTS Preload List에 제출하기 위한 조건입니다(hstspreload.org에서 별도 제출 필요).

출처: jvns.ca — Editing HTTP Headers with Workers


4. HTTP 헤더 인젝션: X-Robots-Tag

특정 경로에 X-Robots-Tag 헤더를 추가하여, HTML의 <meta name="robots"> 태그 없이도 크롤링/색인을 제어합니다. 관리자 페이지나 스테이징 환경에 유용합니다.

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const response = await fetch(request);
    const newHeaders = new Headers(response.headers);

    if (url.pathname.startsWith('/admin') || url.pathname.startsWith('/staging')) {
      newHeaders.set('X-Robots-Tag', 'noindex, nofollow');
    }

    return new Response(response.body, {
      status: response.status,
      headers: newHeaders,
    });
  }
};

X-Robots-Tag는 Google, Bing, Yandex(부분)가 지원합니다. 비HTML 리소스(PDF, 이미지)의 색인을 제어할 때도 사용할 수 있습니다. HTML의 meta robots 태그로는 비HTML 리소스를 제어할 수 없으므로, X-Robots-Tag 헤더가 유일한 방법입니다.

출처: Cloudflare Workers — Headers Docs


5. HTTP 헤더 인젝션: CSP

CSP(Content Security Policy) 헤더는 브라우저가 로드할 수 있는 리소스의 출처를 제한합니다. 비인가 서드파티 스크립트를 차단하여 페이지 무게를 줄이고, Core Web Vitals(LCP, FID)를 개선할 수 있습니다.

export default {
  async fetch(request) {
    const response = await fetch(request);
    const newHeaders = new Headers(response.headers);
    newHeaders.set(
      'Content-Security-Policy',
      "default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline';"
    );
    return new Response(response.body, {
      status: response.status,
      headers: newHeaders,
    });
  }
};

주의: CSP가 너무 엄격하면 애널리틱스 추적, 소셜 미디어 위젯, A/B 테스트 도구, 문의 폼이 차단될 수 있습니다. Googlebot의 JavaScript 렌더링에도 영향을 줄 수 있습니다. 반드시 Content-Security-Policy-Report-Only 헤더로 먼저 테스트한 후 적용합니다.


6. HTMLRewriter: 동적 메타 태그 인젝션

Cloudflare Workers의 HTMLRewriter API는 HTML 응답을 스트리밍 방식으로 파싱하고 수정할 수 있습니다. 전체 HTML을 메모리에 올리지 않으므로 대용량 페이지에서도 효율적입니다.

다음 코드는 Open Graph URL 태그를 <head>에 동적으로 삽입합니다.

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const response = await fetch(request);

    if (!response.headers.get('content-type')?.includes('text/html')) {
      return response;
    }

    return new HTMLRewriter()
      .on('head', {
        element(element) {
          element.append(
            `<meta property="og:url" content="${url.href}" />`,
            { html: true }
          );
        }
      })
      .transform(response);
  }
};

content-type 체크가 중요합니다. 이미지, CSS, JavaScript 파일에는 HTMLRewriter를 적용하면 안 됩니다.

출처: Cloudflare — HTMLRewriter Docs

rauf.wtf — Dynamic Meta Tags with Workers


7. HTMLRewriter: 캐노니컬 URL 인젝션

CMS가 캐노니컬 태그를 자동 생성하지 않거나, 잘못된 캐노니컬을 출력하는 경우에 엣지에서 올바른 캐노니컬을 삽입합니다.

function getCanonicalUrl(url) {
  // 쿼리 파라미터 제거, 소문자 변환 등 정규화 로직
  const canonical = new URL(url.pathname, 'https://example.com');
  return canonical.href;
}

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const response = await fetch(request);

    if (!response.headers.get('content-type')?.includes('text/html')) {
      return response;
    }

    return new HTMLRewriter()
      .on('head', {
        element(element) {
          const canonicalUrl = getCanonicalUrl(url);
          element.append(
            `<link rel="canonical" href="${canonicalUrl}" />`,
            { html: true }
          );
        }
      })
      .transform(response);
  }
};

주의: 오리진이 이미 캐노니컬 태그를 출력하고 있다면, 중복 캐노니컬이 발생합니다. 기존 캐노니컬을 제거하고 새로 삽입하는 로직이 필요합니다.


8. HTMLRewriter: Hreflang 인젝션

다국어 사이트에서 hreflang 태그를 CMS 템플릿 수정 없이 일괄 관리합니다.

const LOCALES = {
  '/ko/': { lang: 'ko', href: 'https://example.com/ko/' },
  '/en/': { lang: 'en', href: 'https://example.com/en/' },
  '/ja/': { lang: 'ja', href: 'https://example.com/ja/' },
};

export default {
  async fetch(request) {
    const response = await fetch(request);
    if (!response.headers.get('content-type')?.includes('text/html')) {
      return response;
    }

    return new HTMLRewriter()
      .on('head', {
        element(element) {
          for (const [path, locale] of Object.entries(LOCALES)) {
            element.append(
              `<link rel="alternate" hreflang="${locale.lang}" href="${locale.href}" />`,
              { html: true }
            );
          }
          element.append(
            `<link rel="alternate" hreflang="x-default" href="https://example.com/en/" />`,
            { html: true }
          );
        }
      })
      .transform(response);
  }
};

실무에서는 추가적인 검증 로직이 필요합니다.

  • 캐노니컬 태그가 현재 페이지 URI와 일치하는지 확인
  • noindex 페이지에는 hreflang을 삽입하지 않음
  • 쿼리 파라미터, 추적 코드가 포함된 URL에는 삽입하지 않음
  • 리다이렉트 응답에는 삽입하지 않음

출처: Chris Lever SEO — Hreflang with Workers

SEO Juice — Edge Hreflang Injection


9. A/B 테스트: 타이틀 태그 분할 테스트

엣지에서 타이틀 태그를 A/B 테스트하여 CTR 변화를 측정합니다.

export default {
  async fetch(request) {
    const response = await fetch(request);
    const url = new URL(request.url);

    // 쿠키 기반 또는 랜덤 50/50 분할
    const cookie = request.headers.get('Cookie') || '';
    const variant = cookie.includes('seo_test=b') ? 'b' :
                    cookie.includes('seo_test=a') ? 'a' :
                    Math.random() < 0.5 ? 'a' : 'b';

    if (variant === 'b') {
      return new HTMLRewriter()
        .on('title', {
          element(element) {
            element.setInnerContent('New Title Variant B');
          }
        })
        .transform(response);
    }

    return response;
  }
};

중요한 주의사항: SEO A/B 테스트에서 캐노니컬 신호 오염을 방지해야 합니다. 변형 페이지에는 원본 URL을 캐노니컬로 지정하거나, 별도의 Worker에서 A/B 분할과 추적을 분리하는 것이 권장됩니다.

출처: Cloudflare — A/B Testing Reference Architecture

SEO Scout — Cloudflare Workers SEO Test


배포 실무 팁

Wrangler CLI로 배포

# Wrangler 설치
npm install -g wrangler

# 로그인
wrangler login

# Worker 생성 및 배포
wrangler init my-seo-worker
wrangler deploy

Route 설정

Worker를 전체 사이트가 아닌 특정 경로에만 적용하려면 Route를 설정합니다. 이미지나 CSS에 불필요하게 Worker가 실행되는 것을 방지하여 무료 플랜의 요청 한도를 절약합니다.

# wrangler.toml
routes = [
  { pattern = "example.com/blog/*", zone_name = "example.com" },
  { pattern = "example.com/products/*", zone_name = "example.com" }
]

테스트 우선

배포 전에 wrangler dev로 로컬 테스트합니다. HTMLRewriter의 변환 결과를 브라우저와 curl로 모두 확인합니다.

# 로컬 개발 서버 실행
wrangler dev

# curl로 헤더 확인
curl -I https://example.com/test-page

핵심 요약

  1. 단일 리다이렉트는 가장 기본적인 Edge SEO이며, 코드 3줄로 구현됩니다
  2. KV 대량 리다이렉트는 수만~수백만 URL을 서브밀리초로 처리합니다
  3. HSTS/CSP/X-Robots-Tag 헤더는 오리진 서버 설정 없이 엣지에서 주입합니다
  4. HTMLRewriter로 OG 태그, 캐노니컬, hreflang을 동적으로 삽입합니다
  5. A/B 테스트는 타이틀 태그 변경의 CTR 효과를 측정할 수 있습니다
  6. Route 설정으로 Worker 실행 범위를 제한하여 비용을 절약합니다

자주 묻는 질문

Q1. HTMLRewriter로 삽입한 메타 태그를 Google이 정상적으로 인식하나요?

인식합니다. Googlebot은 최종 렌더링된 HTML을 기준으로 페이지를 분석합니다. Cloudflare Workers가 변환한 HTML은 오리진 서버가 직접 출력한 HTML과 동일하게 처리됩니다. Google Search Console의 URL 검사 도구에서 "렌더링된 페이지 보기"로 확인할 수 있습니다.

출처: Cloudflare Blog — Technical SEO with Workers

Q2. KV에 리다이렉트 맵을 업로드하는 방법은 무엇인가요?

Wrangler CLI로 JSON 파일을 KV에 일괄 업로드할 수 있습니다. wrangler kv:bulk put --namespace-id=NAMESPACE_ID redirects.json 명령으로 키-값 쌍을 한 번에 업로드합니다. Cloudflare 대시보드에서 수동으로 추가하는 것도 가능합니다.

출처: Cloudflare Workers — Bulk Redirects

Q3. 오리진이 이미 캐노니컬 태그를 출력하는데, 엣지에서도 삽입하면 중복 문제가 생기나요?

중복 캐노니컬은 Google이 어떤 것을 따를지 모호하게 만듭니다. HTMLRewriter에서 기존 캐노니컬 태그를 먼저 제거(element.remove())하고 새로 삽입하는 로직을 추가해야 합니다. .on('link[rel="canonical"]', { element(el) { el.remove(); } }) 핸들러를 추가하면 됩니다.

Q4. CSP 헤더가 Googlebot의 JavaScript 렌더링을 방해할 수 있나요?

방해할 수 있습니다. CSP가 Google의 렌더링 서비스(Web Rendering Service)가 필요로 하는 리소스를 차단하면, 페이지가 제대로 렌더링되지 않아 색인 품질이 저하됩니다. Content-Security-Policy-Report-Only 헤더로 먼저 모니터링한 후, 차단되는 리소스가 없는지 확인하고 적용합니다.

Q5. Worker에서 오류가 발생하면 사이트가 다운되나요?

기본적으로 Worker에서 처리되지 않은 예외(uncaught exception)가 발생하면 Cloudflare는 요청을 오리진 서버로 직접 전달합니다(failopen 동작). 즉, Worker 오류가 사이트 다운으로 이어지지 않습니다. 다만 이 동작은 설정에 따라 다를 수 있으므로, Wrangler 설정에서 compatibility_flags를 확인하고, 코드에 try-catch를 추가하는 것이 안전합니다.


Edge SEO 구현이 필요하시면 XEO 무료 진단을 신청하세요.

검색 최적화가 필요하신가요?

무료 상담을 통해 비즈니스에 맞는 최적화 전략을 확인하세요.