DEV Community

Magevanta
Magevanta

Posted on • Originally published at magevanta.com

Magento 2 Headless Architecture: Performance Best Practices

Headless Magento gives you the freedom to build a blazing-fast frontend — but only if your API layer is architected correctly. A poorly configured GraphQL endpoint will make your headless store slower than a traditional Magento theme.

Here's how to get it right.

The headless performance stack

A well-architected headless Magento setup has three layers:

Browser / App
     ↓
CDN (Cloudflare / Fastly)          ← Cache static & public API responses
     ↓
Next.js / Nuxt / Astro frontend    ← SSR + ISR for dynamic content
     ↓
Magento GraphQL API                ← With Redis caching, compression, rate limiting
     ↓
MySQL + Redis                      ← Optimized queries, tag-based cache
Enter fullscreen mode Exit fullscreen mode

Each layer needs to be optimized. Most guides only cover the frontend. Let's talk about the API layer.

GraphQL vs REST for headless

For product catalog, category navigation, and CMS content: use GraphQL. It gives you:

  • Request exactly the fields you need (no over-fetching)
  • Batch multiple queries in one request
  • Strong typing helps frontend developers catch issues early

For cart, checkout, and customer operations: REST is often faster for simple operations (add to cart, apply coupon) because Magento's REST endpoints are more mature and have better caching support.

Server-Side Rendering vs Static Generation

Page type Strategy Cache TTL
Homepage ISR (revalidate every 5 min) CDN: 5 min
Category pages ISR (revalidate every 10 min) CDN: 10 min
Product detail ISR (revalidate on price/stock change) CDN: 1 min
Cart / Checkout SSR (no cache) None
CMS pages Static (rebuild on publish) CDN: 24h

The key insight: most of your traffic hits product and category pages. These can be statically generated or ISR-cached. Only cart and checkout need true SSR.

Caching GraphQL responses at the edge

Public GraphQL queries (product listings, category trees, CMS blocks) can be cached at the CDN edge:

Strategy: Convert POST GraphQL requests to GET using persisted queries, then cache the GET responses at CDN level.

// Next.js example: fetch with cache
const data = await fetch('https://shop.example.com/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: PRODUCTS_QUERY }),
  next: { revalidate: 300 }, // ISR: revalidate every 5 minutes
});
Enter fullscreen mode Exit fullscreen mode

With Cloudflare, you can also cache GraphQL responses at the edge using Cache Rules based on query hash.

Invalidating the CDN on catalog updates

The hardest problem in headless Magento: when a product price changes, you need to invalidate the right CDN pages without a full cache flush.

Tag-based invalidation workflow:

  1. Product is saved in Magento admin
  2. Magento fires catalog_product_save_after event
  3. Observer collects affected URLs (PDP, parent category pages, related widgets)
  4. Calls Cloudflare API to purge those specific URLs
public function execute(\Magento\Framework\Event\Observer $observer): void
{
    $product = $observer->getProduct();
    $urls = $this->urlCollector->getAffectedUrls($product);
    $this->cloudflareClient->purgePaths($urls);
}
Enter fullscreen mode Exit fullscreen mode

This is more complex to set up but essential at scale. Flushing your entire CDN on every product save destroys your cache hit rate.

Handling customer-specific content

Personalized content (prices for logged-in customers, wishlist counts, cart totals) can't be cached. But you don't have to sacrifice performance:

Shell + client-side hydration pattern:

  1. Serve a cached shell (header, navigation, product grid) from CDN
  2. On load, fetch customer-specific data from a dedicated API endpoint
  3. Hydrate the shell with customer data client-side

This gives you CDN-fast page loads with accurate customer data. The perceived performance is excellent because the page structure appears immediately.

API rate limiting and abuse protection

Headless APIs are more exposed than traditional Magento installs. Without rate limiting:

  • Crawlers can saturate your GraphQL endpoint
  • Competitors can scrape your entire catalog
  • Checkout bots can abuse your cart API

Implement tiered rate limiting:

  • Unauthenticated requests: 60/minute per IP
  • Authenticated frontend: 600/minute per session token
  • Server-to-server (your Next.js backend): unlimited with a dedicated API key

Monitoring API performance

Track these metrics per endpoint:

  • P50/P95/P99 response times — P95 and P99 reveal tail latency that real users experience
  • Cache hit rate — aim for >90% on product/category endpoints
  • Error rate — alert at >0.1%
  • Payload size — track average response size, flag regressions

A 200ms P95 response time on your product API translates directly to a 200ms longer page load for 1 in 20 users.

The complete headless optimization checklist

  • [ ] Separate Redis instances for cache, FPC, and sessions
  • [ ] GraphQL response caching in Redis (by query hash + store + currency)
  • [ ] Brotli compression on all API responses
  • [ ] Persisted queries for CDN cacheability
  • [ ] Tag-based CDN invalidation on catalog updates
  • [ ] Rate limiting with tiered API keys
  • [ ] Shell + hydration pattern for customer-specific content
  • [ ] P95 response time monitoring with alerts

The BetterMagento Lightweight API module handles the Redis caching, Brotli compression, persisted queries, and rate limiting out of the box.


Originally published on magevanta.com

Top comments (0)