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 athttps://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/*.
/_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 andgetConfiguredMailApiBaseUrlorigin 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.tsdefinedefaults,void.config.tsvarsNEXT_PUBLIC_APP_URL. - Durable share links:
packages/meet-core/.../meeting-metadata.tsandapps/meet/src/runtime/page-model.server.tscanonicals (/r/<code>), ICS UIDs (meet-ics.ts, which is an identifier and should keep its value). - Machine endpoints:
wrangler.email-routing.jsoncMAIL_INGEST_URL, SES SNS subscription, and theMAIL_{GOOGLE,MICROSOFT}_REDIRECT_URIsecrets. - Mobile:
apps/mobile/lib/core/router/deep_links.dart(host == 'meet.tuturuuu.com'),env.dart,api_origins.dart. - Meet’s
/live-connectWebSocket comparesOriginagainstNEXT_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: proxytuturuuu.com through Cloudflare
to the existing Vercel origin.
Recommendation: (a), direct zone routes, one pair per app.
/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
/meetor/mailexactly (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 matchestuturuuu.com/meet/*. - Exact path equality never touches
/meeting,/meet-together,/mailboxor 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.
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 tocloudflare-origin, which trustscf-connecting-iponly 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, orcf-connecting-ipbecomes 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.appdeployment URL directly. Before Web trustscf-connecting-ipin 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 forgedcf-connecting-ipresolves to the caller’s real IP or is refused) with O3.
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 exampleapp.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>tohttps://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
/meetback 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-infoand/.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 setsmail_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).
- 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. SetMAIL_GOOGLE_APEX_REDIRECT_URIandMAIL_MICROSOFT_APEX_REDIRECT_URIonly after registration. - The allowlist is exactly the configured callbacks:
MAIL_<P>_REDIRECT_URI(the subdomain) andMAIL_<P>_APEX_REDIRECT_URI. The start request selects the entry whose origin and mount (/mailor root) match the serving request. A forwarded host is never trusted, and an unregistered host fails closed before any pending state is written. - 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. mail_oauth_stateis set and cleared at the selected mount’s path:/mail/api/v1/mail/connectedon the apex and/api/v1/mail/connectedon the subdomain. A cookie set on one mount never reaches the other’s callback.- Keep the old callback served (not redirected) on
mail.tuturuuu.comuntil 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.
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.
Mobile and links
- 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 becomehttps://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">withprerenderatmoderateeagerness (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) untildocument.prerenderingis false. Bound the cost: Chrome capsmoderateprerenders, and each prerender is a real SSR request to a billable Worker. Useprefetchfor long lists. - Cross-document View Transitions.
@view-transition { navigation: auto; }only animates same-origin navigations, and cross-origin switches can never use it. Sharedview-transition-names on the satellite shell (sidebar, top bar, app switcher) make the shell look persistent while the content swaps. Honorprefers-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’sttr: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-HttpOnlyttr_switch=<epoch-ms>.<from-app>cookie withDomain=.tuturuuu.com. The subdomain baseline can read it across the auth-handoff redirects, and the apex can read it after cutover. The destination computestimeOrigin + now() − clickEpochatttr:app-ready, then clears the cookie. - Attach the dimensions
from,to,topology(subdomain|one-origin),prerendered,handoff(whetherverify-tokenran),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:- Server-side write budget. The ingest endpoint caps writes per isolate and per time window, and drops (never queues) beacons beyond the budget.
- Bounded work. One beacon writes at most one data point. There are no retries, and a failed write is dropped.
- Operator-controlled durable stop fence. One variable or durable flag that an
operator flips without a deploy turns ingestion into a cheap
204with zero writes, and stays off across restarts. - Operation-count acceptance. A test proves the number of writes for N beacons under budget, over budget and with the fence on.
- 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 (plannedscripts/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 flagTTR_ONE_ORIGIN_<APP> is a
Worker var with three values:
off: subdomain is canonical; apex route absent or 404.shadow: apex is served withnoindex; canonical stays on the subdomain; no redirects.on: apex is canonical; subdomain navigations get 307, later 301.
Operations, each needing user approval through the orchestrator:
- O1. Create a non-canonical proxied staging host (for example
one-origin-staging.tuturuuu.com) pointing at Web staging, with staging Worker routes for/meetand/mail. - O2. Measure gate W1 and record a baseline ASI on O1.
- 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_PROXYswitch together, and record the origin access verification for custom and deployment URLs. - O4. Lift Meet’s wave-1 hold for the base-path build. Add the
/meetcanonicalization redirect, attachtuturuuu.com/meet/*and set the flag toshadowfor the pre-cutover smoke test and routing acceptance. - O5. In the same window, set Meet to
on(307). There is no staged canary. Switch to 301 after the observation window. - O6. Register Mail’s new OAuth redirect URIs, then set the
MAIL_*_APEX_REDIRECT_URIvariables. - O7. Run O4–O5 for Mail.
- 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. Theapp.tuturuuu.com
fallback is not pursued unless gate W1 fails.