Timing-Allow-Origin — Why Your RUM Tool Shows Zero Timing Data
Updated April 2026
Reading this? Verify your fix in real-time. Audit your performance headers → EdgeFix
Your RUM (Real User Monitoring) tool shows that your CDN images load in 0ms. That is not correct — it is the browser hiding timing data from your JavaScript because the resources are cross-origin. Timing-Allow-Origin fixes it.
Why this happens
The Resource Timing API (performance.getEntriesByType("resource")) returns timing data for all resources a page loads. But for cross-origin resources, the browser zeroes out most fields to prevent timing side-channel attacks. Your RUM script cannot see how long your CDN took to respond.
// Without Timing-Allow-Origin:
const entries = performance.getEntriesByType("resource");
const cdnEntry = entries.find(e => e.name.includes("cdn.yourapp.com"));
console.log(cdnEntry.responseEnd - cdnEntry.startTime); // → 0 (blocked)
// With Timing-Allow-Origin:
console.log(cdnEntry.responseEnd - cdnEntry.startTime); // → 234.5ms (real data)
Add Timing-Allow-Origin to your CDN or asset server
# Allow your origin to read timing data Timing-Allow-Origin: https://yourapp.com # Allow all origins (safe for public CDN assets) Timing-Allow-Origin: *
Config by location
Nginx — CDN or asset server
location ~* \.(js|css|woff2|png|jpg|webp|svg)$ { add_header Timing-Allow-Origin "*" always; add_header Cache-Control "public, max-age=31536000, immutable" always;
}
Cloudflare (Transform Rules)
Rules → Transform Rules → Modify Response Headers → Add header: Timing-Allow-Origin = * (for public assets). Apply to paths matching your static files.
Vercel (vercel.json)
{ "headers": [ { "source": "/static/(.*)", "headers": [ { "key": "Timing-Allow-Origin", "value": "*" }, { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ]
}
Verify it is working
// Run in browser console on your site:
performance.getEntriesByType("resource") .filter(e => e.name.includes("cdn.yourapp.com")) .map(e => ({ name: e.name.split("/").pop(), duration: Math.round(e.responseEnd - e.startTime), dns: Math.round(e.domainLookupEnd - e.domainLookupStart), connect: Math.round(e.connectEnd - e.connectStart), ttfb: Math.round(e.responseStart - e.requestStart) }));
// Should now show real millisecond values instead of 0
Which timing fields become available
| Field | Without TAO | With TAO |
|---|---|---|
| duration | 0 | Real value |
| startTime | Real value | Real value |
| responseEnd | 0 | Real value |
| domainLookupStart/End | 0 | Real value |
| connectStart/End | 0 | Real value |
| responseStart (TTFB) | 0 | Real value |