Agent Skills: Shopify Performance Tuning

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/shopify-performance-tuning

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/shopify-pack/skills/shopify-performance-tuning

Skill Files

Browse the full folder contents for shopify-performance-tuning.

Download Skill

Loading file tree…

plugins/saas-packs/shopify-pack/skills/shopify-performance-tuning/SKILL.md

Skill Metadata

Name
shopify-performance-tuning
Description
|

Shopify Performance Tuning

Overview

Optimize Shopify API performance through GraphQL query cost reduction, bulk operations for large data exports, response caching, and Storefront API for high-traffic public-facing queries.

Prerequisites

  • Understanding of Shopify's calculated query cost system
  • Access to the Shopify-GraphQL-Cost-Debug: 1 header for cost analysis
  • Redis or in-memory cache available (optional)

Instructions

Step 1: Analyze Query Cost

# Debug query cost with special header
curl -X POST "https://$STORE/admin/api/2024-10/graphql.json" \
  -H "X-Shopify-Access-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Shopify-GraphQL-Cost-Debug: 1" \
  -d '{"query": "{ products(first: 50) { edges { node { id title variants(first: 20) { edges { node { id price } } } } } } }"}' \
  | jq '.extensions.cost'

Response shows cost breakdown:

{
  "requestedQueryCost": 152,
  "actualQueryCost": 42,
  "throttleStatus": {
    "maximumAvailable": 1000.0,
    "currentlyAvailable": 958.0,
    "restoreRate": 50.0
  }
}

Key rule: requestedQueryCost is calculated as first * nested_fields. Reducing first: from 250 to 50 can cut cost by 5x.

Step 2: Reduce Query Cost

// BEFORE: High cost — requests too many fields and items
// requestedQueryCost: ~5,502
const EXPENSIVE_QUERY = `{
  products(first: 250) {
    edges {
      node {
        id title description descriptionHtml vendor productType tags
        variants(first: 100) {
          edges {
            node {
              id title price compareAtPrice sku barcode
              inventoryQuantity weight weightUnit
              selectedOptions { name value }
              metafields(first: 10) {
                edges { node { namespace key value type } }
              }
            }
          }
        }
        images(first: 20) {
          edges { node { url altText width height } }
        }
        metafields(first: 10) {
          edges { node { namespace key value type } }
        }
      }
    }
  }
}`;

// AFTER: Optimized — only needed fields, smaller page sizes
// requestedQueryCost: ~112
const OPTIMIZED_QUERY = `{
  products(first: 50) {
    edges {
      node {
        id
        title
        status
        variants(first: 5) {
          edges {
            node { id price sku inventoryQuantity }
          }
        }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}`;

Step 3: Use Bulk Operations for Large Exports

Bulk operations bypass rate limits and are designed for exporting large datasets:

// Step 1: Start bulk operation
const START_BULK = `
  mutation {
    bulkOperationRunQuery(query: """
      {
        products {
          edges {
            node {
              id
              title
              handle
              variants {
                edges {
                  node {
                    id
                    sku
                    price
                    inventoryQuantity
                  }
                }
              }
            }
          }
        }
      }
    """) {
      bulkOperation {
        id
        status
      }
      userErrors { field message }
    }
  }
`;

// Step 2: Poll for completion
const CHECK_BULK = `{
  currentBulkOperation {
    id
    status       # CREATED, RUNNING, COMPLETED, FAILED
    errorCode
    objectCount
    fileSize
    url          # JSONL download URL when COMPLETED
    createdAt
  }
}`;

// Step 3: Download results (JSONL format — one JSON object per line)
// const response = await fetch(bulkOperation.url);
// Each line: {"id":"gid://shopify/Product/123","title":"Widget",...}

Step 4: Cache Frequently Accessed Data

import { LRUCache } from "lru-cache";

const shopifyCache = new LRUCache<string, any>({
  max: 500,
  ttl: 5 * 60 * 1000, // 5 minutes
  updateAgeOnGet: true,
});

async function cachedQuery<T>(
  cacheKey: string,
  queryFn: () => Promise<T>,
  ttlMs?: number
): Promise<T> {
  const cached = shopifyCache.get(cacheKey);
  if (cached !== undefined) return cached as T;

  const result = await queryFn();
  shopifyCache.set(cacheKey, result, { ttl: ttlMs });
  return result;
}

// Usage — cache product data for 5 minutes
const product = await cachedQuery(
  `product:${productId}`,
  () => shopifyQuery(shop, PRODUCT_QUERY, { id: productId })
);

// Invalidate on webhook
app.post("/webhooks", (req, res) => {
  const topic = req.headers["x-shopify-topic"];
  if (topic === "products/update") {
    const payload = JSON.parse(req.body);
    shopifyCache.delete(`product:gid://shopify/Product/${payload.id}`);
  }
});

Step 5: Use Storefront API for Public Queries

The Storefront API has separate rate limits and is designed for high-traffic public storefronts:

// Storefront API — safe for client-side, higher rate limits
const storefrontClient = new shopify.clients.Storefront({
  session,
  apiVersion: "2024-10",
});

// Storefront API query — no admin credentials exposed
const products = await storefrontClient.request(`{
  products(first: 12, sortKey: BEST_SELLING) {
    edges {
      node {
        id
        title
        handle
        priceRange {
          minVariantPrice { amount currencyCode }
        }
        featuredImage {
          url(transform: { maxWidth: 400 })
          altText
        }
      }
    }
  }
}`);

Output

  • Query costs reduced through field selection and page size optimization
  • Bulk operations configured for large data exports
  • Response caching with webhook-driven invalidation
  • Storefront API used for public-facing high-traffic queries

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | THROTTLED on every query | requestedQueryCost too high | Reduce first: and remove unused fields | | Bulk operation FAILED | Query syntax error | Test query in GraphiQL first | | Stale cache data | Cache not invalidated | Add webhook handlers to clear cache | | Storefront API 403 | Wrong token type | Use Storefront API access token, not Admin |

Examples

Performance Comparison

| Approach | 10K Products Export | Rate Limit Impact | |----------|-------------------|-------------------| | Paginated (first: 250) | 40 queries, ~60s | Uses ~6,000 points | | Paginated (first: 50) | 200 queries, ~300s | Uses ~22,000 points | | Bulk Operation | 1 query + poll, ~30s | Minimal impact |

Resources

Next Steps

For cost optimization, see shopify-cost-tuning.