Skip to main content
Status: approved design, implementation in progress. The user approved every recommendation on 2026-10-25 (see recorded decisions). Every DNS, proxy, route, provider-console or cutover write on the live tuturuuu.com zone still needs its own explicit user approval. The companion same-origin security model gates each app’s admission.

Recorded decisions

User decisions of 2026-10-25 (GMT+7). They supersede the matching proposals and open questions below.

Goal

Serve products at https://tuturuuu.com/<app>/... instead of https://<app>.tuturuuu.com/.... Opening another app should feel like moving around inside one app. The design adds no extra network hop for the mounted apps. Meet and Mail are the pilot. Web (apps/web on Vercel) keeps / and every path it does not hand over.

Current state (observed 2026-10-25)

Hosting

Both apps already have TanStack Start + Void + Vite+ native source: vite.config.ts, void.config.ts, cloudflare/worker.ts, and a single src/routes/$.tsx catch-all that dispatches to the retained Next page modules through runtime/ adapters. Production for both is held by the wave-1 publication hold. Meet keeps its currently deployed Worker version. Mail keeps its Vercel deployment. The Mail native Worker (tuturuuu-mail) has no production route yet.

Auth and cookies

Satellites run sessionMode: 'supabase-first'. A signed-in user already presents the shared Supabase cookie, and the app-session JWT is the satellite fallback. The cross-app handoff (tuturuuu.com/login?returnUrl=https://meet.tuturuuu.com/verify-token... followed by POST /api/auth/verify-app-token) is what an anonymous first visit pays today.

Web API proxy pattern

Unmatched /api/* requests are forwarded to Web. Meet does this in apps/meet/src/runtime/handler.ts and Mail in apps/mail/src/runtime/handler.ts. Both use x-forwarded-host, and legacy Next uses fallback rewrites. Each app also owns API paths of its own under the same /api namespace:
  • Meet: /api/auth/*, /api/build-info, /api/meet-ai/*, /api/meet-call/* (including the programming preview proxy), /api/meet-live/*.
  • Mail: /api/auth/*, /api/ai/email-draft, /api/v1/mail/*, /api/v1/webhooks/mail/{cloudflare,ses}, /api/v1/workspaces/[wsId]/mail/*.
Server functions use /_meet/functions and /_mail/functions.

Hard-coded origins

rg meet.tuturuuu.com and rg mail.tuturuuu.com find about 120 files, mostly tests and fixtures. These non-test sources must become base-path aware:
  • Registry and URL builders: packages/utils/src/internal-domains.ts (origin-only matching), launchable-apps.ts, seo-policy.json, packages/internal-api/src/client.ts (host lists and getConfiguredMailApiBaseUrl origin normalization), apps/web/src/lib/{meet,mail}-app-url.ts, apps/parley/src/features/studio/meet-links.ts, packages/ai/.../mail-helpers.ts.
  • App constants and config: apps/{meet,mail}/src/constants/*.ts, vite.config.ts define defaults, void.config.ts vars NEXT_PUBLIC_APP_URL.
  • Durable share links: packages/meet-core/.../meeting-metadata.ts and apps/meet/src/runtime/page-model.server.ts canonicals (/r/<code>), ICS UIDs (meet-ics.ts, which is an identifier and should keep its value).
  • Machine endpoints: wrangler.email-routing.jsonc MAIL_INGEST_URL, SES SNS subscription, and the MAIL_{GOOGLE,MICROSOFT}_REDIRECT_URI secrets.
  • Mobile: apps/mobile/lib/core/router/deep_links.dart (host == 'meet.tuturuuu.com'), env.dart, api_origins.dart.
  • Meet’s /live-connect WebSocket compares Origin against NEXT_PUBLIC_APP_URL. This breaks once that value has a path. The code must split origin from base URL.

Path collisions on the apex

Web already serves /meet (the Meet-Together marketing listing), /meet/plans/[planId] and /vi/meet.... It also redirects /products/meet-together. Meet’s own /plans/<id> route maps to the same /meet/plans/<id> URL under the new base path. The marketing landing page needs an owner decision (see open questions). Web has no /mail page route. /meet-together exists, so route patterns must never be tuturuuu.com/meet*.

Routing options and latency

Path routing at Cloudflare needs the apex to be a proxied (orange-cloud) record. The apex is DNS-only to Vercel today. Options (a) and (b) therefore both start with one shared operational change: proxy tuturuuu.com through Cloudflare to the existing Vercel origin. Recommendation: (a), direct zone routes, one pair per app.
Cloudflare matches a route against the whole URL, including the query string. A route pattern cannot contain a query, so /meet?utm_source=x matches neither tuturuuu.com/meet nor tuturuuu.com/meet/* and would fall through to Web. The zone therefore canonicalizes the bare mount instead of routing it:
  • A Single Redirect rule matches the path /meet or /mail exactly (http.request.uri.path in {"/meet" "/mail"}) and redirects to the same path with a trailing slash, preserving the query. It uses 307 during the app’s observation window and 301 afterwards, the same as the subdomain redirects. Redirect rules run before Worker routes, so /meet/ then matches tuturuuu.com/meet/*.
  • Exact path equality never touches /meeting, /meet-together, /mailbox or any other path that merely starts with the mount.
  • The app’s root URL, canonical link and subdomain redirect target are /<app>/, so links never pay the extra hop.
Declare the Worker routes in each app’s void.config.ts (zone_name: 'tuturuuu.com') so they are reviewed config-as-code and covered by the existing sealed-config checks. The redirect rule is a zone rule owned by the Cloudflare operations lane. Never use tuturuuu.com/meet*, because it also matches /meet-together and /meeting. Routing acceptance, for each app before its flag reaches shadow: The router Worker (b) is rejected for two reasons:
  • It buys flexibility we do not need. The flag lives in the app Worker (see plan), and cutover is direct, with no staged canaries.
  • It adds a Worker invocation to every Web request. The Cloudflare billable-operation rules would then require cost bounds that we otherwise avoid.

How Web keeps / without a double hop

With (a), Web traffic crosses Cloudflare’s edge as a plain proxied pass-through with no Worker. It then reaches Vercel’s edge, which still does its own caching (x-vercel-cache: HIT today). This is one added CDN-to-CDN leg, not a second application hop. Both providers have PoPs in HKG/SIN, and Cloudflare pools origin connections, so the leg is expected to cost low single-digit milliseconds. That figure is an estimate, not measured. Treat it as a gate:
  • Gate W1: Web p75 TTFB through Cloudflare must regress by ≤ 10 ms compared with direct Vercel, measured on a non-canonical proxied hostname before touching the apex (operation O2).
  • Disable Cloudflare HTML caching for Web paths (cache-bypass rule). Vercel stays the cache of record, so purges keep working.
  • Keep SSL mode Full (strict). Vercel discourages third-party proxies in front of deployments, so confirm current Vercel guidance and plan support before O3.
  • Certificate renewal. Exempt http://<host>/.well-known/acme-challenge/* on port 80 from every HTTP-to-HTTPS redirect so Vercel can keep renewing certificates.
  • No Cloudflare caching of /.well-known/vercel/*. The cache-bypass rule covers it explicitly, along with every Web HTML path.
  • Client IP trust changes. Behind Cloudflare, Vercel sees Cloudflare egress IPs. The trusted-proxy resolver (packages/utils/src/abuse-protection/client-ip.ts, TRUSTED_PROXY) switches Web to cloudflare-origin, which trusts cf-connecting-ip only on requests that carry the Cloudflare-injected origin secret (see Cloudflare in front of Web). Otherwise rate-limit buckets collapse to a few edge IPs, or cf-connecting-ip becomes spoofable by anyone who reaches Vercel directly.
  • Origin access is a separate gate. Vercel’s Verified Proxy is not an origin-authentication fence: it does not stop anyone from calling the custom domain’s origin or a *.vercel.app deployment URL directly. Before Web trusts cf-connecting-ip in production, a separately verified origin access restriction must cover the custom domains and every deployment URL, for example Vercel Deployment Protection on deployment URLs plus the secret check above. Record the verification (a direct request to each URL class with a forged cf-connecting-ip resolves to the caller’s real IP or is refused) with O3.
The end state removes the leg entirely. Per AGENTS.md, every active app, including Web, eventually moves to Vite+ on Cloudflare. A Web Worker on the apex would make (a) zero-hop for every path. If gate W1 fails, see fallback host.

Fallback: a shared non-apex host

If proxying the apex is rejected, serve the super-app from a new proxied host, for example app.tuturuuu.com/{meet,mail}. All the same-origin benefits hold between mounted apps, but not with Web’s dashboard on tuturuuu.com. This fallback is only worth it if Web’s dashboard also moves under that host later.

Per-app mounting

The parallel Start/Void lane is making Meet and Mail base-path ready. The pilot needs exactly the following from that lane: The [...slug]-under-[locale]/[wsId] prohibition still applies to the retained Next page modules. The proxy-ordering trap moves from /api to /<app>/api.

Migrating the old subdomains

Redirect rules

The app Worker keeps its subdomain Custom Domain and decides per request:
  • GET/HEAD navigations redirect https://<app>.tuturuuu.com<rawPath><rawQuery> to https://tuturuuu.com/<app><rawPath><rawQuery>. Use the raw, undecoded path and query bytes so duplicate keys, + and encoded slashes survive. Fragments survive automatically.
  • Use 307 during the observation window, then 301. A cached 301 cannot be revoked. If a route rollback sends /meet back to Web, which then redirects to the subdomain, browsers holding a cached 301 would loop. Switch to 301 after the recorded observation window (3 days Meet, 7 days Mail). Keep the subdomain redirects indefinitely, because room links live in calendar invites, ICS files and emails.
  • Never redirect machine traffic. POST/PUT/DELETE, WebSocket upgrades, webhooks (/api/v1/webhooks/mail/*, SNS, Email Routing ingest), OAuth callbacks, /api/build-info and /.well-known/* keep being served on the subdomain until each sender is re-pointed. Open tabs on old builds keep calling the old host.
  • Web adds a temporary /meet/:path* and /mail/:path* → subdomain 307 as its rollback safety net. This is unreachable while the Worker routes exist.

OAuth callback continuity

Mail’s Google/Microsoft connect flow sets mail_oauth_state on the host where it starts. The token exchange must repeat the exact redirect_uri used at authorization. Implemented by #6744 (apps/mail/src/lib/mail/connected/oauth-callback-url.ts).
  1. Register the new callback (https://tuturuuu.com/mail/api/v1/mail/connected/callback) with Google and Microsoft in addition to the old one. Provider console changes are owner actions that need approval. Set MAIL_GOOGLE_APEX_REDIRECT_URI and MAIL_MICROSOFT_APEX_REDIRECT_URI only after registration.
  2. The allowlist is exactly the configured callbacks: MAIL_<P>_REDIRECT_URI (the subdomain) and MAIL_<P>_APEX_REDIRECT_URI. The start request selects the entry whose origin and mount (/mail or root) match the serving request. A forwarded host is never trusted, and an unregistered host fails closed before any pending state is written.
  3. The callback selects from the same allowlist, requires its own path to equal the selected entry’s path, and sends that entry’s exact configured value as the exchange redirect_uri. It never derives the value from the request URL.
  4. mail_oauth_state is set and cleared at the selected mount’s path: /mail/api/v1/mail/connected on the apex and /api/v1/mail/connected on the subdomain. A cookie set on one mount never reaches the other’s callback.
  5. Keep the old callback served (not redirected) on mail.tuturuuu.com until no request older than the 10-minute state TTL can arrive. Remove the old URI from the providers only after the subdomain-retirement decision. No schema migration is needed.
Web’s central sign-in (returnUrl) must accept same-origin /meet/... and /mail/... return paths. On one origin, the Supabase cookie is already present after sign-in, so the verify-token round trip disappears for signed-in users. Per the recorded decision, no session is carried from the subdomain to the apex. Users who already hold the root session stay signed in; only users without one sign in once on the apex at cutover.
  • Before the redirect flips, ship a mobile build whose deep-link resolver accepts tuturuuu.com/meet/r/<code> and /meet/vi/r/<code>. Old app versions keep resolving the subdomain link directly.
  • Universal/App Links on the apex must list /meet/r/*. Associated-domain files are per host.
  • Generated share links (meeting-metadata.ts, notification and AI mail helpers) switch to the registry URL in the same release that flips the flag.

SEO

Canonical URLs become https://tuturuuu.com/meet/..., with hreflang alternates for /meet/vi/.... seo-policy.json gains a base path. Meet public pages (room preview, plans) join Web’s sitemap index. Mail stays noindex. Redirecting subdomain responses carry no canonical. Search engines consolidate signals through the 301 once it replaces the 307.

Same-origin wins

  • Speculation Rules. The launcher and shell links emit <script type="speculationrules"> with prerender at moderate eagerness (hover or pointerdown) for /<app>/ entry points. Today a cross-origin prerender needs the target to opt into credentialed prerender. Same-origin works by default, with cookies. Apps must defer side effects (camera, realtime connect, analytics) until document.prerendering is false. Bound the cost: Chrome caps moderate prerenders, and each prerender is a real SSR request to a billable Worker. Use prefetch for long lists.
  • Cross-document View Transitions. @view-transition { navigation: auto; } only animates same-origin navigations, and cross-origin switches can never use it. Shared view-transition-names on the satellite shell (sidebar, top bar, app switcher) make the shell look persistent while the content swaps. Honor prefers-reduced-motion.
  • No cross-app CORS or proxy hop. Mounted apps call /api/* on Web directly with the shared session. Meet and Mail no longer forward central API calls through their Worker or a Vercel rewrite. The cross-app token handoff disappears for signed-in users.
  • One connection. Every app shares the HTTP/2 or HTTP/3 connection to the Cloudflare edge. Today Meet (Cloudflare IP) and Web (Vercel IP) need separate DNS, TCP and TLS setup on every first switch.

Metrics

App-switch time-to-interactive (ASI): the time from user activation of a link in app A to app B’s ttr:app-ready mark. Each app sets that mark once its primary control is hydrated and its first data has loaded. For prerendered pages, measure from activationStart.

RUM

  • When a cross-app link is activated, a shared, framework-free helper (planned @tuturuuu/utils/rum/app-switch) writes a 30-second, non-HttpOnly ttr_switch=<epoch-ms>.<from-app> cookie with Domain=.tuturuuu.com. The subdomain baseline can read it across the auth-handoff redirects, and the apex can read it after cutover. The destination computes timeOrigin + now() − clickEpoch at ttr:app-ready, then clears the cookie.
  • Attach the dimensions from, to, topology (subdomain|one-origin), prerendered, handoff (whether verify-token ran), nextHopProtocol, connect/TLS duration and the effective connection type.
  • Transport is a sampled (10%), size-bounded sendBeacon, and it follows the existing telemetry consent. Storage is a Workers Analytics Engine dataset (recorded decision). Report p50/p75/p95 by topology.

Ingestion cost and stop gate

Every accepted beacon is a billable Worker request plus an Analytics Engine write, so ingestion follows the Cloudflare billable-operation rules. Client sampling is not a spend cap. No Analytics Engine binding is added to any Worker config until all of these exist and pass with exact-commit evidence:
  1. Server-side write budget. The ingest endpoint caps writes per isolate and per time window, and drops (never queues) beacons beyond the budget.
  2. Bounded work. One beacon writes at most one data point. There are no retries, and a failed write is dropped.
  3. Operator-controlled durable stop fence. One variable or durable flag that an operator flips without a deploy turns ingestion into a cheap 204 with zero writes, and stays off across restarts.
  4. Operation-count acceptance. A test proves the number of writes for N beacons under budget, over budget and with the fence on.
  5. Stop/restart acceptance. A test proves the fence holds across isolate restarts and that re-enabling resumes without a backlog burst.

Lab

A Playwright script (planned scripts/perf/app-switch.lab.mjs) runs on a Studio host, not the M4. It signs in a synthetic account, warms Web’s dashboard and clicks the launcher to Meet and to Mail 30 times each. It runs cold and warm, under no throttling and under a “Slow 4G” profile, against the subdomain and the one-origin staging host. It also records Web TTFB direct versus through Cloudflare (gate W1). Proposed targets:
  • ASI p75 improves by ≥ 30% without prerender.
  • ASI with prerender is ≤ 300 ms at p75 on desktop.
  • Web p75 TTFB regresses by ≤ 10 ms.
  • No change in Meet/Mail 5xx or auth-loop rates.

Implementation plan

Each PR is small, flagged and reversible. Per-app flag TTR_ONE_ORIGIN_<APP> is a Worker var with three values:
  • off: subdomain is canonical; apex route absent or 404.
  • shadow: apex is served with noindex; canonical stays on the subdomain; no redirects.
  • on: apex is canonical; subdomain navigations get 307, later 301.
Lane ownership follows the recorded decisions. “Bridge” is the TanStack Start bridge lane; “L2” is the Web marketing lane; “Ops” is the staged Cloudflare DNS/proxy lane. Operations, each needing user approval through the orchestrator:
  1. O1. Create a non-canonical proxied staging host (for example one-origin-staging.tuturuuu.com) pointing at Web staging, with staging Worker routes for /meet and /mail.
  2. O2. Measure gate W1 and record a baseline ASI on O1.
  3. O3. Proxy the production apex through Cloudflare (no Worker routes yet). Capture DNS records and proxy modes for rollback. Configure the origin-auth header and the Web TRUSTED_PROXY switch together, and record the origin access verification for custom and deployment URLs.
  4. O4. Lift Meet’s wave-1 hold for the base-path build. Add the /meet canonicalization redirect, attach tuturuuu.com/meet/* and set the flag to shadow for the pre-cutover smoke test and routing acceptance.
  5. O5. In the same window, set Meet to on (307). There is no staged canary. Switch to 301 after the observation window.
  6. O6. Register Mail’s new OAuth redirect URIs, then set the MAIL_*_APEX_REDIRECT_URI variables.
  7. O7. Run O4–O5 for Mail.
Rollback:
  • Detach the app’s routes and its canonicalization redirect. Web’s 307 safety net returns traffic to the subdomain, where the flag is off. Capture both before the flip so rollback is one step.
  • Revert O3 by restoring the captured DNS-only records.
  • Never rebuild on the rollback path.

Open questions

Resolved by the recorded decisions. The app.tuturuuu.com fallback is not pursued unless gate W1 fails.