Webflow Performance Tuning
Overview
Optimize Webflow Data API v2 performance. Key insight: CDN-cached requests
to live items have no rate limits — use the Content Delivery API for read-heavy
workloads and reserve write API calls for mutations.
Prerequisites
webflow-api SDK installed
- Understanding of your read/write ratio
- Redis or in-memory cache (optional)
Webflow Performance Characteristics
| Operation | Typical Latency | Rate Limited | Cacheable |
|---|
| Live items (CDN) | 5-50ms | No | Yes (CDN) |
| Staged items | 50-200ms | Yes | Application cache |
| Create/update item | 100-300ms | Yes | No |
| Bulk create (100) | 200-500ms | Yes (1 count) | No |
| Site publish | 500-2000ms | 1/min | No |
| List collections | 50-150ms | Yes | Application cache |
Key optimization: CDN-cached live item reads do not count against rate limits.
Instructions
Strategy 1: Use Content Delivery API for Reads
async function getPublishedContent(collectionId: string) {
const { items } = await webflow.collections.items.listItemsLive(collectionId, {
limit: 100,
});
return items;
}
async function getPublishedItem(collectionId: string, itemId: string) {
return webflow.collections.items.getItemLive(collectionId, itemId);
}
Strategy 2: Application-Level Response Caching
import { LRUCache } from "lru-cache";
const cache = new LRUCache<string, any>({
max: 500,
ttl: 5 * 60 * 1000,
updateAgeOnGet: true,
});
async function cachedFetch<T>(
key: string,
fetcher: () => Promise<T>,
ttlMs?: number
): Promise<T> {
const cached = cache.get(key);
if (cached !== undefined) return cached as T;
const result = await fetcher();
cache.set(key, result, { ttl: ttlMs });
return result;
}
const collections = await cachedFetch(
`collections:${siteId}`,
() => webflow.collections.list(siteId).( r.),
* *
);
items = (
,
webflow...(collectionId).( r.),
*
);
Strategy 3: Redis Distributed Cache
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);
async function cachedWithRedis<T>(
key: string,
fetcher: () => Promise<T>,
ttlSeconds = 300
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached) as T;
const result = await fetcher();
await redis.setex(key, ttlSeconds, JSON.stringify(result));
return result;
}
async function invalidateOnWebhook(triggerType: string, payload: any) {
if (triggerType === "collection_item_changed" || triggerType === "collection_item_created") {
const collectionId = payload.collectionId;
await redis.del(`items:live:`);
redis.();
.();
}
(triggerType === ) {
keys = redis.();
(keys. > ) redis.(...keys);
.();
}
}
Strategy 4: Bulk Endpoints for Writes
One bulk request = 1 rate limit count for up to 100 items:
for (const item of items) {
await webflow.collections.items.createItem(collectionId, {
fieldData: item,
});
}
await webflow.collections.items.createItemsBulk(collectionId, {
items: items.slice(0, 100).map(item => ({ fieldData: item })),
});
async function batchCreate(
collectionId: string,
allItems: Array<Record<string, any>>
) {
for (let i = 0; i < allItems.length; i += 100) {
const batch = allItems.slice(i, i + 100);
await webflow.collections.items.createItemsBulk(collectionId, {
items: batch.map( => ({ : item, : })),
});
(i + < allItems.) {
( (r, ));
}
}
}
Strategy 5: Parallel Requests with Concurrency Control
import PQueue from "p-queue";
const queue = new PQueue({
concurrency: 5,
interval: 1000,
intervalCap: 10,
});
async function fetchFromMultipleCollections(collectionIds: string[]) {
const results = await Promise.all(
collectionIds.map(id =>
queue.add(() =>
webflow.collections.items.listItemsLive(id, { limit: 100 })
)
)
);
return results;
}
Strategy 6: Efficient Pagination
async function fetchAll(collectionId: string) {
const allItems = [];
let offset = 0;
const limit = 100;
while (true) {
const { items, pagination } = await webflow.collections.items.listItems(
collectionId,
{ offset, limit }
);
allItems.push(...(items || []));
if (allItems.length >= (pagination?.total || 0)) break;
offset += limit;
}
return allItems;
}
Strategy 7: Performance Monitoring
async function timedCall<T>(label: string, fn: () => Promise<T>): Promise<T> {
const start = performance.now();
try {
const result = await fn();
const ms = (performance.now() - start).toFixed(1);
console.log(`[perf] ${label}: ${ms}ms`);
return result;
} catch (error) {
const ms = (performance.now() - start).toFixed(1);
console.error(`[perf] ${label}: FAILED after ${ms}ms`);
throw error;
}
}
const items = await timedCall("listItemsLive", () =>
webflow.collections.items.listItemsLive(collectionId)
);
Performance Optimization Summary
| Strategy | Impact | Effort |
|---|
| Live item API (CDN) | 10x faster reads, no rate limits | Low |
| Bulk endpoints | 100x fewer API calls | Low |
| LRU cache | Eliminates repeat reads | Medium |
| Redis distributed cache | Multi-instance caching | Medium |
| Webhook cache invalidation | Fresh data without polling | Medium |
| Concurrency control | Max throughput without 429s | Low |
Output
- CDN-cached reads for published content
- Application-level caching with TTL
- Bulk writes reducing API call count 100x
- Webhook-triggered cache invalidation
- Performance monitoring for all API calls
Error Handling
| Issue | Cause | Solution |
|---|
| Stale cache | TTL too long | Reduce TTL or use webhook invalidation |
| Cache miss storm | All entries expire simultaneously | Add jitter to TTL |
| Bulk request 400 | >100 items | Cap batches at 100 |
| Memory pressure | LRU cache too large | Set max limit on cache |
Resources
Next Steps
For cost optimization, see webflow-cost-tuning.