Skip to content

Commit 81d4c5a

Browse files
committed
feat: path-based backend via Vercel rewrite
Replace subdomain (senzen-api.pantorn.site) with path-based routing: browser hits senzen.pantorn.site/<path>, Vercel proxies the request to Render at senzen-api.onrender.com/api/<path>. - Add frontend/vercel.json: catch-all rewrite /:path* → Render /api/:path* (Vercel only applies rewrites when Next.js returns 404, so actual pages still work) - vercel.tf: NEXT_PUBLIC_BACKEND = '' (empty) so the frontend builds relative URLs that resolve against the current origin - cloudflare.tf: remove the senzen-api record (not needed anymore — the browser only ever sees senzen.pantorn.site) - render.yaml: BACKEND_URL = https://senzen.pantorn.site (the URL the browser sees; Vercel rewrites /google_callback to Render /api/google_callback server-side) - backend/main.go: move /health and /validate-token under /api/* so every backend route is consistently at /api/* (matches the rewrite destination pattern) - outputs.tf: drop the backend_url and api_url outputs (no longer meaningful — the only public URL is the frontend) What changes for the user: - One DNS record instead of two - No Cloudflare proxy needed (and no 'Full' SSL mode config) - No CORS to configure (same origin) - OAuth callback URLs become https://senzen.pantorn.site/{provider}_callback - Cold-start caveat: Vercel free plan has 10s timeout; Render takes ~30-60s to wake from sleep. First request after 15 min idle fails with 504. Workaround: UptimeRobot pings /health every 14 min.
1 parent f2c3757 commit 81d4c5a

6 files changed

Lines changed: 33 additions & 61 deletions

File tree

backend/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ func main() {
3030

3131
app.Use(middleware.CORSMiddleware())
3232

33-
app.Get("/health", func(c *fiber.Ctx) error {
33+
// All routes live under /api so the Vercel rewrite
34+
// (/:path* → senzen-api.onrender.com/api/:path*) lines up.
35+
api.Get("/health", func(c *fiber.Ctx) error {
3436
return c.SendString("health check ok")
3537
})
3638

37-
app.Get("/validate-token", middleware.ValidateToken)
39+
api.Get("/validate-token", middleware.ValidateToken)
3840

3941
routes.SetupOAuthRoutes(api)
4042
routes.SetupAuthRoutes(api)

frontend/vercel.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rewrites": [
3+
{
4+
"source": "/:path*",
5+
"destination": "https://senzen-api.onrender.com/api/:path*"
6+
}
7+
]
8+
}

infra/terraform/cloudflare.tf

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,21 @@
11
# =============================================================
2-
# Cloudflare DNS — senzen.pantorn.site
2+
# Cloudflare DNS — pantorn.site
33
# =============================================================
4-
# This file manages the DNS records for the Senzen project.
5-
# Records:
6-
# senzen CNAME → cname.vercel-dns.com (frontend, DNS-only)
7-
# senzen-api CNAME → <render-service>.onrender.com (backend, Cloudflare-proxied)
4+
# Path-based architecture: the frontend (senzen.pantorn.site) is
5+
# on Vercel, and Vercel rewrites /<anything> to Render's
6+
# /api/<anything> via vercel.json. The browser only ever sees
7+
# senzen.pantorn.site, so only ONE DNS record is needed:
8+
# senzen CNAME → cname.vercel-dns.com (frontend, DNS-only)
89
#
9-
# The backend is proxied (orange cloud) because Render's free
10-
# plan does NOT support custom domains — so the service runs
11-
# at senzen-api.onrender.com, and Cloudflare terminates TLS
12-
# for senzen-api.pantorn.site using Universal SSL, then
13-
# forwards to Render in "Full" SSL mode (no cert verification).
14-
#
15-
# Both records are ONE level deep under pantorn.site so they're
16-
# covered by Cloudflare's Universal SSL (*.pantorn.site).
10+
# No backend DNS record — the backend is reached via the Vercel
11+
# rewrite, never directly via a public hostname.
1712
# =============================================================
1813

19-
# ─────────────────────────────────────────────────────────────
20-
# Frontend: senzen.pantorn.site → Vercel (DNS-only)
21-
# Vercel issues its own Let's Encrypt cert via ACME HTTP-01,
22-
# which fails if Cloudflare proxy is on. So we leave it
23-
# proxied = false and let Vercel handle TLS.
24-
# ─────────────────────────────────────────────────────────────
2514
resource "cloudflare_record" "senzen_frontend" {
2615
zone_id = var.cloudflare_zone_id
27-
name = split(".", var.frontend_domain)[0] # "senzen" from "senzen.pantorn.site"
16+
name = split(".", var.frontend_domain)[0] # "senzen"
2817
type = "CNAME"
2918
value = var.vercel_cname_target
3019
proxied = false
31-
comment = "Senzen frontend → Vercel (DNS-only, Vercel issues cert)"
32-
}
33-
34-
# ─────────────────────────────────────────────────────────────
35-
# Backend: senzen-api.pantorn.site → Render (proxied)
36-
# Render free plan doesn't issue certs for custom domains.
37-
# Cloudflare proxies, terminates TLS with Universal SSL, and
38-
# forwards to Render's onrender.com URL. Set Cloudflare zone
39-
# SSL mode to "Full" (NOT "Full (Strict)") so the origin cert
40-
# isn't required to be valid for senzen-api.pantorn.site.
41-
# ─────────────────────────────────────────────────────────────
42-
resource "cloudflare_record" "senzen_backend" {
43-
zone_id = var.cloudflare_zone_id
44-
name = var.backend_subdomain # "senzen-api"
45-
type = "CNAME"
46-
value = "${var.render_service_name}.onrender.com"
47-
proxied = true
48-
comment = "Senzen backend → Render via Cloudflare proxy (Render free doesn't support custom domains)"
20+
comment = "Senzen frontend → Vercel (DNS-only, Vercel issues its own cert)"
4921
}

infra/terraform/outputs.tf

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,16 @@ output "vercel_project_id" {
99
}
1010

1111
output "frontend_url" {
12-
description = "Public frontend URL (custom domain)"
12+
description = "Public frontend URL (custom domain) — also the only public URL for the backend (Vercel rewrites /<path> to Render /api/<path>)"
1313
value = "https://${var.frontend_domain}"
1414
}
1515

16-
output "backend_url" {
17-
description = "Public backend URL (custom domain + /api prefix)"
18-
value = "https://${var.backend_subdomain}.${var.frontend_domain}"
19-
}
20-
21-
output "backend_api_url" {
22-
description = "Backend API base path the frontend calls"
23-
value = "https://${var.backend_subdomain}.${var.frontend_domain}/api"
24-
}
25-
2616
output "render_onrender_url" {
27-
description = "Render's default onrender.com URL (DNS target in Cloudflare)"
17+
description = "Render's default onrender.com URL — backend is reached through this only via the Vercel rewrite, not directly"
2818
value = "https://${var.render_service_name}.onrender.com"
2919
}
3020

3121
output "cloudflare_record_senzen" {
3222
description = "Cloudflare CNAME record for the frontend"
3323
value = cloudflare_record.senzen_frontend.hostname
3424
}
35-
36-
output "cloudflare_record_api" {
37-
description = "Cloudflare CNAME record for the backend"
38-
value = cloudflare_record.senzen_backend.hostname
39-
}

infra/terraform/vercel.tf

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ resource "vercel_project_environment_variable" "frontend_url" {
2727
resource "vercel_project_environment_variable" "backend_url" {
2828
project_id = vercel_project.cashwise.id
2929
key = "NEXT_PUBLIC_BACKEND"
30-
value = "https://${var.backend_subdomain}.${var.frontend_domain}/api"
31-
target = ["production"]
32-
sensitive = false
30+
# Empty string so the frontend builds relative URLs like "/api/users"
31+
# which Vercel rewrites to https://senzen-api.onrender.com/api/users
32+
value = ""
33+
target = ["production"]
34+
sensitive = false
3335
}

render.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ services:
4646
- key: FRONTEND_URL
4747
value: https://senzen.pantorn.site
4848
- key: BACKEND_URL
49-
value: https://senzen-api.pantorn.site
49+
# Public URL the user sees — used by OAuth providers as the
50+
# callback redirect base. Vercel rewrites /google_callback to
51+
# Render's /api/google_callback server-side.
52+
value: https://senzen.pantorn.site
5053
- key: GO_ENV
5154
value: production
5255

0 commit comments

Comments
 (0)