🔀

Vercel Subdirectory Reverse Proxy는 어떻게 동작하나?

Next.js 도메인 아래 서브디렉토리로 외부 서버 콘텐츠를 통합하여 SEO 권위를 집중

메인 도메인(negabaro.com)은 Vercel에서 Next.js로 운영합니다.
블로그 콘텐츠(/blog/)는 *별도 서버**에서 렌더링합니다.

Vercel의 Next.js Middleware가 NextResponse.rewrite()로 요청을 프록시하되, Host 헤더를 원본 서버 호스트로 교체합니다.
브라우저에는 negabaro.com/blog/... URL이 유지되므로 도메인 권위가 분산되지 않습니다.

왜 서브디렉토리인가

Google은 서브도메인(blog.example.com)을 별도 사이트로 취급합니다.
반면, 서브디렉토리(example.com/blog)는 같은 사이트의 일부로 인식합니다.

블로그에서 쌓은 백링크·트래픽·콘텐츠 권위가 메인 도메인에 직접 합산됩니다.

백엔드에 무관한 패턴

동적 서버(Rails on DigitalOcean)든, 정적 사이트(Jekyll on GitHub Pages)든 동일한 프록시 패턴으로 통합됩니다.

차이점은 에셋 URL 처리 방식뿐입니다:

  • Rails: asset_host Proc으로 요청 시점에 동적 결정

  • Jekyll: _config.yml{{ site.url }}로 빌드 시점에 정적 확정

현실적 선택: Jekyll은 서브도메인 유지

서브디렉토리 프록시가 SEO에 최적이지만, Jekyll(GitHub Pages)은 URL 경로 구조를 맞추는 작업이 번거롭습니다.

Jekyll의 permalink 구조를 메인 도메인의 /blog/archive/... 경로와 완벽히 일치시키려면:

  • permalink, baseurl 수정

  • 내부 링크 전면 교체

따라서 Jekyll은 서브도메인(negabaro.github.io)에 그대로 두되, canonical/OGP/에셋 절대 URL만 설정하여 SEO 이점만 취하는 방식을 선택했습니다.

서브디렉토리만큼의 도메인 권위 통합은 안 되지만, canonical을 통한 중복 방지와 에셋 절대 URL로 프록시 호환성은 확보됩니다.

Vercel Edge를 앞단에 둔 성능 이점

Vercel의 글로벌 Edge Network가 프록시 앞단에서 캐시 + CDN 역할을 합니다.

  • s-maxage=60 → 60초간 Edge에서 즉시 응답

  • stale-while-revalidate=300 → 캐시 만료 후에도 5분간 stale 응답을 먼저 전달, 백그라운드에서 갱신

사용자 입장에서는 전 세계 어디서든 Vercel Edge에서 캐시된 HTML을 받으므로 원본 서버의 지리적 위치와 무관하게 빠른 TTFB를 얻습니다.

배포 독립성

각 원본 서버는 완전히 독립적으로 배포됩니다.

  • Rails 서버를 재배포해도 Jekyll 블로그에 영향 없음 (역도 마찬가지)

  • Vercel Edge 캐시가 원본 서버 다운타임을 흡수 → 짧은 배포 중단은 사용자에게 노출되지 않음

  • 새 카테고리 추가 시 Vercel Middleware에 rewrite 규칙 1줄 추가로 끝

구조 다이어그램

🌐
User Browser
negabaro.com/blog/...
① Request
Vercel Edge Network
Next.js Middleware + Global CDN
Rewrite
Host header
Cache
s-maxage=60
② Reverse Proxy
🛤️
Rails (DigitalOcean)
protocols.negabaro.com
canonical → www.negabaro.com
robots.txt: Disallow: /
asset_host = Proc { req.host }
📝
Jekyll (GitHub Pages)
negabaro.github.io
canonical → www.negabaro.com
canonical → SEO
url: https://user.github.io
PERFORMANCE Vercel Edge 앞단 배치의 성능 이점
CACHE HIT
User → Vercel Edge (최근접 노드) → 캐시된 HTML 즉시 반환 ~50ms
CACHE MISS
User → Vercel Edge → Origin Server → HTML → Edge 캐시 저장 → 반환 ~200-500ms
STALE
User → Edge (stale 응답 즉시 반환) + 백그라운드에서 Origin 갱신 ~50ms
0~60s: 캐시 응답
60~360s: stale + 갱신
360s+: 재검증 필수
~50ms
Edge TTFB (캐시 적중)
70+
Edge 노드 (전세계)
0s
배포 시 다운타임
DEPLOY 독립 배포의 이점
Rails
Rails 서버 재배포 중 → Vercel Edge 캐시가 계속 응답 → Jekyll 블로그 영향 없음
Jekyll
GitHub Pages에 git push → 빌드 완료 후 자동 반영 → Rails 서버 영향 없음
Vercel
Middleware 규칙 변경 → Edge에 즉시 반영 (수 초) → 원본 서버 변경 불필요
새 카테고리 추가 시
// src/middleware.ts
if (path.startsWith('/blog/new-category')) {
  return NextResponse.rewrite(`https://new.origin.com${path}`)
}
ASSET 에셋 URL 처리: Rails vs Jekyll
🛤️ Rails: 동적 결정
config.asset_host = Proc.new { |_, req|
  "#{req.protocol}#{req.host_with_port}"
}
https://protocols.negabaro.com/static/app.css
요청마다 호스트 자동 판별
📝 Jekyll: 빌드 시 확정
# _config.yml
url: https://negabaro.github.io
https://negabaro.github.io/css/style.css
빌드 시 {{ site.url }} 치환
📝 Jekyll 프록시 적용 시 변경 파일
파일 변경 내용
_includes/head.html CSS 3개 → {{ site.url }}/css/... 절대 URL + canonical/OGP
_includes/footer.html JS 3개 → {{ site.url }}/js/... 절대 URL
_layouts/post.html header 배경이미지 → {{ site.url }}/img/...
_layouts/page.html 동일 패턴 적용
🔍 SEO 전략 (Rails / Jekyll 공통)
canonical: www.negabaro.com/blog/... → 도메인 권위 집중
robots.txt: 원본 서버 Disallow: / → 중복 인덱싱 방지
hreflang: ko/en/ja + x-default → 다국어 SEO
에셋 절대 URL: 프록시 경유해도 원본에서 직접 로드
서브도메인 vs 서브디렉토리
blog.example.com example.com/blog
Google 인식 별도 사이트 동일 사이트
도메인 권위 분산 집중 (백링크 합산)
설정 복잡도 낮음 높음 (프록시 필요)
인프라 독립성 완전 독립 독립 (프록시 경유)
Rails vs Jekyll: 프록시 설정 비교
🛤️ Rails (DO) 📝 Jekyll (GitHub Pages)
에셋 URL 동적 Proc (request.host) 정적 {{ site.url }}
결과 protocols.negabaro.com/... negabaro.github.io/...
canonical Helper 메서드 (동적) Liquid 템플릿 (정적)
robots.txt Controller에서 Disallow canonical로 통합
배포 DO에 독립 배포 git push만으로 자동 배포

동작 흐름

1

[공통] 사용자가 negabaro.com/blog/... 에 접속 → Vercel Edge의 Next.js Middleware가 경로 패턴 매칭

2

[공통] NextResponse.rewrite()로 원본 서버에 프록시 (Host 헤더를 원본 서버 호스트로 교체)

3

[Rails] 원본 서버가 동적으로 HTML 렌더링. asset_host = Proc { request.host_with_port } → 에셋 URL이 요청 시점에 절대 경로로 결정

4

[Jekyll] GitHub Pages가 정적 HTML 반환. _config.yml의 url: https://user.github.io → 빌드 시 에셋이 {{ site.url }}/css/... 로 확정

5

[공통] Vercel Edge가 응답 캐시 (s-maxage + stale-while-revalidate) 후 사용자에게 전달

6

[공통] 브라우저가 CSS/JS를 원본 서버에서 직접 로드 (절대 URL이므로 프록시를 거치지 않음)

장점

  • SEO 권위 집중: 블로그의 백링크·트래픽·콘텐츠 권위가 메인 도메인에 직접 합산
  • 글로벌 Edge 캐싱: Vercel의 전 세계 Edge 노드에서 캐시된 HTML을 즉시 응답 → 원본 서버 위치와 무관하게 빠른 TTFB
  • stale-while-revalidate: 캐시 만료 후에도 stale 응답을 먼저 전달하면서 백그라운드 갱신 → 사용자는 항상 빠른 응답
  • 배포 다운타임 흡수: 원본 서버 재배포 중에도 Edge 캐시가 응답을 계속 제공 → 제로 다운타임 배포 효과
  • 독립 배포: Rails/Jekyll/기타 서비스를 각각 독립 배포. 한쪽 장애가 다른 쪽에 전파되지 않음
  • 기술 스택 자유: 카테고리별로 최적의 기술 선택 (Rails, Jekyll, Hugo, Django 등 무엇이든 가능)
  • 점진적 확장: 새 카테고리 추가 시 Vercel Middleware에 rewrite 규칙 1줄 추가로 끝

단점

  • 설정 복잡도: Host 헤더 교체, asset_host, canonical, robots.txt 등 다수 설정 필요
  • asset 로딩 이슈: 프록시 경유 시 상대 경로 CSS/JS가 Next.js 측에서 404 발생
  • 디버깅 어려움: 프록시 체인(Vercel Edge → Rails)에서 문제 발생 시 원인 특정 어려움
  • TTFB 증가: 캐시 미스 시 Vercel → Rails 왕복 시간 추가 (stale-while-revalidate로 완화)
  • 서브도메인 중복 인덱싱 위험: robots.txt + canonical 미설정 시 동일 콘텐츠가 2개 URL로 인덱싱

사용 사례

기술 블로그를 메인 도메인 하위 경로로 통합 (SEO 권위 집중) 마이크로서비스를 단일 도메인으로 통합 (서비스별 다른 기술 스택 사용) 정적 사이트(Next.js) + 동적 콘텐츠(Rails/Django) 하이브리드 GitHub Pages(Jekyll) 블로그를 메인 도메인 서브디렉토리로 통합 멀티 앱 블로그 통합 (Rails + Jekyll + 기타 서비스를 카테고리별로 프록시)