Magento 2's GraphQL API is powerful, but it's slow by default. Out of the box, every GraphQL request hits your PHP stack, executes database queries, and returns uncompressed JSON. For a headless storefront, this adds up fast.
Here's how to make it production-fast.
Why Magento GraphQL is slow by default
A few root causes:
- No response caching — each request is processed from scratch
- No compression — JSON responses are sent uncompressed
- Over-fetching — clients receive all fields, even unused ones
- No rate limiting — one misbehaving client can saturate your server
Let's fix each one.
Response Caching
The most impactful change. For public data (product listings, category pages, CMS blocks), GraphQL responses can be cached in Redis and reused across requests.
Cache key strategy matters here: you need to vary the cache by:
- The query hash
- The store view
- The customer group (for pricing)
- Currency
Tag your cache entries by entity type (product_1234, category_56) so you can invalidate precisely when data changes — not flush everything.
# Example: flush cache for a specific product
bin/magento cache:clean --type=full_page --tag=product_1234
Expected improvement: 80–95% of GraphQL requests served from cache, response time drops from 200–500ms to under 10ms.
Brotli Compression
Brotli compresses 15–25% better than gzip. For GraphQL responses (JSON), real-world compression ratios are typically 70–80%.
A 50KB product listing response becomes ~10KB with Brotli. On mobile connections, that's a 200–400ms improvement on its own.
Enable it in your nginx config:
brotli on;
brotli_comp_level 6;
brotli_types application/json text/html text/css application/javascript;
Or handle it in PHP for responses that bypass nginx caching.
Persisted Queries
Instead of sending the full GraphQL query string with every request:
POST /graphql
{ "query": "{ products(filter: { category_id: { eq: \"5\" } }) { items { id name price { regularPrice { amount { value } } } } } }" }
Store the query on the server and send only its hash:
GET /graphql?queryId=a1b2c3d4&variables={"categoryId":"5"}
Benefits:
- Smaller request payloads (especially on mobile)
- GET requests are cacheable by CDN/proxy
- Prevents clients from executing arbitrary queries
Field Pruning
Magento's GraphQL resolvers often fetch more data than the client requested. Field pruning analyzes the incoming query, determines which fields are actually needed, and short-circuits database fetches for unused fields.
For product detail pages, this commonly eliminates 30–50% of database queries.
Rate Limiting
Without rate limiting, a single crawl bot or misbehaving integration can bring down your API. Implement token bucket rate limiting:
- Free tier: 60 requests/minute per IP
- Authenticated tier: 600 requests/minute per API key
- Premium tier: unlimited
Return 429 Too Many Requests with Retry-After header when limits are exceeded.
Query Complexity Analysis
GraphQL allows deeply nested queries that can be extremely expensive:
{
products {
items {
related_products {
related_products {
related_products { ... }
}
}
}
}
}
Calculate a complexity score for each query and reject those above a threshold before they hit your database.
Putting it all together
| Optimization | Implementation effort | Performance gain |
|---|---|---|
| Response caching | Medium | ⭐⭐⭐⭐⭐ |
| Brotli compression | Low | ⭐⭐⭐ |
| Persisted queries | Medium | ⭐⭐⭐ |
| Field pruning | High | ⭐⭐⭐⭐ |
| Rate limiting | Medium | 🛡️ Reliability |
| Complexity analysis | Medium | 🛡️ Reliability |
The BetterMagento Lightweight API module implements all of these in a single Composer package — no custom development required.
Originally published on magevanta.com
Top comments (0)