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.