블로그 주소가 왜 중요한지 처음부터 알고 있었던 건 아니다. myblog.postlark.ai도 충분히 괜찮다고 생각했다. HTTPS 자동 적용되고, 짧고, 기억하기 쉬우니까. 그런데 블로그를 진지하게 운영하려는 사람들의 첫 질문은 항상 같았다. "내 도메인 연결할 수 있어?"

서브도메인은 남의 집이다

Postlark에서 블로그를 만들면 {slug}.postlark.ai 주소를 받는다. 기능적으로는 문제가 없다. 하지만 검색엔진은 도메인의 히스토리와 권위를 신뢰 시그널로 쓴다. 서브도메인은 플랫폼에 종속된 주소다. 플랫폼이 사라지면 URL도 같이 증발한다.

Medium이 커스텀 도메인 지원을 중단했을 때 많은 블로거가 하루아침에 검색 순위를 잃었다. 그런 걸 지켜본 사람일수록 "내 도메인"에 민감하다. SEO만의 문제도 아니다. blog.joonpark.devjoonpark.postlark.ai보다 세련되어 보인다는 단순한 취향도 크다. 개인 브랜드를 키우는 사람에게 도메인은 이름표고, 이름표를 남이 정해주는 건 불편하다.

그래서 커스텀 도메인은 "있으면 좋은 기능"이 아니라 블로그 플랫폼의 기본 요건이었다. 뒤로 미룰 수 없었다.

CNAME 하나면 끝

사용자가 할 일은 딱 하나 — DNS에 CNAME 레코드를 추가하는 것이다.

대시보드에서 도메인을 입력하면 안내 화면이 뜬다. 복사 버튼으로 CNAME 값을 가져다 DNS 설정에 붙여넣는다. 전파가 끝나면 검증 버튼을 누르고, SSL 인증서가 자동으로 발급된다. 세 단계, 보통 10분 안에 끝난다.

TXT 레코드로 소유권을 이중 검증할까 고민했지만 접었다. CNAME 자체가 이미 소유권 증명이다. 남의 DNS를 수정할 수 있는 사람은 없으니까. 사용자에게 한 단계라도 덜 요구하는 게 낫다.

admin.postlark.ai 사건

개발 중에 꽤 웃긴 사고가 하나 있었다.

블로그 slug를 서브도메인으로 쓰는 구조다 보니, 누군가 admin이라는 slug로 블로그를 만들면 admin.postlark.ai가 관리자 페이지가 아니라 그 블로그를 보여줘야 하는 충돌이 생긴다. 이론적으로는 뻔한 문제다. 하지만 막상 코드를 짜면서는 완전히 놓쳤다.

테스트 중에 관리자 페이지에 접속했더니 "블로그를 찾을 수 없습니다"가 떴다. 라우터가 모든 서브도메인을 블로그로 해석하고 있었다. admin이라는 블로그가 없으니 404. 원인을 찾는 데 5분, 자괴감을 느끼는 데 5분.

해결은 예약어 목록이었다. admin, api, app, docs, dashboard, www 등 40개 넘는 slug를 예약어로 등록하고, 블로그 생성 시 이 slug를 쓰려고 하면 거부하도록 했다. 라우터도 예약 서브도메인이면 블로그 해석을 건너뛴다. "이런 slug로 블로그 만들 사람이 있겠어?" 싶겠지만, test, blog, my, www — 직감적으로 쓸 법한 단어가 전부 충돌 후보다. 브랜드 이름에 흔히 쓰는 SaaS 용어까지 포함하니 목록이 금방 불어났다.

두 주소가 같은 콘텐츠를 가리키면

커스텀 도메인을 연결한 뒤 기존 서브도메인은 어떻게 처리할까. 같은 글이 두 URL에 존재하면 검색엔진 입장에서 중복 콘텐츠다. 링크 가치가 분산되고, 어느 쪽이 원본인지 혼란이 생긴다.

301 영구 리다이렉트를 택했다. myblog.postlark.ai로 접속하면 blog.mydomain.com으로 자동 이동한다. 검색엔진은 301을 보면 링크 가치를 새 주소로 넘긴다. canonical URL도 커스텀 도메인 쪽으로 자동 설정된다. 두 주소가 공존하는 순간은 없다.

도메인을 해제하면? 서브도메인이 즉시 다시 살아난다. 리다이렉트가 사라지고 원래 주소로 콘텐츠를 보여준다. 다른 서비스에서 도메인 해제가 복잡하거나 데이터가 꼬이는 경험을 몇 번 겪은 뒤로, 이건 처음부터 깔끔하게 동작하도록 만들었다. 떠날 자유가 보장되어야 들어올 마음도 생긴다.

하드코딩 13곳

커스텀 도메인 기능을 만든 뒤에 예상 못한 후속 작업이 기다리고 있었다.

코드를 둘러보니 플랫폼 도메인이 문자열로 박혀 있는 곳이 열세 군데였다. 레이아웃 템플릿, 에러 페이지, 검색, 메타 태그, 사이트맵까지 — 블로그 라우터가 건드리는 거의 모든 곳에서 도메인을 참조하고 있었고, 전부 하드코딩이었다. 커스텀 도메인이 연결된 블로그에서 이 값들이 동적으로 바뀌어야 하니 전부 환경 설정으로 뽑아내는 리팩토링이 필요했다.

한번에 끝났으면 좋았겠지만, 교체 작업 후에도 배포할 때마다 하드코딩된 도메인이 하나씩 더 튀어나왔다. "이번이 마지막이겠지" 하고 커밋을 올리면 스테이징에서 또 발견되는 패턴. 결국 기존 값은 폴백 기본값으로만 남기고 전부 동적으로 교체했다.

프로젝트 초반에 이런 값을 상수로 뽑아두면 나중에 얼마나 편한지 다들 안다. 알면서도 처음엔 하드코딩한다. 나도 그랬고, 다음에도 아마 그럴 거다. 급할 때 상수 파일 만드는 건 세상에서 가장 하기 싫은 일이니까.