๐Ÿ”€

How Does Vercel Subdirectory Reverse Proxy Work?

Consolidating External Server Content Under a Next.js Domain Subdirectory for SEO Authority

The main domain (negabaro.com) runs on Vercel with Next.js.
Blog content (/blog/) is rendered by **separate servers*.

Vercel's Next.js Middleware proxies requests via NextResponse.rewrite(), replacing the Host header with the origin server's host.
The browser URL remains negabaro.com/blog/... so domain authority is not diluted.

Why Subdirectory

Google treats subdomains (blog.example.com) as separate sites.
But subdirectories (example.com/blog) are recognized as part of the same site.

Backlinks, traffic, and content authority built by the blog directly contribute to the main domain.

Backend-Agnostic Pattern

Whether it's a dynamic server (Rails on DigitalOcean) or a static site (Jekyll on GitHub Pages), the same proxy pattern applies.

The only difference is asset URL handling:

  • Rails: asset_host Proc for dynamic resolution at request time

  • Jekyll: {{ site.url }} in _config.yml for static resolution at build time

Pragmatic Choice: Jekyll Stays on Subdomain

While subdirectory proxy is optimal for SEO, aligning Jekyll (GitHub Pages) URL paths is tedious.

Matching Jekyll permalinks to /blog/archive/... requires:

  • Modifying permalink/baseurl settings

  • Rewriting all internal links

So Jekyll stays on its subdomain (negabaro.github.io) with only canonical/OGP/absolute asset URLs configured for SEO benefits.

This doesn't achieve full domain authority consolidation, but prevents duplicate indexing via canonical and ensures asset loading works through the proxy.

Performance Benefits of Vercel Edge Frontend

Vercel's global Edge Network acts as cache + CDN in front of the proxy.

  • s-maxage=60 โ†’ instant response from Edge for 60 seconds

  • stale-while-revalidate=300 โ†’ serves stale content for 5 more minutes while refreshing in background

Users get cached HTML from the nearest Vercel Edge node worldwide, achieving fast TTFB regardless of origin server location.

Deployment Independence

Each origin server deploys completely independently.

  • Redeploying Rails doesn't affect Jekyll blog (and vice versa)

  • Vercel Edge cache absorbs origin downtime โ†’ brief deployment interruptions are invisible to users

  • Adding new categories only requires one rewrite rule in Vercel Middleware

Architecture Diagram

๐ŸŒ
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 Performance Benefits of Vercel Edge Frontend
CACHE HIT
User โ†’ Vercel Edge (nearest node) โ†’ cached HTML instant response ~50ms
CACHE MISS
User โ†’ Vercel Edge โ†’ Origin Server โ†’ HTML โ†’ Edge cache store โ†’ response ~200-500ms
STALE
User โ†’ Edge (stale response immediate) + background Origin refresh ~50ms
0~60s: cached response
60~360s: stale + refresh
360s+: revalidation required
~50ms
Edge TTFB (cache hit)
70+
Edge nodes (worldwide)
0s
Deploy downtime
DEPLOY Benefits of Independent Deployment
Rails
During Rails server redeploy โ†’ Vercel Edge cache keeps responding โ†’ Jekyll blog unaffected
Jekyll
git push to GitHub Pages โ†’ auto-deploy after build โ†’ Rails server unaffected
Vercel
Middleware rule change โ†’ instant Edge deploy (seconds) โ†’ no origin server change needed
Adding a new category
// src/middleware.ts
if (path.startsWith('/blog/new-category')) {
  return NextResponse.rewrite(`https://new.origin.com${path}`)
}
ASSET Asset URL Handling: Rails vs Jekyll
๐Ÿ›ค๏ธ Rails: Dynamic Resolution
config.asset_host = Proc.new { |_, req|
  "#{req.protocol}#{req.host_with_port}"
}
https://protocols.negabaro.com/static/app.css
Host auto-detected per request
๐Ÿ“ Jekyll: Static at Build Time
# _config.yml
url: https://negabaro.github.io
https://negabaro.github.io/css/style.css
{{ site.url }} replaced at build
๐Ÿ“ Jekyll Proxy Configuration Files
File Changes
_includes/head.html 3 CSS โ†’ {{ site.url }}/css/... absolute URL + canonical/OGP
_includes/footer.html 3 JS โ†’ {{ site.url }}/js/... absolute URL
_layouts/post.html header background image โ†’ {{ site.url }}/img/...
_layouts/page.html Same pattern applied
๐Ÿ” SEO Strategy (Rails / Jekyll Common)
โœ“ canonical: www.negabaro.com/blog/... โ†’ domain authority concentration
โœ“ robots.txt: origin server Disallow: / โ†’ prevent duplicate indexing
โœ“ hreflang: ko/en/ja + x-default โ†’ multilingual SEO
โœ“ Absolute asset URL: loads directly from origin even through proxy
Subdomain vs Subdirectory
blog.example.com example.com/blog
Google recognition Separate site Same site
Domain authority Distributed Concentrated (backlinks combined)
Config complexity Low High (proxy required)
Infra independence Fully independent Independent (via proxy)
Rails vs Jekyll: Proxy Config Comparison
๐Ÿ›ค๏ธ Rails (DO) ๐Ÿ“ Jekyll (GitHub Pages)
Asset URL Dynamic Proc (request.host) Static {{ site.url }}
Result protocols.negabaro.com/... negabaro.github.io/...
canonical Helper method (dynamic) Liquid template (static)
robots.txt Controller Disallow Consolidated via canonical
Deploy Independent deploy to DO Auto deploy via git push

How It Works

1

[Common] User visits negabaro.com/blog/... โ†’ Next.js Middleware on Vercel Edge matches path pattern

2

[Common] NextResponse.rewrite() proxies to origin server (Host header replaced with origin host)

3

[Rails] Origin server dynamically renders HTML. asset_host = Proc { request.host_with_port } โ†’ asset URLs resolved to absolute paths at request time

4

[Jekyll] GitHub Pages returns static HTML. _config.yml url: https://user.github.io โ†’ assets baked as {{ site.url }}/css/... at build time

5

[Common] Vercel Edge caches response (s-maxage + stale-while-revalidate) and delivers to user

6

[Common] Browser loads CSS/JS directly from origin server (absolute URLs bypass proxy)

Pros

  • SEO authority concentration: blog backlinks, traffic, and content authority directly add to main domain
  • Global Edge caching: cached HTML served instantly from Vercel Edge nodes worldwide โ†’ fast TTFB regardless of origin server location
  • stale-while-revalidate: serves stale response first while refreshing in background after cache expiry โ†’ users always get fast responses
  • Deployment downtime absorption: Edge cache continues serving during origin redeployment โ†’ zero-downtime deployment effect
  • Independent deployment: Rails/Jekyll/other services deploy independently. Failures in one do not propagate to others
  • Tech stack freedom: choose optimal technology per category (Rails, Jekyll, Hugo, Django โ€” anything works)
  • Gradual expansion: adding a new category only requires one rewrite rule in Vercel Middleware

Cons

  • Configuration complexity: Host header rewriting, asset_host, canonical, robots.txt and more required
  • Asset loading issues: relative path CSS/JS returns 404 on Next.js side when proxied
  • Debugging difficulty: hard to pinpoint issues in proxy chain (Vercel Edge โ†’ Rails)
  • TTFB increase: added Vercel โ†’ Rails round trip on cache miss (mitigated by stale-while-revalidate)
  • Subdomain duplicate indexing risk: same content indexed at 2 URLs without robots.txt + canonical

Use Cases

Consolidating a tech blog under main domain path (SEO authority concentration) Unifying microservices under a single domain (different tech stacks per service) Static site (Next.js) + dynamic content (Rails/Django) hybrid Consolidating GitHub Pages (Jekyll) blog under main domain subdirectory Multi-app blog consolidation (proxy Rails + Jekyll + other services per category)