Site Fix Notes
A Vercel Origin Transfer Warning: Making Gallery Filters Static Again
Tracing a Fast Origin Transfer warning to crawlers, filter combinations, and dynamic rendering, then restoring static delivery without losing gallery navigation.
Contents
Vercel sent a warning: my free team had consumed 75% of its 10 GB Fast Origin Transfer allowance. With photography, calligraphy scans, and a 3D homepage on this site, large assets seemed an obvious suspect. The request data pointed mainly to the calligraphy gallery, where fixed content was repeatedly rendered by functions as crawlers followed filter combinations.
The aim of this change was to keep ordinary personal-site use within the free allowances, leaving the domain as the main recurring expense. That required identifying the work behind the meter before choosing a paid plan or another server.
Which Transfer Was Growing?
Fast Data Transfer measures traffic between visitors and Vercel's CDN. Fast Origin Transfer measures traffic between the CDN and services such as functions, Middleware, and caches. A request can pass through several stages; caching a function response at the CDN can reduce subsequent origin work. At the time of this investigation, Hobby included 100 GB and 10 GB respectively. Vercel CDN usage
The notification alone identified a team allowance approaching its limit. Its “8h” marker did not mean that 7.5 GB had been consumed in eight hours, and request counts were not visitor counts.
On the morning of September 6, 2026, the project's Last 30 Days view showed:
| Metric | Observed usage |
|---|---|
| Fast Origin Transfer | About 7.72 GB / 10 GB |
| Fast Data Transfer | About 6.08 GB / 100 GB |
| Function Invocations | 512,779 |
| Active CPU | About 2 hours 20 minutes |
This project accounted for 99.9% of the team's origin transfer. These were observations over the selected window, not a calendar-month invoice or a forecast.
Delivery paths mattered too. Photography, calligraphy, and blog images use img.huzejun.com directly, with unoptimized on their image components. The 3D GLB is a same-origin static asset with long-lived caching. Both affect downloads, but the evidence did not identify either as the main cause of this origin-transfer warning.
How 95 Records Became Repeated Function Work
The most recent 12 hours of function activity gave a clearer lead:
| Route | Function invocations | Page cache hit rate |
|---|---|---|
| Calligraphy gallery | About 6.9K | 0% |
| Calligraphy detail | About 4.7K | 0% |
| Photography detail | 31 | 0% |
During the same period, the bot grouping showed about 10K Amazonbot requests with a 0.1% cache hit rate. One log sample carried six gallery conditions, including work type, script, series, tag, and year, then executed both the locale Middleware and the page function.
That supported a specific diagnosis: crawlers were traversing filter combinations whose dynamic responses kept reaching the origin. It did not establish a bot attribution for every byte in the full monthly window.
The gallery contained only 95 calligraphy records, but filters and contextual work links generated many addresses:
/en/calligraphy/gallery?kind=practice
/en/calligraphy/gallery?kind=practice&year=2020Detail links carried these conditions onward so that previous, next, and return navigation retained the selected collection. That behavior was useful. The expensive part was reading its searchParams in the server page.
With this project's Next.js 16.3 configuration, reading the page's server searchParams introduces request-time rendering. Having generateStaticParams in a detail route does not by itself prove that the final response is static. Next.js page parameters
Before the change, sampled responses carried private, no-cache, no-store, and the build manifest lacked complete prerenders for these pages. Those outputs were stronger evidence than the apparent intent of the source code.
Separating Content from Browsing Context
Artwork text, image URLs, and metadata change when content is published. Filters and reading position change as someone browses. I moved them to separate places in the implementation.
The server now builds a default gallery and complete content for each work without waiting for filter parameters. The browser reads the URL and derives filtered results, neighboring works, and return links from the supplied records. Addresses remain shareable, and Back and Forward restore the selected context.
A small Suspense child owns useSearchParams and reports query changes to its parent. The reading surface remains outside that boundary, preserving useful static HTML instead of withholding the whole gallery until JavaScript runs. useSearchParams and static rendering
There is a deliberate tradeoff: opening a filtered address first receives the default static content, then the browser applies the query. The initial HTML retains the content, while restoration of the filter state requires JavaScript.
Filter changes now use native history. Discrete selections call pushState; typing in search calls replaceState so every character does not add a history entry. Next.js supports updating history without a full reload and synchronizes these operations with useSearchParams. Whether this project makes additional data requests is checked separately in the request log. Native History API
Rapid changes also merge against the URL at the moment of the action. Merging only against state captured by the previous render can lose the first selection when a second one follows quickly.
To support detail navigation in the browser, I separated pure calligraphy data operations from the generated-content reader. Details receive an index containing only the extra navigation fields they need. For 95 records, it occupies 78,160 B instead of 180,672 B for the complete records. That 57% reduction applies to this index, not to total site traffic or the Vercel bill.
Reducing Unnecessary Routing and Crawling
Several related changes support static delivery:
- Explicit
/zh/...and/en/...paths bypass locale Middleware. The root and unprefixed entries still negotiate a language and redirect. - Public content parameters are closed at build time. Unknown works or locales return 404; old calligraphy aliases use permanent configuration redirects that preserve their queries.
- Gallery and work pages gain canonical URLs. Contextual query links disable automatic prefetching and carry
nofollow. robots.txtexcludes archive query combinations and declares a disallow rule for the identified Amazonbot and SemrushBot traffic.
Robots directives and nofollow are not hard traffic controls. Crawlers can cache or ignore them, so static delivery must continue reducing function work even while old addresses are still requested.
Verifying Delivery Before Claiming Savings
The build now runs delivery:verify. It reads the actual manifests and sitemap, checking that public routes have complete prerenders, unknown parameters cannot request runtime generation, and cache policies have not become private again. The homepage alone retains bounded ISR for recent listening activity.
Before adding this article, the optimized build verified 360 public localized paths and 12 closed route templates, with all 422 tests passing. Another 1,425 query combinations compared complete calligraphy data with the compact index, checking positions, adjacent works, and related recommendations.
In local production mode, the filtered and unfiltered calligraphy gallery returned the same 527,797 B HTML body with:
x-nextjs-cache: HIT
Cache-Control: s-maxage=31536000These were prerendered responses, not arbitrary dynamic responses assigned a year of caching. They were also local results, not measurements of Vercel's production edge hit rate.
Browser checks covered filters, reloads, Back and Forward, keyboard navigation, both languages, and both themes. A local request log confirmed that two successive calligraphy filters, and photography search combined with a year, produced no new page or RSC request.
This demonstrates that avoidable dynamic work has been removed. It does not yet establish a percentage reduction in billing. After deployment, the useful comparison is daily additions to origin transfer, function invocations, and CPU time. A release does not immediately erase the existing 30-day total.
Does the Site Still Need Pro or Its Own Server?
At the time of investigation, Vercel Pro had a $20 monthly base charge, including one deploying seat and a $20 monthly usage credit. Usage beyond the credit can add charges. Vercel Pro
For this project, retaining Hobby while removing repeated rendering better fits the current goal. A self-managed server might reduce the base rent compared with continuing paid hosting, but it also brings deployment, patching, backup, and incident work. This quota notification alone did not justify a move.
R2 requires a separate check. Its Standard free tier includes 10 GB-month of storage, one million Class A operations, and ten million Class B operations each month, with free Internet egress. An absence of warnings is not proof of a zero bill. R2 pricing
Keeping routine costs close to the domain fee remains the target. Homepage ISR, the music-artwork proxy, cache misses, and unusual traffic can still consume allowances. The durable improvement is to serve fixed content statically and make an accidental return to dynamic delivery fail visibly during the build.

