A blog at the top of the CPU chart
On October 12 I was looking at usage and found something odd. This dev blog was the team's top Fluid CPU consumer. A near-static site with a handful of posts had no obvious reason to be there.
The cause was the proxy (proxy.ts). The proxy runs as a function on every request, including requests that are CDN cache HITs. The page may come straight from cache, but the proxy in front of it still wakes up and burns CPU every time.
So I split the proxy invocations apart. Of 49, only 17 were page views. The other 32 were prefetches sent by Next.js <Link>, carrying Sec-Fetch-Dest: empty. Before anyone clicked anything, the proxy was running just because a link was visible on screen.
Do you know what share of your middleware or proxy invocations are pages a person actually opened?
Why I missed it
The code already had an isPageView check: a request counts as a page view if Sec-Fetch-Dest is document or the header is missing. The problem was where that check lived. It ran inside the function. Even when it recognized a prefetch and did nothing, the function had already been invoked to find that out. The filtering logic was right; its location was wrong.
What would you do? Make the early return inside the function cheaper, or keep the function from being called at all?
The fix: move the filter to the routing layer
I changed the proxy matcher to mirror isPageView. Only requests where Sec-Fetch-Dest is document or absent reach the proxy; prefetches are skipped at routing. Prefetches no longer wake the function. The change was one file, proxy.ts: 8 lines added, 1 removed.
The lesson is simple. An early return inside a function does not save you the invocation. The invocation is the cost. If a request can be filtered, filter it before it reaches the function.
Self-check
- Have you confirmed whether your middleware or proxy also runs on cache HIT requests?
- Have you split invocations by a header like
Sec-Fetch-Destand counted the actual page-view ratio? - Are there conditions you check inside the function that could move into the matcher at the routing layer?
The honest part
The evidence is one day, October 12, and 49 invocations. I don't know whether that ratio holds on other days, and I haven't yet measured how much CPU usage actually dropped after the change. Skipping prefetches in the matcher also means prefetch responses no longer get whatever the proxy did to them, and I haven't watched long enough to be sure that's harmless in every case.