From 1ebcfc777e8a5ff0cd9881ca469680c918265fe6 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Tue, 6 Oct 2026 16:43:25 -0700 Subject: [PATCH] Perf: Serve homepage and assets without invoking Functions Add _routes.json so /, /robots.txt and /assets/* skip the worker and don't count against the Functions request quota. rate-limit.js moves to assets/ so the homepage reads it client-side instead of via HTMLRewriter. Co-Authored-By: Claude Opus 5.5 --- _routes.json | 5 +++++ {functions/_utils => assets}/rate-limit.js | 3 ++- functions/[[path]].js | 20 +++----------------- index.html | 4 ++++ 4 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 _routes.json rename {functions/_utils => assets}/rate-limit.js (62%) diff --git a/_routes.json b/_routes.json new file mode 100644 index 0000000..f9ef377 --- /dev/null +++ b/_routes.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "include": ["/*"], + "exclude": ["/", "/index.html", "/robots.txt", "/assets/*"] +} diff --git a/functions/_utils/rate-limit.js b/assets/rate-limit.js similarity index 62% rename from functions/_utils/rate-limit.js rename to assets/rate-limit.js index 531bcdc..5ec0abb 100644 --- a/functions/_utils/rate-limit.js +++ b/assets/rate-limit.js @@ -1,5 +1,6 @@ // New searches (cache misses) allowed per IP per day, reset at 00:00 UTC. Cache hits never count. -// Safe to edit by hand. The homepage reads these automatically; README.md needs a manual update. +// Safe to edit by hand. Imported by the worker and by the homepage (as a static file, so no Functions quota); +// README.md needs a manual update. // The burst limit (~10 req/10s) is a Cloudflare WAF rule, not set here. export const DAILY_LIMIT = 35; export const FREE_DAILY_LIMIT = 100; diff --git a/functions/[[path]].js b/functions/[[path]].js index d9b1c38..2e2c770 100644 --- a/functions/[[path]].js +++ b/functions/[[path]].js @@ -2,7 +2,7 @@ import { braveImageSearch } from "./_utils/brave.js"; import { bingImageSearchFallback } from "./_utils/bing.js"; import { freeImageUrls, parseFreeSource, FREE_UA } from "./_utils/free.js"; import { isFlickrPlaceholder } from "./_utils/flickr.js"; -import * as LIMITS from "./_utils/rate-limit.js"; +import * as LIMITS from "../assets/rate-limit.js"; const TTL_SECONDS = 90 * 24 * 60 * 60; const MAX_INDEX = 20; @@ -13,8 +13,8 @@ export async function onRequest(context) { const path = params.path?.join("/") || ""; const asset = f => env.ASSETS.fetch(new Request(new URL(`/assets/${f}`, url.origin))); - if (!path || path === "index.html") return homepage(env, url); - if (path === "robots.txt" || path.startsWith("assets/")) { + // _routes.json serves these statically without invoking this function; kept as a fallback + if (!path || path === "index.html" || path === "robots.txt" || path.startsWith("assets/")) { return env.ASSETS.fetch(request); } if (path === "favicon.ico") return asset("favicon.ico"); @@ -207,20 +207,6 @@ async function* webImageUrls(context, query) { yield* (await bingImageSearchFallback(query)) || []; } -// Fills each with that export of rate-limit.js, so the page never shows stale limits. -// Fetched without the browser's If-None-Match and served without an ETag, so a cached copy can't either. -async function homepage(env, url) { - const res = await env.ASSETS.fetch(new Request(url)); - const headers = new Headers(res.headers); - headers.delete("etag"); - return new HTMLRewriter().on("[data-limit]", { - element: e => { - const v = LIMITS[e.getAttribute("data-limit")]; - if (v != null) e.setInnerContent(`${v}`); - } - }).transform(new Response(res.body, { status: res.status, headers })); -} - async function setupSurreal(env) { if (!env.SURREAL_URL || !env.SURREAL_USER || !env.SURREAL_PASS) return jsonResponse(500, { error: "SurrealDB env vars missing" }); const sql = `DEFINE NAMESPACE IF NOT EXISTS direct_img; diff --git a/index.html b/index.html index dc3996a..9e5a1e2 100644 --- a/index.html +++ b/index.html @@ -127,5 +127,9 @@ Use images to complement your responses, powered by Brave. },()=>getSelection().selectAllChildren(p)); }; +