Skip to content
Back to skills

Miro Performance Tuning

ASecurity

'Optimize Miro REST API v2 performance with caching, cursor pagination, request batching, and connection pooling for high-throughput integrations. Trigger with phrases like \"miro performance\", \"optimize miro\", \"miro latency\", \"miro caching\", \"miro slow\", \"miro batch\". '

  • 3 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added September 12, 2026
ai-agentstypescriptgonodegitapiperformance

Works with

  • cursor
  • api

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add thedixitjain/the-mega-skill-library --skill miro-performance-tuning --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Miro Performance Tuning?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Miro Performance Tuning
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/thedixitjain-miro-performance-tuning/badge)](https://www.skillsdirectory.com/skills/thedixitjain-miro-performance-tuning)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: miro-performance-tuning
description: "'Optimize Miro REST API v2 performance with caching, cursor pagination, request batching, and connection pooling for high-throughput integrations. Trigger with phrases like \"miro performance\", \"optimize miro\", \"miro latency\", \"miro caching\", \"miro slow\", \"miro batch\". '"
allowed-tools: "Read, Write, Edit"
category: engineering-core
source_repo: jeremylongshore/claude-code-plugins-plus-skills
source_path: "plugins/saas-packs/miro-pack/skills/miro-performance-tuning/SKILL.md"
source_url: https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/saas-packs/miro-pack/skills/miro-performance-tuning/SKILL.md
---

# Miro Performance Tuning

## Overview

Optimize Miro REST API v2 throughput and latency. Key levers: minimize API calls with cursor pagination, cache board/item data, batch writes with controlled concurrency, and use connection pooling.

## Latency Benchmarks

Typical latencies for `api.miro.com` (US region):

| Operation | Endpoint | P50 | P95 | Credits |
|-----------|----------|-----|-----|---------|
| Get board | `GET /v2/boards/{id}` | 80ms | 200ms | Level 1 |
| List items (50) | `GET /v2/boards/{id}/items?limit=50` | 120ms | 350ms | Level 1 |
| Create sticky note | `POST /v2/boards/{id}/sticky_notes` | 150ms | 400ms | Level 2 |
| Create connector | `POST /v2/boards/{id}/connectors` | 160ms | 420ms | Level 2 |
| Update item | `PATCH /v2/boards/{id}/items/{id}` | 130ms | 350ms | Level 2 |
| Delete item | `DELETE /v2/boards/{id}/items/{id}` | 100ms | 280ms | Level 2 |

## Cursor Pagination (Eliminate Over-Fetching)

Miro v2 uses cursor-based pagination. Fetch only what you need.

```typescript
// Efficient paginated iterator
async function* paginateItems(
  boardId: string,
  options: { type?: string; limit?: number } = {}
): AsyncGenerator<MiroBoardItem> {
  const limit = options.limit ?? 50;  // Max 50 per page
  let cursor: string | undefined;

  do {
    const params = new URLSearchParams({ limit: String(limit) });
    if (options.type) params.set('type', options.type);
    if (cursor) params.set('cursor', cursor);

    const response = await fetch(
      `https://api.miro.com/v2/boards/${boardId}/items?${params}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    );

    const page = await response.json();
    for (const item of page.data) {
      yield item;
    }

    cursor = page.cursor;  // undefined when no more pages
  } while (cursor);
}

// Usage: process items without loading entire board into memory
for await (const item of paginateItems(boardId, { type: 'sticky_note' })) {
  await processItem(item);
}
```

## Caching Strategy

### In-Memory Cache (Single Instance)

```typescript
import { LRUCache } from 'lru-cache';

const boardCache = new LRUCache<string, unknown>({
  max: 500,                    // Max 500 cached entries
  ttl: 60_000,                 // 1 minute TTL
  updateAgeOnGet: true,        // Extend TTL on access
  updateAgeOnHas: false,
});

async function getCachedBoard(boardId: string): Promise<MiroBoard> {
  const cacheKey = `board:${boardId}`;
  const cached = boardCache.get(cacheKey);
  if (cached) return cached as MiroBoard;

  const board = await miroFetch(`/v2/boards/${boardId}`);
  boardCache.set(cacheKey, board);
  return board;
}

// Invalidate on webhook events
function onBoardEvent(event: MiroBoardEvent) {
  boardCache.delete(`board:${event.boardId}`);
  boardCache.delete(`items:${event.boardId}`);
}
```

### Redis Cache (Distributed)

```typescript
import Redis from 'ioredis';

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

async function getCachedItems(boardId: string): Promise<MiroBoardItem[]> {
  const key = `miro:items:${boardId}`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  // Fetch all items
  const items: MiroBoardItem[] = [];
  for await (const item of paginateItems(boardId)) {
    items.push(item);
  }

  // Cache for 2 minutes (boards with active editing should use shorter TTL)
  await redis.setex(key, 120, JSON.stringify(items));
  return items;
}

// Webhook-driven cache invalidation
async function invalidateOnEvent(event: MiroBoardEvent) {
  const keys = [
    `miro:items:${event.boardId}`,
    `miro:board:${event.boardId}`,
  ];
  await redis.del(...keys);
}
```

## Controlled Concurrency for Bulk Operations

```typescript
import PQueue from 'p-queue';

// Create items in parallel with controlled concurrency
async function bulkCreateStickyNotes(
  boardId: string,
  notes: Array<{ content: string; color: string; x: number; y: number }>
): Promise<string[]> {
  const queue = new PQueue({
    concurrency: 5,       // Max 5 parallel requests
    interval: 1000,       // Per-second window
    intervalCap: 10,      // Max 10 requests per second
  });

  const ids: string[] = [];

  for (const note of notes) {
    queue.add(async () => {
      const result = await miroFetch(`/v2/boards/${boardId}/sticky_notes`, 'POST', {
        data: { content: note.content, shape: 'square' },
        style: { fillColor: note.color },
        position: { x: note.x, y: note.y },
      });
      ids.push(result.id);
    });
  }

  await queue.onIdle();  // Wait for all to complete
  return ids;
}

// Example: Create a grid of 50 sticky notes efficiently
const notes = Array.from({ length: 50 }, (_, i) => ({
  content: `Note ${i + 1}`,
  color: 'light_yellow',
  x: (i % 10) * 250,
  y: Math.floor(i / 10) * 250,
}));

const createdIds = await bulkCreateStickyNotes(boardId, notes);
console.log(`Created ${createdIds.length} sticky notes`);
```

## Connection Pooling

```typescript
import { Agent } from 'https';

// Reuse TCP connections across requests
const httpsAgent = new Agent({
  keepAlive: true,
  maxSockets: 10,        // Max 10 concurrent connections to api.miro.com
  maxFreeSockets: 5,     // Keep 5 idle connections in pool
  timeout: 30000,        // Socket timeout
});

async function miroFetchPooled(path: string, options: RequestInit = {}) {
  // Note: Node.js native fetch doesn't support custom agents directly.
  // Use undici or node-fetch for connection pooling.
  const { default: fetch } = await import('node-fetch');

  return fetch(`https://api.miro.com${path}`, {
    ...options,
    agent: httpsAgent,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });
}
```

## Performance Monitoring

```typescript
async function instrumentedMiroFetch<T>(
  operation: string,
  path: string,
  method = 'GET',
  body?: unknown,
): Promise<{ data: T; metrics: RequestMetrics }> {
  const start = performance.now();
  const response = await fetch(`https://api.miro.com${path}`, {
    method,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    ...(body ? { body: JSON.stringify(body) } : {}),
  });

  const duration = performance.now() - start;
  const metrics: RequestMetrics = {
    operation,
    path,
    method,
    status: response.status,
    durationMs: Math.round(duration),
    rateLimitRemaining: parseInt(response.headers.get('X-RateLimit-Remaining') ?? '0', 10),
    rateLimitReset: response.headers.get('X-RateLimit-Reset') ?? '',
  };

  // Log for dashboarding
  console.log('[MIRO_PERF]', JSON.stringify(metrics));

  const data = response.status !== 204 ? await response.json() : null;
  return { data: data as T, metrics };
}
```

## Optimization Checklist

| Technique | Impact | Effort | When to Use |
|-----------|--------|--------|-------------|
| Type-filtered queries | High | Low | Always — `?type=sticky_note` reduces payload |
| LRU cache | High | Low | Read-heavy workloads |
| Redis cache + webhook invalidation | Very High | Medium | Multi-instance deployments |
| Controlled concurrency | High | Low | Bulk create/update operations |
| Connection pooling | Medium | Low | High request volume |
| Cursor pagination | High | Low | Always — avoid loading full board |

## Error Handling

| Issue | Cause | Solution |
|-------|-------|----------|
| Cache stale data | Long TTL + frequent edits | Shorten TTL or use webhook invalidation |
| Concurrency errors | Too many parallel requests | Reduce `concurrency` in PQueue |
| Connection pool exhausted | Max sockets too low | Increase `maxSockets` |
| Pagination cursor expired | Long gap between pages | Re-start pagination from beginning |

## Resources

- [Get Items on Board](https://developers.miro.com/reference/get-items)
- [Rate Limiting](https://developers.miro.com/reference/rate-limiting)
- [LRU Cache](https://github.com/isaacs/node-lru-cache)
- [p-queue](https://github.com/sindresorhus/p-queue)

## Next Steps

For cost optimization, see `miro-cost-tuning`.

---

**Source:** [`jeremylongshore/claude-code-plugins-plus-skills`](https://github.com/jeremylongshore/claude-code-plugins-plus-skills) → `plugins/saas-packs/miro-pack/skills/miro-performance-tuning/SKILL.md`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.