refactor(stats): serve opencode.ai/data straight from the stats worker (#62192)

This commit is contained in:
Dax 2026-11-05 16:39:45 -05:00 • committed by GitHub
parent 80bf7f7b0d
commit 2f9cbcf4eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 107 additions and 8 deletions

View file

@ -1,5 +1,6 @@
import { EMAILOCTOPUS_API_KEY } from "./app"
import { domain } from "./stage"
import { LOCALES } from "../packages/stats/app/src/lib/language"
import { domain, zoneID } from "./stage"
////////////////
// DATABASE
@ -62,16 +63,42 @@ new sst.x.DevCommand("StatsStudio", {
// APP
////////////////
export const app = new sst.cloudflare.x.SolidStart("Stats", {
export const publicUrl = `https://${domain}/data`
const app = new sst.cloudflare.x.SolidStart("Stats", {
path: "packages/stats/app",
buildCommand: "bun run build",
domain: `stats.${domain}`,
link: [database, EMAILOCTOPUS_API_KEY],
environment: {
PUBLIC_URL: `https://${domain}/data`,
PUBLIC_URL: publicUrl,
},
transform: {
server: {
url: false,
},
},
})
// The site serves itself under /data and /<locale>/data, and these routes are its only way in: it has no custom domain
// or workers.dev URL. Route patterns can't wildcard the middle of a path, hence one pair per locale. `sst dev` deploys
// no Worker.
const appServer = app.nodes.server
if (appServer) {
for (const prefix of ["data", ...LOCALES.map((locale) => `${locale}/data`)]) {
const name = prefix.replaceAll("/", "")
for (const [suffix, pattern] of [
["", `${domain}/${prefix}`],
["Path", `${domain}/${prefix}/*`],
]) {
new cloudflare.WorkersRoute(`StatsRoute${name}${suffix}`, {
zoneId: zoneID,
pattern,
script: appServer.nodes.worker.scriptName,
})
}
}
}
////////////////
// SERVICES
////////////////

View file

@ -40,6 +40,6 @@ To run one sync pass on your machine against a stage's resources, run `bun sst s
## Public page caching
Stats HTML, JSON, and Markdown use a one-minute browser cache and a five-minute shared cache. Expired responses must revalidate instead of serving stale snapshots. The console `/data` proxy inherits this policy from the stats app.
Stats HTML, JSON, and Markdown use a one-minute browser cache and a five-minute shared cache. Expired responses must revalidate instead of serving stale snapshots.
Cloudflare must respect these origin headers. The live `Stats pages: respect short origin cache TTL` cache rule applies to `/data` and `/data/*` on `opencode.ai` and `stats.opencode.ai`: browser TTL respects origin, edge TTL uses cache-control headers and bypasses cache when absent, and serving stale content while updating is disabled. This dashboard-managed rule overrides the zone's four-hour browser-cache minimum; preserve it when changing Cloudflare configuration. Purge both stats URL prefixes after changing the policy to clear existing cached responses.
The Worker has no custom domain. `infra/stats.ts` routes `/data`, `/data/*`, `/<locale>/data` and `/<locale>/data/*` on the main host straight to it, so its responses skip the Cloudflare cache and reach browsers with these headers unchanged. Static assets are uploaded under `/data`, and the build moves Nitro's `_headers` to the assets root so they stay immutable.

View file

@ -49,6 +49,8 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
document.documentElement.lang = tag(store.locale)
document.documentElement.dir = dir(store.locale)
document.documentElement.dataset.locale = store.locale
// Remember a language picked by URL, so unprefixed /data links redirect back to it.
if (fromPathname(window.location.pathname)) document.cookie = cookie(store.locale)
})
return {

View file

@ -9,7 +9,18 @@ import {
modelJson,
modelMarkdown,
} from "./lib/agent-formats"
import { fromPathname, LOCALE_HEADER, pageUrl, parseLocale, prefersMarkdown, route, strip } from "./lib/language"
import {
basePath,
cookie,
fromPathname,
LOCALE_HEADER,
localeFromRequest,
pageUrl,
parseLocale,
prefersMarkdown,
route,
strip,
} from "./lib/language"
import { loadComparePage, loadHomePage, loadLabPage, loadModelPage } from "./lib/page-data"
import { canonicalModelEntry, catalogModelPath, findModelCatalogLab, loadModelCatalog } from "./routes/model-catalog"
import { statsPageCacheControl } from "./routes/stats-cache"
@ -41,6 +52,8 @@ export default createMiddleware({
const request = event.request
if (request.method !== "GET" && request.method !== "HEAD") return
const url = new URL(request.url)
const localized = localizedRedirect(request, url)
if (localized) return localized
const path = strip(url.pathname)
const parsed = parsePath(path)
if (!parsed) return
@ -69,6 +82,43 @@ export default createMiddleware({
},
})
// Unprefixed pages redirect to the visitor's language from their cookie or Accept-Language. /en/data stays put so
// English remains reachable for visitors whose browser prefers another language.
function localizedRedirect(request: Request, url: URL) {
if (fromPathname(url.pathname)) return undefined
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) return undefined
if (isDataBypassPath(url.pathname)) return undefined
const accept = request.headers.get("accept")
// Agents asking for Markdown get the English page in place.
if (prefersMarkdown(accept)) return undefined
if (accept && !accept.includes("text/html") && !accept.includes("*/*")) return undefined
const locale = localeFromRequest(request)
if (locale === "en") return undefined
const headers = new Headers({
// route() normalizes /data to /data/, so keep the visitor's own trailing slash.
Location: `${url.pathname === basePath ? `/${locale}${basePath}` : route(locale, url.pathname)}${url.search}`,
"Cache-Control": "private, no-store",
Vary: "Accept, Accept-Language, Cookie",
})
headers.append("Set-Cookie", cookie(locale))
return new Response(null, { status: 308, headers })
}
function isDataBypassPath(pathname: string) {
return (
pathname.startsWith(`${basePath}/_build/`) ||
pathname.startsWith(`${basePath}/api/`) ||
pathname.startsWith(`${basePath}/stats/`) ||
pathname.startsWith(`${basePath}/_server`) ||
pathname === `${basePath}/banner.jpg` ||
pathname === `${basePath}/banner.png` ||
pathname === `${basePath}/sitemap.xml` ||
pathname.endsWith("/llms.txt") ||
pathname.endsWith(".md") ||
pathname.endsWith(".json")
)
}
function parsePath(path: string) {
const segments = path.split("/").filter(Boolean).slice(1)
if (segments.some((segment) => !/^[\w.-]+$/.test(segment))) return undefined

View file

@ -1,3 +1,5 @@
import { readFile, rm, writeFile } from "node:fs/promises"
import path from "node:path"
import { solidStart } from "@solidjs/start/config"
import { nitro } from "nitro/vite"
import { defineConfig, type PluginOption } from "vite"
@ -7,12 +9,30 @@ export default defineConfig({
plugins: [
solidStart({ middleware: "./src/middleware.ts" }) as PluginOption,
nitro({
// The Worker owns opencode.ai/data/* directly, so static assets are uploaded under /data like the pages that
// reference them. Nitro's baseURL would do this too, but its router would then stop serving /<locale>/data.
output: { publicDir: "{{ output.dir }}/public/data" },
compatibilityDate: "2024-09-19",
preset: "cloudflare-module",
cloudflare: {
nodeCompat: true,
},
}),
{
// Cloudflare only reads _headers at the assets root, but Nitro writes it into publicDir with unprefixed paths.
name: "stats:headers",
buildApp: {
order: "post",
async handler() {
const source = path.join(import.meta.dirname, ".output/public/data/_headers")
await writeFile(
path.join(import.meta.dirname, ".output/public/_headers"),
(await readFile(source, "utf8")).replace(/^\//gm, "/data/"),
)
await rm(source)
},
},
},
],
server: {
allowedHosts: true,

View file

@ -43,7 +43,7 @@ export default $config({
await import("./infra/share.js")
return {
...(stats ? { StatsUrl: stats.app.url } : {}),
...(stats ? { StatsUrl: stats.publicUrl } : {}),
AwsStage: stage.awsStage,
}
},