canva-performance-tuning

Optimize Canva Connect API performance with caching, pagination, and connection pooling. Use when experiencing slow API responses, implementing caching strategies, or optimizing request throughput for Canva integrations. Trigger with phrases like "canva performance", "optimize canva", "canva latency", "canva caching", "canva slow", "canva pagination".

Allowed Tools

ReadWriteEdit

Provided by Plugin

canva-pack

Claude Code skill pack for Canva (30 skills)

saas packs v1.5.0
View Plugin

Installation

This skill is included in the canva-pack plugin:

/plugin install canva-pack@claude-code-plugins-plus

Click to copy

Instructions

Canva Performance Tuning

Overview

Optimize Canva Connect API performance. The REST API at api.canva.com/rest/v1/* has per-user rate limits and async operations (exports, uploads, autofills) that require polling.

Prerequisites

  • A protected non-production benchmark workload, current account-limit evidence, and defined latency/freshness/reconciliation budgets.
  • A feature flag and rollback plan for every cache, polling, batch, or concurrency change.

Instructions

  1. Measure a bounded synthetic workload before changing one performance variable.
  2. Minimize requested/persisted fields, encrypt and expire caches, and never cache OAuth values, signed URLs, or unapproved asset data.
  3. Respect provider limits, preserve idempotency/reconciliation state, and roll back on policy, freshness, or completion regression.

Caching Strategy

Design Metadata Cache


import { LRUCache } from 'lru-cache';

// Design metadata changes infrequently — cache aggressively
const designCache = new LRUCache<string, any>({
  max: 500,
  ttl: 5 * 60 * 1000,  // 5 minutes
});

async function getDesignCached(designId: string, token: string) {
  const cached = designCache.get(designId);
  if (cached) return cached;

  const data = await canvaAPI(`/designs/${designId}`, token);
  designCache.set(designId, data);
  return data;
}

// IMPORTANT: Do NOT cache these — they expire quickly:
// - Thumbnail URLs: expire in 15 minutes
// - Edit/view URLs: expire in 30 days
// - Export download URLs: expire in 24 hours

Redis Cache for Distributed Systems


import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

async function cachedCanvaCall<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttlSeconds = 300
): Promise<T> {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const result = await fetcher();
  await redis.setex(key, ttlSeconds, JSON.stringify(result));
  return result;
}

// Cache brand template list — rarely changes
const templates = await cachedCanvaCall(
  'canva:brand-templates:list',
  () => canvaAPI('/brand-templates', token),
  3600 // 1 hour
);

Pagination Optimization


// Canva uses continuation-based pagination
async function* paginateDesigns(
  token: string,
  opts: { ownership?: string; limit?: number } = {}
): AsyncGenerator<any> {
  let continuation: string | undefined;

  do {
    const params = new URLSearchParams({
      limit: String(opts.limit || 100),  // Max 100 per page
      ...(opts.ownership && { ownership: opts.ownership }),
      ...(continuation && { continuation }),
    });

    const data = await canvaAPI(`/designs?${params}`, token);

    for (const design of data.items) {
      yield design;
    }

    continuation = data.continuation; // undefined = last page
  } while (continuation);
}

// Usage — processes designs as they arrive
for await (const design of paginateDesigns(token, { ownership: 'owned' })) {
  console.log(`${design.title} (${design.id})`);
}

Export Polling Optimization


// Smart polling with progressive backoff
async function pollExport(exportId: string, token: string): Promise<string[]> {
  const delays = [500, 1000, 2000, 3000, 5000, 5000, 10000]; // Progressive backoff
  let attempt = 0;

  while (attempt < 20) { // Max ~60s total
    const { job } = await canvaAPI(`/exports/${exportId}`, token);

    if (job.status === 'success') return job.urls;
    if (job.status === 'failed') throw new Error(`Export failed: ${job.error?.message}`);

    const delay = delays[Math.min(attempt, delays.length - 1)];
    await new Promise(r => setTimeout(r, delay));
    attempt++;
  }

  throw new Error('Export polling timeout');
}

// Batch exports with concurrency control
import PQueue from 'p-queue';

const exportQueue = new PQueue({ concurrency: 3 });

async function batchExport(
  designIds: string[],
  format: object,
  token: string
): Promise<Map<string, string[]>> {
  const results = new Map<string, string[]>();

  await Promise.all(
    designIds.map(id =>
      exportQueue.add(async () => {
        const { job } = await canvaAPI('/exports', token, {
          method: 'POST',
          body: JSON.stringify({ design_id: id, format }),
        });
        const urls = await pollExport(job.id, token);
        results.set(id, urls);
      })
    )
  );

  return results;
}

Connection Optimization


import { Agent } from 'https';

// Keep-alive for connection reuse
const agent = new Agent({
  keepAlive: true,
  maxSockets: 10,
  maxFreeSockets: 5,
  timeout: 30000,
});

// Use with Node.js fetch or undici
const res = await fetch('https://api.canva.com/rest/v1/designs', {
  headers: { 'Authorization': `Bearer ${token}` },
  // @ts-expect-error — Node.js specific
  agent,
});

Performance Monitoring


async function measuredCanvaCall<T>(
  operation: string,
  fn: () => Promise<T>
): Promise<T> {
  const start = performance.now();
  try {
    const result = await fn();
    const ms = (performance.now() - start).toFixed(0);
    console.log(`[canva] ${operation}: ${ms}ms OK`);
    return result;
  } catch (error) {
    const ms = (performance.now() - start).toFixed(0);
    console.error(`[canva] ${operation}: ${ms}ms FAIL`, error);
    throw error;
  }
}

Performance Benchmarks

Operation Typical Latency Rate Limit
GET /users/me 50-150ms 10/min
POST /designs 200-500ms 20/min
GET /designs (list) 100-300ms 100/min
POST /exports 100-300ms (job start) 75/5min
Export completion 2-15s (depending on size) N/A
POST /asset-uploads 300-2000ms 30/min
POST /autofills 500-3000ms (job start) 60/min

Output

Performance work produces a baseline, bounded aggregate latency/rate result, cache data classification, rollout decision, and rollback result. It excludes customer content, tokens, asset URLs, and user-identifying values.

Examples

Benchmark metadata-only reads against synthetic designs, enable one TTL-limited encrypted cache behind a feature flag, then compare p95 latency and reconciliation correctness. Disable the change if it causes stale authorization, missed completion, or a rate-limit regression.

Error Handling

Issue Cause Solution
Stale cache Long TTL Reduce TTL or invalidate on write
Export timeout Large/complex design Increase poll timeout
Memory pressure Cache too large Set LRU max entries
Connection refused Pool exhausted Increase maxSockets

Resources

Next Steps

For cost optimization, see canva-cost-tuning.

Ready to use canva-pack?