Skip to content

Domain consolidation: serve app at compasscalendar.com/app #1726

@tyler-dane

Description

@tyler-dane

Context

Currently the calendar app (compass repo) is deployed to app.compasscalendar.com (GCP VM) and the marketing site (compasscalendar.com repo) is deployed to www.compasscalendar.com (Vercel). Many landing page visitors never click the CTA into the app. The goal is to consolidate onto compasscalendar.com — the marketing site becomes the shell, the app lives at compasscalendar.com/app, and the two domains are eliminated in favor of one.

Architecture after full migration:

  • compasscalendar.com — Next.js on Vercel (marketing + blog, unchanged repo)
  • compasscalendar.com/app/* — React SPA, Vercel rewrites → GCP VM
  • compasscalendar.com/api/* — Express backend, Vercel rewrites → GCP VM
  • app.compasscalendar.com — unlisted internal proxy target (Vercel rewrites point here); direct browser visits 301 → compasscalendar.com
  • Self-hosters — no change, continue serving at root /

Phase 1: Deploy app to compasscalendar.com/app

Goal: Prove it works. The app becomes accessible at compasscalendar.com/app while app.compasscalendar.com continues to work.

1. Make the web build base path configurable

compass/packages/web/build.ts:63 — Change hardcoded publicPath: "/" to:

publicPath: process.env.APP_BASE_PATH ?? "/",

Also inject it into the define object so the router can consume it at runtime:

APP_BASE_PATH: JSON.stringify(process.env.APP_BASE_PATH ?? "/"),

2. Add basename to React Router

compass/packages/web/src/routers/index.tsx:31 — Add a basename to createBrowserRouter:

export const router = createBrowserRouter([...routes], {
  basename: APP_BASE_PATH,   // global injected at build time
  future: { v7_relativeSplatPath: true },
});

APP_BASE_PATH is the build-time global defined above ("/app" in production, "/" for self-hosters and dev).

3. Update Nginx on the GCP VM

compass-scripts repogcp/local-vm/nginx-prod/sites-enabled/app.compasscalendar.com

Replace the current root location / with:

  • location /app — serves the static SPA (with try_files falling back to /app/index.html)
  • location = / — 301 redirect to /app (keeps existing app.compasscalendar.com links alive during transition)
  • /api and /socket.io blocks stay unchanged

4. Update production env vars

compass-scripts repogcp/.prod.env

BASEURL=https://compasscalendar.com/api
FRONTEND_URL=https://compasscalendar.com
CORS=http://localhost:3000,http://localhost:9080,https://app.compasscalendar.com,https://compasscalendar.com
APP_BASE_PATH=/app

5. Add Vercel rewrites in compasscalendar.com repo

compasscalendar.com/next.config.mjs — Add to the rewrites() return:

{ source: "/app",        destination: "https://app.compasscalendar.com/app" },
{ source: "/app/:path*", destination: "https://app.compasscalendar.com/app/:path*" },
{ source: "/api/:path*", destination: "https://app.compasscalendar.com/api/:path*" },

Note: WebSocket (/socket.io) may need its own rewrite entry — confirm during testing.

6. Manual: Google OAuth Console

Add https://compasscalendar.com to authorized JavaScript origins and redirect URIs (https://compasscalendar.com/auth/google/callback).

7. Deploy & verify

bash dev/compass.sh deploy --project web --env production
bash dev/compass.sh deploy --project backend --env production

Visit compasscalendar.com/app — SPA loads, Google auth works, calendar is functional.


Phase 2: Welcome dialog

Goal: Show a brief "what is this" dialog to unauthenticated users over the app (like tweek.so).

New component: packages/web/src/components/WelcomeDialog/WelcomeDialog.tsx

  • Show when user is not authenticated
  • Hide once user signs in — tied to auth state (reappears on logout)
  • Content: 2–3 sentences describing Compass + a sign-in CTA
  • Follow existing modal conventions (AuthModal)

Wire into packages/web/src/views/Root.tsx.

Verify: Visit compasscalendar.com/app logged out → dialog appears. Sign in → dialog disappears.


Phase 3: Decommission app.compasscalendar.com

Goal: One domain. Old subdomain demoted to unlisted internal proxy target.

  1. No DNS changesapp.compasscalendar.com stays in DNS, Vercel rewrites from Phase 1 continue to target it unchanged.

  2. Nginx — Keep all location blocks intact (Vercel still proxies through). Optionally add a redirect for direct browser visits:

    if ($http_x_vercel_proxy != "1") {
        return 301 https://compasscalendar.com;
    }

    Simpler alternative: leave the vhost fully open — it's never linked anywhere, so it's effectively dead to users once CTA URLs are updated.

  3. Update CTA URLs in compasscalendar.com repo:

    • components/simplify-your-day-button.tsxhttps://app.compasscalendar.com/?utm_...https://compasscalendar.com/app?utm_...
    • components/quiz/Results.tsx — same change
  4. Manual: Remove app.compasscalendar.com from Google OAuth Console. Update Red Sift cert monitoring.

Verify: app.compasscalendar.com/* → 301 to compasscalendar.com. compasscalendar.com/app loads and auth works end-to-end.


Self-hosting: No changes needed

APP_BASE_PATH defaults to "/" when unset. Docker Compose and self-hosting docs are unaffected.


Files changed

File Phase Change
compass/packages/web/build.ts 1 Configurable publicPath + inject APP_BASE_PATH global
compass/packages/web/src/routers/index.tsx 1 Add basename: APP_BASE_PATH to router
compass/packages/web/src/views/Root.tsx 2 Wire WelcomeDialog
compass/packages/web/src/components/WelcomeDialog/ 2 New component
compass-scripts/gcp/.prod.env 1 Update BASEURL, FRONTEND_URL, CORS, APP_BASE_PATH
compass-scripts/gcp/local-vm/nginx-prod/sites-enabled/app.compasscalendar.com 1+3 /app location + / redirect; browser-visit 301 in Phase 3
compasscalendar.com/next.config.mjs 1 Add /app and /api rewrites
compasscalendar.com/components/simplify-your-day-button.tsx 3 Update CTA URL
compasscalendar.com/components/quiz/Results.tsx 3 Update CTA URL

Metadata

Metadata

Assignees

Labels

devopsCI/CD, e2e tests, infrawebFrontend/web related issue

Projects

Status

Ready

Relationships

None yet

Development

No branches or pull requests

Issue actions