Caching & Performance
How Crop Picker stays fast with Vercel edge SWR caching, the per-route cache rules, stale-while-revalidate semantics, region handling, invalidation, and what is not cached.
How Crop Picker stays fast and keeps Supabase traffic low — and why the caching strategy holds up as new regions come online.
#Caching layers, outermost first
| Layer | What it caches | TTL | Controlled by |
|---|---|---|---|
| Vercel CDN | SSR HTML + API JSON | 10 min – 24 h SWR | routeRules in nuxt.config.ts |
| Browser HTTP cache | Static assets (hashed) | 1 yr | Nuxt build pipeline |
| Nuxt useAsyncData | SSR payload, client nav | in-memory | Nuxt runtime |
There is no Redis or application-level cache. The CDN layer is doing all the heavy lifting; Supabase is only hit on cache MISSes or SWR revalidations.
#Route rules (in nuxt.config.ts)
- / → redirect to /crops
- /crops → swr 600 (10 min)
- /crops/ → swr 3600 (1 hr)
- /about → swr 3600
- /api/v1/meta → swr 86400 (24 hr)
- /api/v1/regions → swr 3600
- /api/v1/data-sources → swr 3600
- /api/v1/crops → cache false (filter/pagination query params; served from origin)
- /api/v1/crops/compare → cache false (query-string params; served from origin)
- /api/v1/crops/ → swr 3600
- /buyers → cache false (filter query params; served from origin)
- /sources → cache false (filter query params; served from origin)
- /api/v1/buyers → cache false (filter query params; served from origin)
- /api/v1/audit-log → cache false (always fresh)
- /sitemap.xml → swr 3600
- /robots.txt → swr 86400
Nitro compiles these into Cache-Control headers with s-maxage and stale-while-revalidate at build time. Vercel's CDN honors them automatically — no dashboard toggle required.
#Stale-while-revalidate semantics
After the s-maxage window expires:
- A request comes in → CDN serves the stale cached response immediately (zero wait for the user)
- The CDN fires an async revalidation request to the origin in the background
- When the fresh response lands, it replaces the stale copy for subsequent requests
The user never waits on a revalidation. The only slow requests are true cache MISSes (first hit per edge region, or after a deploy).
#Verifying the cache is live
Run: curl -sI https://crops.every.farm/api/v1/meta
Look for cache-control and x-vercel-cache headers in the response. After the second request to the same edge node you should see x-vercel-cache: HIT.
#Region handling
A brief on how the cache stays correct across users in different regions.
Problem: every visitor hits the same URL (e.g. /crops/elderberry) but may be in a different region. If the CDN cached a region-specific payload, users in other regions would see the wrong snowflake scores.
Solution: the server always renders with the platform's default region. The CDN caches that single payload per URL. After hydration, useUserLocation reads the user's region from localStorage and, if it differs from the default, the region-aware composables (useCropDetail, useCrops, useCropComparison, useNearbyBuyers) refetch client-side via their watch hooks on regionKey.
Trade-off: a brief (~100–300 ms) window after hydration where a non-default-region user sees default-region data. Functional correctness is preserved; the flash is cosmetic.
Net effect for adding regions: zero cache config changes. Adding a row to regions + running the ingestion for that region is sufficient. The picker auto-updates, client-side refetches take care of data correctness.
#Invalidation
Cache entries expire on TTL, and every deploy purges the edge cache globally. For the platform's ingestion cadence (data updates on the order of days, not seconds) this provides a predictable freshness window for consumers.
#What is NOT cached
- /api/v1/audit-log — explicit cache: false. Admin and AI-agent consumers need real-time changes.
- Any client-side Supabase query (they bypass Vercel entirely, going direct to Supabase). If a composable's data becomes hot enough to matter, move that read through /api/v1/*.
- First request per edge region after deploy (always MISS; normal and harmless).
#Summary
For consumers of the public API, this means most requests resolve at the CDN edge in under 50 ms globally, and the platform behaves consistently under traffic spikes because origin load is decoupled from request volume.
Updated
Something missing or out of date? Tell us — the docs are updated with every release.