Subrequest Profiling in Shopify Oxygen: Staying Within Budget

Introduction

One of the most misunderstood limits in Shopify Oxygen is the subrequest budget. Every time your storefront calls an API, fetches content, or resolves a metafield, Oxygen counts it as a subrequest. Exceed 60 per route, and your page may slow down, error out, or fail deployment entirely.

In this post, we’ll explore what subrequests are, why budgets matter, and how to use profiling tools to keep Time to First Byte (TTFB) predictable and under control.

What Counts as a Subrequest?

A subrequest is any secondary operation your route makes while rendering:

  • Storefront API queries (GraphQL/REST)
  • Admin API calls (backend)
  • Content from CMS (Sanity, Contentful, etc.)
  • Authentication checks (Customer Account API)
  • Edge cache misses that hit origin

Even if your page looks fast in dev, Oxygen may be burning through its budget behind the scenes.

Why Shopify Enforces Budgets

  • Performance at the edge: Too many round-trips → slow PoPs.
  • Fair resource allocation: Prevents one storefront hogging capacity.
  • Predictable costs: Keeps Shopify’s infrastructure reliable across thousands of stores.

The guardrail is designed to protect merchants and customers alike — but developers must actively manage it.

Subrequest Profiling in Practice

Step 1: Run the profiler in dev

npm run dev -- --subrequest-profiler

Step 2: Analyze output

/                       8 subrequests ✅

/collections/summer     22 subrequests ✅

/products/sofa          37 subrequests ✅

/checkout               63 subrequests ⚠️ FAIL

 

  • Green (✅): Safe (<40)
  • Yellow (⚠️): Risk zone (40–59)
  • Red (❌): Over budget (≥60)

Step 3: Fix hot routes

  • Merge queries (product + metafields in one GraphQL request).
  • Cache aggressively (edge/stale-while-revalidate).
  • Remove redundant calls (e.g., double-fetching variants).

TTFB and Subrequests

Time to First Byte (TTFB) is directly tied to subrequest count:

  • <20 subrequests → TTFB < 100 ms (edge fast).
  • 20–40 subrequests → TTFB 100–150 ms (safe).
  • 40–60 subrequests → TTFB 150–250 ms (warning zone).
  • >60 subrequests → TTFB > 250 ms (risk of rejection).

⚡ Pro Tip: Prioritize trimming subrequests on checkout and product detail pages — these routes often balloon fastest.

CI/CD Guardrails

Don’t wait for manual checks — bake profiling into CI.

scripts/profile-subrequests.mjs

import fetch from "node-fetch"; const ROUTES = ["/", "/collections/new", "/products/example", "/checkout"]; const LIMIT = 50; for (const r of ROUTES) { const res = await fetch(`http://localhost:3000/__profile?route=${encodeURIComponent(r)}`); const data = await res.json(); if (data.subrequests > LIMIT) { console.error(`❌ ${r}: ${data.subrequests} subrequests (limit ${LIMIT})`); process.exit(1); } else { console.log(`✅ ${r}: ${data.subrequests} subrequests`); } }

Then run in GitHub Actions before deploy:

- run: node scripts/profile-subrequests.mjs

Best Practices for Staying Within Budget

  • ✅ Batch queries → One GraphQL query > many small ones.
  • ✅ Cache smart → Tokenless queries + stale-while-revalidate.
  • ✅ Lazy load → Only fetch what’s needed above the fold.
  • ✅ Audit checkout → Merge shipping, tax, and line-item calls.

Conclusion

Subrequest budgets aren’t arbitrary — they’re Shopify’s way of ensuring everyone benefits from fast, reliable Oxygen infrastructure. By profiling routes regularly and enforcing CI/CD checks, you can keep TTFB tight, avoid deployment surprises, and build storefronts that scale cleanly.

In modern Shopify, performance is contractual: stay under budget, or pay the price in latency and reliability.