사이트를 처음 배포하면 의외로 같은 페이지가 여러 주소로 열리는 경우가 많습니다. https://example.com, https://www.example.com, Vercel이 제공하는 기본 도메인, 프리뷰 배포 주소, 쿼리 파라미터가 붙은 URL까지 동시에 접근되다 보면 운영자는 “어차피 같은 페이지인데 뭐가 문제지?”라고 생각하기 쉽습니다. 그런데 검색과 측정 관점에서는 이 상태가 꽤 불편합니다. 어떤 주소를 기준으로 링크가 모여야 하는지 흐려지고, canonical이 흔들리면 색인 결과도 예측하기 어려워집니다.

Google Search Central은 canonical 신호를 줄 수 있는 방법으로 redirect, rel="canonical", sitemap을 제시하고, 이 신호들은 함께 쓸수록 더 잘 작동한다고 설명합니다. 즉 한 가지 설정만 맞춘다고 끝나는 문제가 아니라, “실제 접속 주소”, “head 안의 canonical”, “사이트맵”, “내부 링크”가 모두 같은 방향을 가리켜야 합니다. 여기에 Vercel 공식 문서까지 함께 보면, 도메인 대표 주소를 고르는 단계에서부터 www와 apex 중 무엇을 기본으로 삼을지 결정해야 이후 작업이 편해집니다.

이번 글에서는 Vercel에 배포된 기술 블로그를 기준으로, www, non-www, canonical을 따로따로 만지는 대신 한 번에 정리하는 흐름을 설명해보겠습니다.

기반 설계배포 정리

Vercel 배포 후 www, non-www, canonical을 한 번에 정리하는 방법

Vercel에 배포한 뒤 같은 사이트가 여러 주소로 열리는 상태를 어떻게 정리해야 하는지 설명합니다. 대표 도메인 선택, 리다이렉트, canonical, 내부 링크, 점검 순서를 한 흐름으로 묶어 정리했습니다.

핵심 1

먼저 결론: 저는 이 순서로 정리합니다

핵심 2

제일 먼저 할 일은 대표 도메인을 하나 정하는 것

핵심 3

대표 도메인을 정했으면, 나머지 호스트는 전부 거기로 보내야 한다

배포 정리기반 설계vercel canonical cleanup
대표 도메인, 리다이렉트, canonical, 내부 링크와 사이트맵을 한 방향으로 맞추는 흐름

먼저 결론: 저는 이 순서로 정리합니다

제 기준에서는 아래 다섯 단계를 한 번에 맞추는 게 가장 깔끔합니다.

  1. 대표 도메인을 하나 고른다
  2. Vercel에서 나머지 호스트를 대표 도메인으로 리다이렉트한다
  3. Next.js metadataBase와 페이지별 canonical을 대표 도메인 기준으로 맞춘다
  4. 내부 링크와 sitemap도 같은 주소만 가리키게 한다
  5. curl과 실제 HTML로 결과를 확인한다

핵심은 “리다이렉트가 있으니 canonical은 대충 해도 된다” 또는 “canonical만 있으면 호스트는 여러 개 열려도 된다”가 아니라, 둘을 함께 맞추는 것입니다.

1. 제일 먼저 할 일은 대표 도메인을 하나 정하는 것

Vercel 공식 문서는 apex domain을 프로젝트에 추가하면 www 도메인도 함께 추가하도록 제안하고, 기본적으로는 www를 대표 도메인으로 두고 non-www에서 www로 리다이렉트하는 방식을 권장합니다. 이유는 간단합니다. DNS 규격상 apex 도메인에는 CNAME을 둘 수 없지만, www 같은 서브도메인에는 CNAME을 둘 수 있기 때문에 Vercel CDN이 더 많은 제어를 할 수 있기 때문입니다.

이건 꽤 중요한 포인트입니다. 많은 글이 “요즘은 apex가 더 예쁘다” 수준에서 끝나는데, Vercel 문서는 왜 www를 권장하는지 기술적인 이유까지 설명합니다. 다만 여기서 꼭 기억해야 할 건, apex를 대표 도메인으로 쓰는 것이 금지된다는 뜻은 아니라는 점입니다. 공식 문서도 apex를 기본으로 선택하는 방식이 여전히 가능하다고 말합니다. 다만 그 경우 Vercel이 www를 기본으로 둘 때만큼의 제어를 갖지는 못합니다.

그래서 현실적인 선택은 둘 중 하나입니다.

  • 운영 편의와 Vercel 권장 방향을 따를 경우: https://www.example.com
  • 브랜드 노출을 단순하게 가져가고 싶을 경우: https://example.com

어느 쪽이든 괜찮지만, 꼭 한 가지를 “대표 주소”로 먼저 정해야 합니다. 이 결정을 미루면 이후 canonical과 내부 링크가 계속 흔들립니다.

2. 대표 도메인을 정했으면, 나머지 호스트는 전부 거기로 보내야 한다

Google Search Central은 영구 리다이렉트를 canonical의 강한 신호라고 설명합니다. 그리고 같은 문서에서 여러 canonical 신호를 함께 쓰면 더 효과적이라고 안내합니다. 즉 www와 non-www가 둘 다 200 응답으로 열리는 상태는 가능한 한 빨리 정리하는 편이 좋습니다.

Vercel 도메인 문서도 방문자가 www를 붙이든 안 붙이든 자동으로 리다이렉트를 시도한다고 설명하지만, 동시에 명시적으로 리다이렉트를 추가하는 것을 고려할 수 있다고 말합니다. 제 경험상 이건 꼭 해두는 편이 좋습니다. 자동 동작을 기대하기보다, 대시보드에서 의도를 명확하게 고정해두는 쪽이 나중에 덜 헷갈립니다.

정리 예시는 이렇습니다.

https://example.com -> https://www.example.com
https://project.vercel.app -> https://www.example.com

혹은 apex를 대표 주소로 고른다면 반대로 갑니다.

https://www.example.com -> https://example.com
https://project.vercel.app -> https://example.com

여기서 중요한 건 “대표 도메인 하나, 나머지 전부 1개 방향”입니다. www는 apex로 보내고, 일부 페이지는 또 다른 호스트를 canonical로 가리키는 식으로 섞이면 안 됩니다.

3. canonical은 리다이렉트와 같은 방향을 가리켜야 한다

가장 많이 생기는 실수가 여기입니다. 리다이렉트는 www로 보내는데, head의 canonical은 apex를 가리키거나, 어떤 글은 절대 URL이고 어떤 글은 상대 경로인 식으로 섞입니다. Google 문서는 서로 다른 canonicalization 기술이 같은 페이지에 대해 서로 다른 URL을 가리키지 않도록 주의하라고 분명히 적고 있습니다. 또한 내부 링크도 canonical URL로 일관되게 연결하라고 권장합니다.

즉 아래 세 가지는 반드시 같은 방향이어야 합니다.

  • 브라우저가 최종 도착하는 실제 URL
  • <link rel="canonical">
  • 사이트 내부 링크

이 사이트처럼 Next.js App Router를 쓰는 경우에는 metadataBase를 루트 레이아웃에 두고, 각 페이지에서 alternates.canonical을 설정하면 흐름이 깔끔해집니다. Next.js 공식 문서도 metadataBase가 URL 기반 메타데이터의 기준이 되고, alternates.canonical을 통해 canonical 링크를 생성한다고 안내합니다.

예를 들면 루트 레이아웃은 이렇게 둘 수 있습니다.

import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://www.example.com"),
  title: {
    default: "Signal Ledger",
    template: "%s | Signal Ledger",
  },
};

그리고 개별 글 페이지에서는 대표 경로를 명시합니다.

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;

  return {
    title: "글 제목",
    alternates: {
      canonical: `/blog/${slug}`,
    },
  };
}

이렇게 하면 최종 출력은 아래처럼 하나의 기준 주소로 정리됩니다.

<link rel="canonical" href="https://www.example.com/blog/vercel-canonical-cleanup" />

이 패턴의 장점은 명확합니다.

  • 루트 도메인이 바뀌어도 metadataBase만 수정하면 전체 메타데이터가 같이 정리됩니다.
  • 개별 글은 경로만 관리하면 됩니다.
  • Open Graph 이미지 같은 절대 URL도 같은 기준을 쓰기 쉬워집니다.

4. 이 레포처럼 환경 변수로 사이트 URL을 관리하면 더 덜 꼬인다

현재 이 프로젝트도 루트 레이아웃에서 metadataBase를 설정하고, 별도 유틸 함수에서 NEXT_PUBLIC_SITE_URL 또는 VERCEL_PROJECT_PRODUCTION_URL을 읽어 사이트 URL을 결정하도록 구성돼 있습니다. 이런 방식은 실제 운영에서 꽤 유리합니다.

예를 들면 이렇게 정리할 수 있습니다.

const FALLBACK_SITE_URL = "http://localhost:3000";

export function getSiteUrl() {
  const rawSiteUrl =
    process.env.NEXT_PUBLIC_SITE_URL ??
    process.env.VERCEL_PROJECT_PRODUCTION_URL;

  if (!rawSiteUrl) {
    return FALLBACK_SITE_URL;
  }

  return rawSiteUrl.startsWith("http")
    ? rawSiteUrl
    : `https://${rawSiteUrl}`;
}

그리고 Vercel Production 환경 변수에 대표 도메인을 넣습니다.

NEXT_PUBLIC_SITE_URL=https://www.example.com

이렇게 하면 프리뷰 배포나 개발 환경과 생산 환경이 뒤섞일 때 canonical이 엉뚱한 주소를 가리킬 가능성을 줄일 수 있습니다. 이 문장은 공식 문서의 직접 표현은 아니고, Next.js의 metadataBase 동작과 Vercel 배포 환경을 함께 놓고 본 실무적 추론입니다. 하지만 실제로는 이 한 줄을 명시해두는 편이 훨씬 안전합니다.

5. 정적 export 프로젝트라면 도메인 리다이렉트는 Vercel 쪽에서 처리하는 편이 단순하다

이 레포는 output: "export"를 사용하고 있습니다. 이런 프로젝트에서는 호스트 수준의 리다이렉트를 Next.js 코드에 과하게 넣기보다, Vercel의 Domain Redirect 설정에서 처리하는 편이 단순합니다. 도메인 리다이렉트는 애플리케이션 라우팅 이전에 처리되는 편이 더 자연스럽기 때문입니다.

반면 canonical은 페이지의 메타데이터이므로 앱 코드 안에서 관리하는 것이 맞습니다. 저는 이 역할을 아래처럼 분리하는 편을 추천합니다.

  • 호스트 정리: Vercel 대시보드
  • 페이지 대표 URL 선언: Next.js metadata
  • 사이트 전체 기준 URL: 환경 변수 + metadataBase

이렇게 역할을 나누면 www/non-www 문제와 개별 페이지 canonical 문제를 혼동하지 않게 됩니다.

6. sitemap과 내부 링크가 다른 주소를 가리키면 정리가 덜 끝난 상태다

Google 문서는 sitemap 포함도 canonical에 대한 약한 신호가 되며, 내부 링크는 canonical URL로 일관되게 연결하라고 설명합니다. 그래서 저는 도메인 정리 작업을 할 때 리다이렉트와 head 태그만 보지 않고, 아래 항목까지 같이 봅니다.

  • sitemap에 들어간 URL이 대표 도메인인가
  • 내비게이션과 카드 링크가 대표 도메인을 기준으로 계산되는가
  • Open Graph URL과 canonical이 다른 호스트를 가리키지 않는가

이건 생각보다 중요합니다. 예를 들어 리다이렉트는 www로 보내는데, sitemap 생성 함수는 apex를 기반으로 URL을 만들고 있으면, 신호가 서로 어긋납니다. 이전 글에서 정리한 slug 구조 글에서 말했듯이, 기준 주소는 한 곳에서만 관리해야 오래 안 꼬입니다.

7. 마지막 검증은 브라우저보다 헤더와 실제 HTML을 보는 편이 빠르다

브라우저는 리다이렉트를 너무 자연스럽게 처리해서, 중간에 어떤 일이 일어났는지 놓치기 쉽습니다. 그래서 도메인 정리 직후에는 아래처럼 헤더를 확인하는 편이 좋습니다.

curl.exe -I https://example.com
curl.exe -I https://www.example.com
curl.exe -I https://project.vercel.app

여기서 보고 싶은 것은 크게 두 가지입니다.

  • location 헤더가 내가 정한 대표 도메인을 가리키는가
  • 불필요하게 두세 번 연속 점프하지 않는가

그리고 실제 canonical은 HTML 소스에서 확인합니다.

curl.exe https://www.example.com/blog/vercel-canonical-cleanup | Select-String 'rel="canonical"'

원하는 출력은 이런 형태입니다.

<link rel="canonical" href="https://www.example.com/blog/vercel-canonical-cleanup" />

만약 여기서 다른 호스트가 보이거나, 프리뷰 URL이 들어가 있거나, 경로가 누락돼 있으면 아직 정리가 덜 끝난 것입니다.

제가 실제로 추천하는 체크리스트

도메인 정리 작업을 마친 뒤에는 이 순서로 다시 확인합니다.

  1. 대표 도메인을 문서로 고정했는가
  2. www와 non-www 중 하나만 200 응답인가
  3. 나머지 호스트는 모두 대표 도메인으로 리다이렉트되는가
  4. metadataBase가 대표 도메인인가
  5. 각 페이지의 canonical이 대표 도메인 + 정확한 경로인가
  6. sitemap URL도 같은 호스트를 쓰는가
  7. 내부 링크가 중복 호스트를 가리키지 않는가

이렇게만 맞춰도 검색 엔진이 기준 주소를 이해하는 데 필요한 신호는 꽤 명확해집니다.

마무리

Vercel에 사이트를 올린 뒤 www, non-www, canonical을 정리하는 작업은 사실 세 가지를 한 번에 맞추는 일입니다. 첫째는 방문자가 실제로 도착하는 호스트를 하나로 모으는 것, 둘째는 head 안에서 canonical을 같은 주소로 선언하는 것, 셋째는 사이트맵과 내부 링크까지 같은 방향으로 정렬하는 것입니다.

Vercel 공식 문서를 기준으로 보면 www를 대표 도메인으로 두는 방식은 분명한 장점이 있고, Google Search Central 문서를 보면 redirect와 canonical, sitemap은 함께 쓸수록 신호가 더 강해집니다. 그래서 제 기준에서는 “대표 도메인 하나를 고르고, 나머지 모두 리다이렉트하고, Next.js metadata로 canonical을 맞춘다”가 가장 실수 적은 기본값입니다.

도메인 연결 자체가 아직 불안정하다면 먼저 Cloudflare 커스텀 도메인 연결 실수 글부터 보고, URL 구조를 처음부터 덜 꼬이게 정리하고 싶다면 slug, 날짜, 카테고리 구조 글을 함께 보는 흐름을 추천합니다.

참고 문서