[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F7WSQ95XRBAR7P3XKXM9VJ":3,"skill-same:01M6F7WSQ95XRBAR7P3XKXM9VJ":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":61,"ownerHtmlUrl":62,"ownerBlogUrl":6,"ownerDescription":63,"updatedAt":64,"install":65,"resources":78},"01M6F7WSQ95XRBAR7P3XKXM9VJ","canva-architecture-variants",null,"Choose and implement Canva Connect API architecture blueprints for different scales.\nUse when designing new Canva integrations, choosing between monolith\u002Fservice\u002Fmicroservice\narchitectures, or planning migration paths.\nTrigger with phrases like \"canva architecture\", \"canva blueprint\",\n\"how to structure canva\", \"canva project layout\", \"canva microservice\".","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcanva-architecture-variants\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"architecture","canva architecture variants","canva","connect","design","architectures","paths","how to","io","layout","project","structure","code","Blueprint","read","choose","integrations","how","api","microservice","2026-11-10 10:12:30",false,"---\nname: canva-architecture-variants\ndescription: |\n  Choose and implement Canva Connect API architecture blueprints for different scales.\n  Use when designing new Canva integrations, choosing between monolith\u002Fservice\u002Fmicroservice\n  architectures, or planning migration paths.\n  Trigger with phrases like \"canva architecture\", \"canva blueprint\",\n  \"how to structure canva\", \"canva project layout\", \"canva microservice\".\nallowed-tools: Read, Grep\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\ntags: [saas, design, canva]\ncompatible-with: claude-code\n---\n\n# Canva Architecture Variants\n\n## Overview\n\nThree validated architecture patterns for Canva Connect API integrations. All use the REST API at `api.canva.com\u002Frest\u002Fv1\u002F*` with OAuth 2.0 PKCE tokens. The key architectural decision is how to handle token storage, async operations (exports, autofills), and rate limit management.\n\n## Variant A: Monolith (Simple)\n\n**Best for:** MVPs, small teams, \u003C 100 Canva users\n\n```\nmy-app\u002F\n├── src\u002F\n│   ├── canva\u002F\n│   │   ├── client.ts          # REST client with auto-refresh\n│   │   ├── auth.ts            # OAuth PKCE flow\n│   │   └── types.ts\n│   ├── routes\u002F\n│   │   ├── auth.ts            # OAuth callback\n│   │   └── designs.ts         # Design CRUD\n│   ├── store\u002F\n│   │   └── tokens.ts          # SQLite\u002Ffile token store\n│   └── index.ts\n```\n\n```typescript\n\u002F\u002F Direct API calls in route handlers\napp.post('\u002Fapi\u002Fdesigns', async (req, res) => {\n  const canva = getClientForUser(req.user.id);\n\n  const { design } = await canva.request('\u002Fdesigns', {\n    method: 'POST',\n    body: JSON.stringify({\n      design_type: { type: 'custom', width: 1080, height: 1080 },\n      title: req.body.title,\n    }),\n  });\n\n  res.json({ designId: design.id, editUrl: design.urls.edit_url });\n});\n```\n\n**Pros:** Fast to build, simple token management, easy to debug.\n**Cons:** Synchronous exports block requests, no job queue for autofills.\n\n---\n\n## Variant B: Service Layer (Moderate)\n\n**Best for:** Growing apps, 100-1,000 users, multiple Canva features\n\n```\nmy-app\u002F\n├── src\u002F\n│   ├── canva\u002F\n│   │   ├── client.ts\n│   │   └── auth.ts\n│   ├── services\u002F\n│   │   ├── design.service.ts   # Business logic + caching\n│   │   ├── export.service.ts   # Async export with polling\n│   │   ├── asset.service.ts    # Upload management\n│   │   └── template.service.ts # Autofill orchestration\n│   ├── queue\u002F\n│   │   └── export-worker.ts    # Background export processing\n│   ├── routes\u002F\n│   └── store\u002F\n│       └── tokens.ts           # PostgreSQL encrypted tokens\n```\n\n```typescript\n\u002F\u002F Service layer handles caching, retry, and async operations\nclass ExportService {\n  constructor(\n    private canva: CanvaClient,\n    private cache: Redis,\n    private queue: Bull.Queue\n  ) {}\n\n  async exportDesign(designId: string, format: object): Promise\u003Cstring> {\n    \u002F\u002F Check cache for recent export\n    const cached = await this.cache.get(`export:${designId}:${JSON.stringify(format)}`);\n    if (cached) return cached;\n\n    \u002F\u002F Queue export job — don't block the request\n    const job = await this.queue.add('canva-export', { designId, format });\n    return job.id;\n  }\n}\n\n\u002F\u002F Background worker polls Canva export API\nexportQueue.process('canva-export', async (job) => {\n  const { designId, format } = job.data;\n  const canva = await getServiceClient();\n\n  const { job: exportJob } = await canva.request('\u002Fexports', {\n    method: 'POST',\n    body: JSON.stringify({ design_id: designId, format }),\n  });\n\n  \u002F\u002F Poll for completion\n  let result = exportJob;\n  while (result.status === 'in_progress') {\n    await new Promise(r => setTimeout(r, 2000));\n    const poll = await canva.request(`\u002Fexports\u002F${result.id}`);\n    result = poll.job;\n  }\n\n  return result.status === 'success' ? result.urls : null;\n});\n```\n\n**Pros:** Non-blocking exports, caching, separation of concerns.\n**Cons:** More infrastructure (Redis, job queue), more complex deployment.\n\n---\n\n## Variant C: Microservice (Enterprise)\n\n**Best for:** 1,000+ users, multi-team, strict SLAs, Canva Enterprise with autofill\n\n```\ncanva-service\u002F                # Dedicated microservice\n├── src\u002F\n│   ├── api\u002F\n│   │   └── grpc\u002F             # Internal gRPC API\n│   ├── canva\u002F\n│   │   ├── client.ts\n│   │   └── auth.ts\n│   ├── services\u002F\n│   ├── workers\u002F\n│   │   ├── export.worker.ts\n│   │   ├── autofill.worker.ts\n│   │   └── webhook.worker.ts\n│   └── store\u002F\n│       └── tokens.ts         # Vault-backed token storage\n├── k8s\u002F\n│   ├── deployment.yaml\n│   ├── service.yaml\n│   └── hpa.yaml              # Scale based on queue depth\n```\n\n**Key differences:**\n- Dedicated service owns all Canva API interaction\n- gRPC for internal services, REST for external\n- Separate workers for exports, autofills, webhooks\n- Circuit breaker per operation type\n- Token storage in HashiCorp Vault or KMS\n- HPA scales based on export queue depth\n\n---\n\n## Decision Matrix\n\n| Factor | Monolith | Service Layer | Microservice |\n|--------|----------|---------------|--------------|\n| Users | \u003C 100 | 100-1,000 | 1,000+ |\n| Team Size | 1-3 | 3-10 | 10+ |\n| Export Volume | \u003C 100\u002Fday | 100-2,000\u002Fday | 2,000-5,000\u002Fday |\n| Canva Tier | Free\u002FPro | Pro\u002FTeams | Enterprise |\n| Infrastructure | Single server | App + Redis + queue | Kubernetes |\n| Time to Build | 1-2 days | 1-2 weeks | 2-4 weeks |\n\n## Migration Path\n\n```\nMonolith → Service Layer:\n1. Extract canva\u002F to services\u002F\n2. Add Redis for caching\n3. Add BullMQ for async exports\n4. Move token store to PostgreSQL\n\nService Layer → Microservice:\n1. Create canva-service repository\n2. Define gRPC contract\n3. Add per-operation workers\n4. Deploy to Kubernetes\n5. Migrate token store to Vault\n```\n\n## Error Handling\n\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| Over-engineering | Wrong variant | Start simpler, migrate when needed |\n| Export blocking requests | No job queue (Variant A) | Queue with BullMQ |\n| Token management complex | Multi-user | Use factory pattern per user |\n| Integration export quota | > 5,000\u002Fday | Contact Canva for increase |\n\n## Resources\n\n- [Canva Starter Kit](https:\u002F\u002Fgithub.com\u002Fcanva-sdks\u002Fcanva-connect-api-starter-kit)\n- [Canva API Reference](https:\u002F\u002Fwww.canva.dev\u002Fdocs\u002Fconnect\u002Fapi-reference\u002F)\n- [Monolith First](https:\u002F\u002Fmartinfowler.com\u002Fbliki\u002FMonolithFirst.html)\n\n## Next Steps\n\nFor common anti-patterns, see `canva-known-pitfalls`.\n\n","# Canva Architecture Variants\n\n## Overview\n\nThree validated architecture patterns for Canva Connect API integrations. All use the REST API at `api.canva.com\u002Frest\u002Fv1\u002F*` with OAuth 2.0 PKCE tokens. The key architectural decision is how to handle token storage, async operations (exports, autofills), and rate limit management.\n\n## Variant A: Monolith (Simple)\n\n**Best for:** MVPs, small teams, \u003C 100 Canva users\n\n```\nmy-app\u002F\n├── src\u002F\n│   ├── canva\u002F\n│   │   ├── client.ts          # REST client with auto-refresh\n│   │   ├── auth.ts            # OAuth PKCE flow\n│   │   └── types.ts\n│   ├── routes\u002F\n│   │   ├── auth.ts            # OAuth callback\n│   │   └── designs.ts         # Design CRUD\n│   ├── store\u002F\n│   │   └── tokens.ts          # SQLite\u002Ffile token store\n│   └── index.ts\n```\n\n```typescript\n\u002F\u002F Direct API calls in route handlers\napp.post('\u002Fapi\u002Fdesigns', async (req, res) => {\n  const canva = getClientForUser(req.user.id);\n\n  const { design } = await canva.request('\u002Fdesigns', {\n    method: 'POST',\n    body: JSON.stringify({\n      design_type: { type: 'custom', width: 1080, height: 1080 },\n      title: req.body.title,\n    }),\n  });\n\n  res.json({ designId: design.id, editUrl: design.urls.edit_url });\n});\n```\n\n**Pros:** Fast to build, simple token management, easy to debug.\n**Cons:** Synchronous exports block requests, no job queue for autofills.\n\n---\n\n## Variant B: Service Layer (Moderate)\n\n**Best for:** Growing apps, 100-1,000 users, multiple Canva features\n\n```\nmy-app\u002F\n├── src\u002F\n│   ├── canva\u002F\n│   │   ├── client.ts\n│   │   └── auth.ts\n│   ├── services\u002F\n│   │   ├── design.service.ts   # Business logic + caching\n│   │   ├── export.service.ts   # Async export with polling\n│   │   ├── asset.service.ts    # Upload management\n│   │   └── template.service.ts # Autofill orchestration\n│   ├── queue\u002F\n│   │   └── export-worker.ts    # Background export processing\n│   ├── routes\u002F\n│   └── store\u002F\n│       └── tokens.ts           # PostgreSQL encrypted tokens\n```\n\n```typescript\n\u002F\u002F Service layer handles caching, retry, and async operations\nclass ExportService {\n  constructor(\n    private canva: CanvaClient,\n    private cache: Redis,\n    private queue: Bull.Queue\n  ) {}\n\n  async exportDesign(designId: string, format: object): Promise\u003Cstring> {\n    \u002F\u002F Check cache for recent export\n    const cached = await this.cache.get(`export:${designId}:${JSON.stringify(format)}`);\n    if (cached) return cached;\n\n    \u002F\u002F Queue export job — don't block the request\n    const job = await this.queue.add('canva-export', { designId, format });\n    return job.id;\n  }\n}\n\n\u002F\u002F Background worker polls Canva export API\nexportQueue.process('canva-export', async (job) => {\n  const { designId, format } = job.data;\n  const canva = await getServiceClient();\n\n  const { job: exportJob } = await canva.request('\u002Fexports', {\n    method: 'POST',\n    body: JSON.stringify({ design_id: designId, format }),\n  });\n\n  \u002F\u002F Poll for completion\n  let result = exportJob;\n  while (result.status === 'in_progress') {\n    await new Promise(r => setTimeout(r, 2000));\n    const poll = await canva.request(`\u002Fexports\u002F${result.id}`);\n    result = poll.job;\n  }\n\n  return result.status === 'success' ? result.urls : null;\n});\n```\n\n**Pros:** Non-blocking exports, caching, separation of concerns.\n**Cons:** More infrastructure (Redis, job queue), more complex deployment.\n\n---\n\n## Variant C: Microservice (Enterprise)\n\n**Best for:** 1,000+ users, multi-team, strict SLAs, Canva Enterprise with autofill\n\n```\ncanva-service\u002F                # Dedicated microservice\n├── src\u002F\n│   ├── api\u002F\n│   │   └── grpc\u002F             # Internal gRPC API\n│   ├── canva\u002F\n│   │   ├── client.ts\n│   │   └── auth.ts\n│   ├── services\u002F\n│   ├── workers\u002F\n│   │   ├── export.worker.ts\n│   │   ├── autofill.worker.ts\n│   │   └── webhook.worker.ts\n│   └── store\u002F\n│       └── tokens.ts         # Vault-backed token storage\n├── k8s\u002F\n│   ├── deployment.yaml\n│   ├── service.yaml\n│   └── hpa.yaml              # Scale based on queue depth\n```\n\n**Key differences:**\n- Dedicated service owns all Canva API interaction\n- gRPC for internal services, REST for external\n- Separate workers for exports, autofills, webhooks\n- Circuit breaker per operation type\n- Token storage in HashiCorp Vault or KMS\n- HPA scales based on export queue depth\n\n---\n\n## Decision Matrix\n\n| Factor | Monolith | Service Layer | Microservice |\n|--------|----------|---------------|--------------|\n| Users | \u003C 100 | 100-1,000 | 1,000+ |\n| Team Size | 1-3 | 3-10 | 10+ |\n| Export Volume | \u003C 100\u002Fday | 100-2,000\u002Fday | 2,000-5,000\u002Fday |\n| Canva Tier | Free\u002FPro | Pro\u002FTeams | Enterprise |\n| Infrastructure | Single server | App + Redis + queue | Kubernetes |\n| Time to Build | 1-2 days | 1-2 weeks | 2-4 weeks |\n\n## Migration Path\n\n```\nMonolith → Service Layer:\n1. Extract canva\u002F to services\u002F\n2. Add Redis for caching\n3. Add BullMQ for async exports\n4. Move token store to PostgreSQL\n\nService Layer → Microservice:\n1. Create canva-service repository\n2. Define gRPC contract\n3. Add per-operation workers\n4. Deploy to Kubernetes\n5. Migrate token store to Vault\n```\n\n## Error Handling\n\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| Over-engineering | Wrong variant | Start simpler, migrate when needed |\n| Export blocking requests | No job queue (Variant A) | Queue with BullMQ |\n| Token management complex | Multi-user | Use factory pattern per user |\n| Integration export quota | > 5,000\u002Fday | Contact Canva for increase |\n\n## Resources\n\n- [Canva Starter Kit](https:\u002F\u002Fgithub.com\u002Fcanva-sdks\u002Fcanva-connect-api-starter-kit)\n- [Canva API Reference](https:\u002F\u002Fwww.canva.dev\u002Fdocs\u002Fconnect\u002Fapi-reference\u002F)\n- [Monolith First](https:\u002F\u002Fmartinfowler.com\u002Fbliki\u002FMonolithFirst.html)\n\n## Next Steps\n\nFor common anti-patterns, see `canva-known-pitfalls`.\n\n",{"name":5,"tags":49,"author":51,"license":15,"version":52,"description":53,"allowed-tools":54,"compatible-with":55},[50,28,26],"saas","Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Choose and implement Canva Connect API architecture blueprints for different scales.\nUse when designing new Canva integrations, choosing between monolith\u002Fservice\u002Fmicroservice\narchitectures, or planning migration paths.\nTrigger with phrases like \"canva architecture\", \"canva blueprint\",\n\"how to structure canva\", \"canva project layout\", \"canva microservice\".\n","Read, Grep","claude-code","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcanva-architecture-variants",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":66,"prompt":76,"zipAvailable":77},[67,70,73],{"tool":68,"command":69},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill canva-architecture-variants",{"tool":71,"command":72},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill canva-architecture-variants",{"tool":74,"command":75},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill canva-architecture-variants","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcanva-architecture-variants\u002FSKILL.md",true,[],[80,94,104,113,119,126],{"skillNo":81,"name":82,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":90,"publishedAt":44},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[85],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[91,92,93],"model","model hierarchy","model hierarchy skill",{"skillNo":95,"name":96,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":100,"publishedAt":44},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[99],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},[101,102,103],"MCP","zoom","zoom mcp",{"skillNo":105,"name":106,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":109,"publishedAt":44},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[110,111,112,102],"sdk","zoom apps sdk","apps",{"skillNo":114,"name":115,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":118,"publishedAt":44},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[115],{"skillNo":120,"name":121,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":125,"publishedAt":44},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[124],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},[121],{"skillNo":127,"name":128,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":44},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[128]]