Claude-code-plugins-plus-skills webflow-performance-tuning

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/webflow-pack/skills/webflow-performance-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-webflow-performance-tuning && rm -rf "$T"
manifest: plugins/saas-packs/webflow-pack/skills/webflow-performance-tuning/SKILL.md
source content

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

OperationTypical LatencyRate LimitedCacheable
Live items (CDN)5-50msNoYes (CDN)
Staged items50-200msYesApplication cache
Create/update item100-300msYesNo
Bulk create (100)200-500msYes (1 count)No
Site publish500-2000ms1/minNo
List collections50-150msYesApplication cache

Key optimization: CDN-cached live item reads do not count against rate limits.

Instructions

Strategy 1: Use Content Delivery API for Reads

// For published content that visitors see, use live item endpoints.
// These are served by Webflow's CDN and have no rate limits.

async function getPublishedContent(collectionId: string) {
  // CDN-cached — fast, no rate limit
  const { items } = await webflow.collections.items.listItemsLive(collectionId, {
    limit: 100,
  });
  return items;
}

// Single live item — also CDN-cached
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,              // Max entries
  ttl: 5 * 60 * 1000,   // 5-minute TTL
  updateAgeOnGet: true,  // Reset TTL on access
});

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;
}

// Usage — cache collection schema (changes rarely)
const collections = await cachedFetch(
  `collections:${siteId}`,
  () => webflow.collections.list(siteId).then(r => r.collections),
  30 * 60 * 1000 // 30-minute cache for schemas
);

// Cache live items (shorter TTL for dynamic content)
const items = await cachedFetch(
  `items:live:${collectionId}`,
  () => webflow.collections.items.listItemsLive(collectionId).then(r => r.items),
  60 * 1000 // 1-minute cache
);

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;
}

// Invalidate cache on webhook events
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:${collectionId}`);
    await redis.del(`items:staged:${collectionId}`);
    console.log(`Cache invalidated for collection ${collectionId}`);
  }

  if (triggerType === "site_publish") {
    // Flush all item caches on publish
    const keys = await redis.keys("items:*");
    if (keys.length > 0) await redis.del(...keys);
    console.log(`Flushed ${keys.length} cache entries on site publish`);
  }
}

Strategy 4: Bulk Endpoints for Writes

One bulk request = 1 rate limit count for up to 100 items:

// BAD: 100 API calls for 100 items
for (const item of items) {
  await webflow.collections.items.createItem(collectionId, {
    fieldData: item,
  });
}
// Rate limit cost: 100

// GOOD: 1 API call for 100 items
await webflow.collections.items.createItemsBulk(collectionId, {
  items: items.slice(0, 100).map(item => ({ fieldData: item })),
});
// Rate limit cost: 1

// For >100 items, batch with delay:
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 => ({ fieldData: item, isDraft: false })),
    });
    if (i + 100 < allItems.length) {
      await new Promise(r => setTimeout(r, 500)); // Breathing room
    }
  }
}

Strategy 5: Parallel Requests with Concurrency Control

import PQueue from "p-queue";

const queue = new PQueue({
  concurrency: 5,
  interval: 1000,
  intervalCap: 10,
});

// Fetch items from multiple collections in parallel
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

// Fetch all items with optimal page size
async function fetchAll(collectionId: string) {
  const allItems = [];
  let offset = 0;
  const limit = 100; // Maximum allowed

  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;
  }
}

// Usage
const items = await timedCall("listItemsLive", () =>
  webflow.collections.items.listItemsLive(collectionId)
);

Performance Optimization Summary

StrategyImpactEffort
Live item API (CDN)10x faster reads, no rate limitsLow
Bulk endpoints100x fewer API callsLow
LRU cacheEliminates repeat readsMedium
Redis distributed cacheMulti-instance cachingMedium
Webhook cache invalidationFresh data without pollingMedium
Concurrency controlMax throughput without 429sLow

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

IssueCauseSolution
Stale cacheTTL too longReduce TTL or use webhook invalidation
Cache miss stormAll entries expire simultaneouslyAdd jitter to TTL
Bulk request 400>100 itemsCap batches at 100
Memory pressureLRU cache too largeSet
max
limit on cache

Resources

Next Steps

For cost optimization, see

webflow-cost-tuning
.