Files
paseo/packages/website/src/server-entry.ts
T
Mohamed Boudra d4841b68ff fix(website): detect the visitor platform during SSR (#4222)
The homepage download CTA was chosen by a client-only hook that started at
"mac-silicon" and corrected itself in an effect, so the server-rendered HTML
said "Download for Mac" for every visitor and only became right after
hydration. On a phone that window is long enough to photograph. Worse, the
detection had no mobile branch at all: Android user agents also say "Linux",
so once hydration did land, a Pixel was offered an AppImage.

Resolve the platform from the request user agent during SSR instead, so the
first paint is already correct. Mobile visitors get the App Store or Play
Store as the primary action and a link to /download for the desktop build,
rather than a .dmg they cannot open.

The markup now varies by user agent, so HTML and server-function responses
carry `Cache-Control: private, no-store` and `Vary: User-Agent`. An absent
cache-control is not no-store — a shared cache may assign heuristic freshness,
and turning on Workers Cache would store a headerless 200 for two hours under
a key that ignores the user agent, letting one Android visitor pin the Play
Store button for every Mac visitor after them.

Also tighten the mobile layout the wrong CTA was sitting in: the primary
action gets its own row so the icon buttons stop orphaning one of themselves,
provider cards drop to a two-column grid, and the hardcoded mobile line break
in the phone showcase gives way to a balanced measure.
2026-09-02 15:46:16 +02:00

106 lines
3.8 KiB
TypeScript

import startEntry from "@tanstack/react-start/server-entry";
import { getAndroidVersionCode } from "~/android-version";
import { getCanonicalRedirect } from "~/canonical-url";
import { getDoc } from "~/docs";
import { getLatestAndroidVersion } from "~/latest-release";
import { buildLlmsTxt } from "~/llms";
interface WebsiteEnv {
WEBSITE_CACHE?: KVNamespace;
}
function markdownResponse(body: string): Response {
return new Response(body, {
headers: {
"content-type": "text/markdown; charset=utf-8",
"cache-control": "public, max-age=300, s-maxage=300",
},
});
}
function plainTextResponse(body: string): Response {
return new Response(body, {
headers: {
"content-type": "text/plain; charset=utf-8",
"cache-control": "public, max-age=300, s-maxage=300",
},
});
}
function docSlugFromMarkdownPath(pathname: string): string | null {
if (pathname === "/docs.md") return "";
const match = pathname.match(/^\/docs\/(.+)\.md$/);
return match ? match[1] : null;
}
/**
* SSR reads the request user agent to pick the download call to action (see
* `~/platform`), so two visitors on the same URL get different markup. Nothing
* caches these responses today — the zone cache sits behind the Worker and
* Workers Cache is off — but an absent `cache-control` is not `no-store`: a
* shared cache may assign heuristic freshness (RFC 9111 §4.2.2), and enabling
* Workers Cache would store a headerless 200 for two hours under a key that
* ignores `user-agent`. Either way one Android visitor could pin the Play Store
* button for every Mac visitor after them.
*
* `private` is what actually stops that. `Vary` records the contract so a
* compliant cache stays correct if the policy is ever loosened. Static assets
* never reach this Worker, so their long-lived `_headers` rules are untouched.
*/
function withoutSharedCaching(response: Response): Response {
const result = new Response(response.body, response);
result.headers.set("cache-control", "private, no-store");
result.headers.set("vary", "user-agent");
return result;
}
function variesByUserAgent(pathname: string, response: Response): boolean {
if (pathname.startsWith("/_serverFn/")) return true;
return response.headers.get("content-type")?.includes("text/html") ?? false;
}
export default {
async fetch(request: Request, env: WebsiteEnv, context: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const environment = import.meta.env.DEV ? "development" : "production";
const canonicalRedirect = getCanonicalRedirect(url, environment);
if (canonicalRedirect) {
return Response.redirect(canonicalRedirect, 301);
}
if (url.pathname === "/cloud" || url.pathname === "/cloud/") {
url.pathname = "/hub";
return Response.redirect(url.toString(), 301);
}
const altRedirectMatch = url.pathname.match(/^\/docs\/alternatives\/(.+?)\/?$/);
if (altRedirectMatch) {
url.pathname = `/alternatives/${altRedirectMatch[1]}`;
return Response.redirect(url.toString(), 301);
}
if (url.pathname === "/llms.txt") {
return markdownResponse(buildLlmsTxt());
}
if (url.pathname === "/android-version.txt") {
const version = await getLatestAndroidVersion({
cache: env.WEBSITE_CACHE ?? null,
waitUntil: (promise) => context.waitUntil(promise),
});
return plainTextResponse(`${getAndroidVersionCode(version)}\n`);
}
const slug = docSlugFromMarkdownPath(url.pathname);
if (slug !== null) {
const doc = getDoc(slug);
if (!doc) return new Response("Not found", { status: 404 });
return markdownResponse(doc.content);
}
const response = await startEntry.fetch(request);
return variesByUserAgent(url.pathname, response) ? withoutSharedCaching(response) : response;
},
};