Skip to content

Commit ffb28cc

Browse files
authored
Bypass Start for marketing requests (#1637)
1 parent 77f7ac3 commit ffb28cc

5 files changed

Lines changed: 97 additions & 52 deletions

File tree

apps/cloud/src/edge/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
// ---------------------------------------------------------------------------
2-
// Edge concerns — the analytics/marketing/docs request middlewares that run at
3-
// the worker edge BEFORE the app's own mcp + api dispatch. None of these touch
4-
// the Effect app layer; they proxy or tunnel to external services (the
5-
// marketing worker, Sentry, PostHog, Mintlify docs).
2+
// Edge concerns — request middleware that runs before the app's own mcp + api
3+
// dispatch. These proxy or tunnel to external services without touching the
4+
// Effect app layer. Marketing is dispatched even earlier, from server.ts, so a
5+
// public page never loads the TanStack Start graph.
66
// ---------------------------------------------------------------------------
77

8-
export { marketingMiddleware } from "./marketing";
98
export { sentryTunnelMiddleware } from "./sentry-tunnel";
109
export { posthogProxyMiddleware } from "./posthog";
1110
export { docsProxyMiddleware } from "./docs";

apps/cloud/src/edge/marketing.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "@effect/vitest";
22

3-
import { isMarketingPath } from "./marketing";
3+
import { isMarketingPath, marketingProxyRequest } from "./marketing";
44

55
// On executor.sh the marketing middleware proxies an allow-list of paths to the
66
// `executor-marketing` worker; everything else falls through to the auth-gated
@@ -38,3 +38,54 @@ describe("isMarketingPath", () => {
3838
});
3939
}
4040
});
41+
42+
describe("marketingProxyRequest", () => {
43+
it("routes a signed-out homepage request", () => {
44+
const request = new Request("https://executor.sh/?source=test");
45+
46+
const proxied = marketingProxyRequest(request);
47+
48+
expect(proxied?.url).toBe("https://executor.sh/?source=test");
49+
});
50+
51+
it("leaves the signed-in homepage with the cloud application", () => {
52+
const request = new Request("https://executor.sh/", {
53+
headers: { cookie: "other=value; wos-session=sealed" },
54+
});
55+
56+
expect(marketingProxyRequest(request)).toBeNull();
57+
});
58+
59+
it("routes public content even when a session cookie is present", () => {
60+
const request = new Request("https://executor.sh/blog/post", {
61+
headers: { cookie: "wos-session=sealed" },
62+
});
63+
64+
expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/blog/post");
65+
});
66+
67+
it("rewrites the public home alias to the marketing root", () => {
68+
const request = new Request("https://executor.sh/home?source=test");
69+
70+
expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/?source=test");
71+
});
72+
73+
it("preserves the request method, headers, and body", async () => {
74+
const request = new Request("https://executor.sh/_astro/_ph/capture", {
75+
method: "POST",
76+
headers: { "content-type": "application/json", "x-request-id": "request-1" },
77+
body: JSON.stringify({ event: "test" }),
78+
});
79+
80+
const proxied = marketingProxyRequest(request);
81+
82+
expect(proxied?.method).toBe("POST");
83+
expect(proxied?.headers.get("x-request-id")).toBe("request-1");
84+
await expect(proxied?.json()).resolves.toEqual({ event: "test" });
85+
});
86+
87+
it("does not proxy non-production hosts or app-owned paths", () => {
88+
expect(marketingProxyRequest(new Request("http://executor-cloud.localhost/"))).toBeNull();
89+
expect(marketingProxyRequest(new Request("https://executor.sh/login"))).toBeNull();
90+
});
91+
});

apps/cloud/src/edge/marketing.ts

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@
33
//
44
// On the production domain (`executor.sh`), marketing paths and the
55
// unauthenticated landing page are served by the separate `executor-marketing`
6-
// worker (bound as `env.MARKETING`). In local dev that worker isn't running, so
7-
// unauthenticated visits fall through to the cloud app's routes (the sign-in
8-
// page).
6+
// worker. This module deliberately has no TanStack Start or cloud application
7+
// imports: the Worker entry calls it before loading the Start server graph.
98
// ---------------------------------------------------------------------------
109

11-
import { env } from "cloudflare:workers";
12-
import { createMiddleware } from "@tanstack/react-start";
13-
1410
import { parseCookie } from "../auth/cookies";
1511

1612
const MARKETING_PATHS = [
@@ -28,33 +24,25 @@ const MARKETING_PATHS = [
2824
"/pattern-graph-paper.svg",
2925
];
3026

31-
export const isMarketingPath = (pathname: string) =>
32-
MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`));
33-
34-
const getMarketingWorker = () => env.MARKETING as { fetch: typeof fetch } | undefined;
35-
36-
export const marketingMiddleware = createMiddleware({ type: "request" }).server(
37-
async ({ pathname, request, next }) => {
38-
// Only proxy to the marketing worker on the production domain. In local
39-
// dev we don't run `executor-marketing`, so unauthenticated visits fall
40-
// through to the cloud app's routes (which show the sign-in page).
41-
const host = new URL(request.url).hostname;
42-
if (host !== "executor.sh") return next();
27+
const SESSION_COOKIE = "wos-session";
4328

44-
const shouldProxyToMarketing =
45-
isMarketingPath(pathname) ||
46-
(pathname === "/" && !parseCookie(request.headers.get("cookie"), "wos-session"));
47-
48-
if (!shouldProxyToMarketing) return next();
49-
50-
const marketing = getMarketingWorker();
51-
if (!marketing) return next();
29+
/** Whether an exact pathname belongs to the public marketing worker. */
30+
export const isMarketingPath = (pathname: string): boolean =>
31+
MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`));
5232

53-
const url = new URL(request.url);
54-
// Rewrite /home to / so marketing worker serves its homepage
55-
if (pathname === "/home") {
56-
url.pathname = "/";
57-
}
58-
return marketing.fetch(new Request(url, request));
59-
},
60-
);
33+
/**
34+
* Project a production request onto the marketing service-binding request.
35+
* Returns `null` when the cloud application owns the request instead.
36+
*/
37+
export const marketingProxyRequest = (request: Request): Request | null => {
38+
const url = new URL(request.url);
39+
if (url.hostname !== "executor.sh") return null;
40+
41+
const shouldProxy =
42+
isMarketingPath(url.pathname) ||
43+
(url.pathname === "/" && !parseCookie(request.headers.get("cookie"), SESSION_COOKIE));
44+
if (!shouldProxy) return null;
45+
46+
if (url.pathname === "/home") url.pathname = "/";
47+
return new Request(url, request);
48+
};

apps/cloud/src/server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import * as Sentry from "@sentry/cloudflare";
1212
import handler from "@tanstack/react-start/server-entry";
1313

1414
import { isAppOwnedPath } from "./app-paths";
15+
import { marketingProxyRequest } from "./edge/marketing";
1516
import { makeCloudMcpAgentHandler } from "./mcp/agent-handler";
1617
import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount";
1718
import { parseTraceparent } from "./mcp/traceparent";
@@ -175,6 +176,14 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({
175176

176177
const cloudflareHandler: ExportedHandler<Env> = {
177178
fetch: async (request, env, ctx) => {
179+
// Public pages must not enter TanStack Start: its first-request dynamic
180+
// import loads the entire React + Effect server graph and can take seconds
181+
// on a cold isolate. Classify and service-bind marketing at the Worker
182+
// entry, before telemetry or fetchHandler touches that graph.
183+
const marketingRequest = marketingProxyRequest(request);
184+
const marketing: Fetcher | undefined = env.MARKETING;
185+
if (marketingRequest && marketing) return marketing.fetch(marketingRequest);
186+
178187
// Browser OTLP ingress — before the server span opens: exporter traffic
179188
// must never trace itself (the browser already excludes /v1/traces from
180189
// its own tracing for the same reason).

apps/cloud/src/start.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { loginPath } from "./auth/return-to";
1010
import { prepareMcpOrgScope } from "./mcp/mount";
1111
import {
1212
docsProxyMiddleware,
13-
marketingMiddleware,
1413
openAiAppsChallengeMiddleware,
1514
posthogProxyMiddleware,
1615
sentryTunnelMiddleware,
@@ -89,20 +88,19 @@ const appRequestMiddleware = createMiddleware({ type: "request" }).server(
8988
},
9089
);
9190

92-
// The edge concerns (marketing proxy, docs proxy, sentry tunnel, posthog proxy)
93-
// live in `./edge`; they run before the app's own dispatch. Ordering is
94-
// load-bearing: marketing first (production landing/page proxy), then the docs
95-
// proxy and analytics tunnels, then the unified app plane (api + mcp), and last
96-
// the SSR auth gate — it only sees document requests nothing above claimed, so
97-
// signed-out visitors are redirected to /login before the SPA (and its
98-
// app-shell skeleton) is served. The docs proxy sits among the edges (not after
99-
// the auth gate) because `/docs` is public and must skip the sign-in redirect;
100-
// its path is disjoint from every other matcher, so its slot is not otherwise
101-
// load-bearing.
91+
// The remaining edge concerns (docs proxy, sentry tunnel, posthog proxy) live
92+
// in `./edge`; they run before the app's own dispatch. Marketing is handled in
93+
// server.ts before this module is loaded. Ordering here is load-bearing: public
94+
// challenges and docs, then analytics tunnels, then the unified app plane (api
95+
// + mcp), and last the SSR auth gate — it only sees document requests nothing
96+
// above claimed, so signed-out visitors are redirected to /login before the SPA
97+
// (and its app-shell skeleton) is served. The docs proxy sits among the edges
98+
// (not after the auth gate) because `/docs` is public and must skip the sign-in
99+
// redirect; its path is disjoint from every other matcher, so its slot is not
100+
// otherwise load-bearing.
102101
export const startInstance = createStart(() => ({
103102
requestMiddleware: [
104103
openAiAppsChallengeMiddleware,
105-
marketingMiddleware,
106104
docsProxyMiddleware,
107105
sentryTunnelMiddleware,
108106
posthogProxyMiddleware,

0 commit comments

Comments
 (0)