Astro builds static HTML at compile time. You fetch data from an API during the build, render it into pages, and deploy the output to a CDN. Fast, cheap, works great — until the API you're calling rate-limits you, goes down mid-build, or returns slow responses that make your build take eight minutes instead of thirty seconds.
The fix: cache the API responses in Redis. On the first build, you hit the live API and store the result in the KV store. On subsequent builds, you read from the cache. The API sees one request instead of a hundred, your build finishes in seconds, and you control cache invalidation instead of hoping the API doesn't change its rate limits.
What the KV store is
A managed Redis instance, separate from the SQL databases (PostgreSQL and MySQL). You provision it once, link it to a project, and get three environment variables injected automatically:
KV_URL— aredis://connection string for native Redis clients (ioredis, redis-py, go-redis)KV_REST_API_URL— an HTTP endpoint for REST-based access (Upstash-style)KV_REST_API_TOKEN— a bearer token for the REST API
The REST API is the right choice for Astro builds. Node.js Redis clients work, but you avoid an extra dependency and the TLS SNI issues that trip up redis-cli (the CLI doesn't send SNI, so it can't connect directly — the REST façade sidesteps this).
Provisioning a KV store
You need a Redis instance before you can link it to the Astro project. The API route is /v1/kv:
curl -X POST https://api.pandastack.io/v1/kv \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "astro-cache"
}'Response: { "success": true, "data": { "kvId": "kv_abc123", "restApiUrl": "https://...", "restApiToken": "..." } }.
Save the kvId. You'll use it to link the KV store to the Astro project.
Linking the KV store to the Astro project
When you deploy the Astro site, pass the kvId in the project creation payload:
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "astro-blog",
"repositoryName": "yourorg/astro-blog",
"branch": "main",
"autoDeploy": true,
"buildCommand": "npm run build",
"outputDir": "dist",
"kvId": "kv_abc123"
}'The build environment now includes KV_REST_API_URL and KV_REST_API_TOKEN. Your Astro build scripts can read them via process.env and talk to Redis over HTTP.
Fetching data with cache fallback
Astro pages can fetch data at build time using top-level await. Here's a pattern that checks the cache first, hits the live API on a miss, and stores the result:
---
const cacheKey = 'github:stars:astro';
const cacheTTL = 3600; // 1 hour in seconds
async function getCachedOrFetch(key, fetchFn, ttl) {
const restUrl = import.meta.env.KV_REST_API_URL;
const restToken = import.meta.env.KV_REST_API_TOKEN;
// Try cache first
const cacheRes = await fetch(`${restUrl}/get/${key}`, {
headers: { Authorization: `Bearer ${restToken}` }
});
if (cacheRes.ok) {
const cached = await cacheRes.json();
if (cached.result) {
console.log(`Cache HIT: ${key}`);
return JSON.parse(cached.result);
}
}
// Cache miss — fetch from live API
console.log(`Cache MISS: ${key}`);
const data = await fetchFn();
// Store in cache with TTL
await fetch(`${restUrl}/setex/${key}/${ttl}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${restToken}`,
'Content-Type': 'text/plain'
},
body: JSON.stringify(data)
});
return data;
}
const stars = await getCachedOrFetch(
cacheKey,
async () => {
const res = await fetch('https://api.github.com/repos/withastro/astro');
const repo = await res.json();
return repo.stargazers_count;
},
cacheTTL
);
---
<h1>Astro has {stars} stars on GitHub</h1>On the first build, the cache is empty. The code hits the GitHub API, gets the star count, stores it in Redis with a one-hour TTL, and renders the page. On the second build (within an hour), the cache returns the stored value, and the GitHub API never sees a request.
How the REST API works
The KV REST API maps Redis commands to HTTP endpoints:
GET /get/{key}→GET key(returns{ "result": "value" })POST /set/{key}→SET key value(send value as request body)POST /setex/{key}/{ttl}→SETEX key ttl value(set with expiration)DELETE /del/{key}→DEL keyGET /exists/{key}→EXISTS key(returns{ "result": 1 }if key exists)
All requests need Authorization: Bearer $KV_REST_API_TOKEN. The token is scoped to the linked KV instance, so you can't access other organizations' data.
For complex operations (sorted sets, hashes, pub/sub), use a native Redis client with KV_URL. The REST API covers the 80% case (GET, SET, DEL, TTLs).
Caching multiple API responses
If you're building a blog that fetches posts from a headless CMS, cache each post individually:
const posts = await Promise.all(
postIds.map(id =>
getCachedOrFetch(
`cms:post:${id}`,
async () => {
const res = await fetch(`https://cms.example.com/posts/${id}`);
return res.json();
},
1800 // 30 minutes
)
)
);On the first build, all posts are fetched and cached. On subsequent builds, only posts that expired (or were never cached) hit the CMS. If you publish a new post, the cache misses for that ID, fetches it, and stores it. Old posts stay cached.
Invalidating the cache manually
If the upstream API changes and you need to force a fresh fetch, delete the cache key:
curl -X DELETE https://<rest-api-url>/del/github:stars:astro \
-H "Authorization: Bearer $KV_REST_API_TOKEN"The next build hits the live API again.
Alternatively, set a short TTL (60 seconds) during development and a long TTL (3600 seconds) in production. The cache refreshes quickly when you're iterating, but stays stable in production.
Using a native Redis client instead
If you prefer ioredis or another Node.js client:
import Redis from 'ioredis';
const redis = new Redis(import.meta.env.KV_URL);
async function getCachedOrFetch(key, fetchFn, ttl) {
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
const data = await fetchFn();
await redis.setex(key, ttl, JSON.stringify(data));
return data;
}The KV_URL includes TLS and credentials, so you don't need to configure SSL manually. The client connects and authenticates automatically.
Important: redis-cli does not work directly because it doesn't send TLS SNI. If you need CLI access, use the REST API with curl or install redis-cli with a TLS SNI patch. Most applications use a client library and don't hit this issue.
Caching build-time data vs runtime data
Astro is a static site generator — the cache is read at build time, not at request time. If you cache API responses, those responses are baked into the static HTML. A user visiting the site sees the cached data from the last build, not live data fetched on every page load.
For live data (e.g. a real-time dashboard), you need a different pattern:
- 1Deploy the Astro site as static HTML with client-side JavaScript.
- 2Deploy a separate API (Node.js, Python, Go) that reads from the KV store at request time.
- 3The Astro page calls the API via
fetch()from the browser.
This is the JAMstack pattern: static HTML + client-side API calls. The KV store sits behind the API, not directly in the Astro build.
When to use the KV store for Astro
Good use cases:
- Caching third-party API responses (CMS, GitHub, analytics) to avoid rate limits
- Storing build-time configuration (feature flags, A/B test variants)
- De-duplicating expensive computations (Markdown processing, image metadata)
Bad use cases:
- Session management (Astro has no runtime server — sessions live in client-side cookies or a separate API)
- Real-time counters (the cache is read at build time, not request time)
- User-submitted data (again, no runtime server — use a separate API)
What you've deployed
An Astro static site that reads from a managed Redis instance at build time, caching API responses to speed up builds and avoid rate limits. The KV store is provisioned independently of the Astro project, linked via kvId, and accessed over HTTP using the REST API or a native Redis client.
The static site is served from a CDN (zero idle cost, instant cold starts). The KV store runs continuously and bills for allocated memory, not query volume. Free-tier apps include edge functions and the KV store with reasonable limits for development and hobby projects.
Redeployments purge the CDN cache automatically, so users never see stale HTML. The Redis cache TTL controls how often the Astro build fetches fresh data from upstream APIs — set it short (60s) for frequently changing data, long (3600s) for stable content.
References
- [Astro data fetching](https://docs.astro.build/en/guides/data-fetching/)
- [Redis commands](https://redis.io/commands/)
- [PandaStack KV store](https://docs.pandastack.io/kv/)
- [Upstash REST API](https://upstash.com/docs/redis/features/restapi)