[{"data":1,"prerenderedAt":1162},["ShallowReactive",2],{"content:\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing":3,"surroundings:\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing":1154},{"id":4,"title":5,"body":6,"description":1132,"extension":1133,"meta":1134,"navigation":1147,"path":1148,"seo":1149,"stem":1152,"__hash__":1153},"content\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing\u002Findex.md","Diagnosing Slow TTFB with Server-Timing",{"type":7,"value":8,"toc":1113},"minimark",[9,14,29,41,151,156,189,193,199,205,211,217,221,226,504,508,519,523,526,530,659,784,788,791,795,801,910,914,921,925,931,934,938,941,945,971,975,981,987,993,999,1003,1015,1024,1036,1045,1054,1063,1072,1076,1098,1103,1106,1109],[10,11,13],"h1",{"id":12},"how-to-diagnose-slow-ttfb-with-server-timing","How to Diagnose Slow TTFB with Server-Timing",[15,16,17,18,23,24,28],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002F","Time to First Byte Optimization",", within ",[19,25,27],{"href":26},"\u002Fnetwork-protocol-optimization\u002F","Network & Server Response Optimization",". When TTFB is slow and Navigation Timing shows that most of it is the time between sending the request and receiving the first byte, the problem is server time. But \"the server is slow\" is not actionable. Is it the database, a downstream API, authentication, template rendering, a cache miss, or a queue in front of the application?",[15,30,31,32,36,37,40],{},"The ",[33,34,35],"code",{},"Server-Timing"," HTTP response header lets the server report named durations for its work, directly to the browser. Chrome DevTools displays them in the Network panel's Timing tab, and the ",[33,38,39],{},"PerformanceServerTiming"," API exposes them to JavaScript, so RUM scripts can collect them from real users. With a few lines of instrumentation, you can see the p75 of each server phase in the field, on the same dashboard as TTFB and LCP.",[15,42,43],{},[44,45,51,52,51,59,51,63,51,66,51,75,51,81,51,88,51,97,51,102,51,106,51,110,51,114,51,118,51,122,51,126,51,130,51,135,51,139,51,143,51,147,51],"svg",{"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"0 0 760 225","100%","img","Bar chart of server-side phases for a product page at the 75th percentile, collected via Server-Timing in RUM.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[53,54],"rect",{"className":55,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],"svg-canvas","0","#ffffff",[60,61,62],"title",{},"Server-Timing breakdown for a product page (p75, field)",[64,65,49],"desc",{},[53,67],{"x":68,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":73,"style":74},"1","758","223","10","none","currentColor","stroke-opacity:0.18",[76,77,62],"text",{"x":78,"y":79,"fill":73,"style":80},"28.0","34.0","font-size:16px;font-weight:700",[76,82,87],{"x":83,"y":84,"fill":73,"style":85,"textAnchor":86},"153.8","70.0","font-size:13px","end","auth\u002Fsession",[53,89],{"x":90,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"165.8","56.0","42.9","19","3","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[76,98,101],{"x":99,"y":84,"fill":73,"style":100},"214.7","font-size:12px;font-weight:600","35ms",[76,103,105],{"x":83,"y":104,"fill":73,"style":85,"textAnchor":86},"101.0","db-product",[53,107],{"x":90,"y":108,"width":109,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"87.0","58.8",[76,111,113],{"x":112,"y":104,"fill":73,"style":100},"230.6","48ms",[76,115,117],{"x":83,"y":116,"fill":73,"style":85,"textAnchor":86},"132.0","api-pricing",[53,119],{"x":90,"y":120,"width":121,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"118.0","88.2",[76,123,125],{"x":124,"y":116,"fill":73,"style":100},"260.0","72ms",[76,127,129],{"x":83,"y":128,"fill":73,"style":85,"textAnchor":86},"163.0","api-recommendations",[53,131],{"x":90,"y":132,"width":133,"height":93,"rx":94,"fill":72,"stroke":73,"style":134},"149.0","502.2","fill-opacity:0.7;stroke-opacity:0.35",[76,136,138],{"x":137,"y":128,"fill":73,"style":100},"674.0","410ms",[76,140,142],{"x":83,"y":141,"fill":73,"style":85,"textAnchor":86},"194.0","render",[53,144],{"x":90,"y":145,"width":146,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"180.0","104.1",[76,148,150],{"x":149,"y":141,"fill":73,"style":100},"275.9","85ms",[152,153,155],"h2",{"id":154},"rapid-diagnosis","Rapid Diagnosis",[157,158,159,171,177,183],"ul",{},[160,161,162,166,167,170],"li",{},[163,164,165],"strong",{},"Check Navigation Timing",": ",[33,168,169],{},"responseStart - requestStart"," large compared with connection time means server time dominates.",[160,172,173,176],{},[163,174,175],{},"Look for an existing Server-Timing header"," in DevTools (Network → select document → Timing).",[160,178,179,182],{},[163,180,181],{},"Check CDN cache status headers"," on the document; a miss means the origin did the work.",[160,184,185,188],{},[163,186,187],{},"Compare TTFB by template"," to find which routes are slow.",[152,190,192],{"id":191},"root-cause-analysis","Root Cause Analysis",[15,194,195,198],{},[163,196,197],{},"1. Slow dependencies."," One downstream API or query dominates.",[15,200,201,204],{},[163,202,203],{},"2. Sequential calls."," Independent work awaited one after another.",[15,206,207,210],{},[163,208,209],{},"3. Cache misses."," Application or CDN caches missing more than expected.",[15,212,213,216],{},[163,214,215],{},"4. Queueing."," Requests waiting for a worker or database connection under load.",[152,218,220],{"id":219},"step-by-step-resolution","Step-by-Step Resolution",[222,223,225],"h3",{"id":224},"_1-instrument-the-server","1. Instrument the server",[227,228,233],"pre",{"className":229,"code":230,"language":231,"meta":232,"style":232},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Generic helper: time async work and collect Server-Timing entries.\nexport function serverTimer() {\n  const entries = [];\n  return {\n    async time(name, fn, desc) {\n      const t0 = performance.now();\n      try { return await fn(); }\n      finally { entries.push(`${name};dur=${(performance.now() - t0).toFixed(1)}${desc ? `;desc=\"${desc}\"` : ''}`); }\n    },\n    header: () => entries.join(', '),\n  };\n}\n\u002F\u002F Route: const t = serverTimer(); const p = await t.time('db-product', () => db.product(id));\n\u002F\u002F res.setHeader('Server-Timing', t.header());\n\u002F\u002F trade-off: headers must be set before the body is sent; with streaming, report\n\u002F\u002F only the phases completed before the first flush, or use trailers where supported.\n","javascript","",[33,234,235,244,262,278,287,316,336,357,436,442,468,474,480,486,492,498],{"__ignoreMap":232},[236,237,240],"span",{"class":238,"line":239},"line",1,[236,241,243],{"class":242},"sjfSM","\u002F\u002F Generic helper: time async work and collect Server-Timing entries.\n",[236,245,247,251,254,258],{"class":238,"line":246},2,[236,248,250],{"class":249},"sPARh","export",[236,252,253],{"class":249}," function",[236,255,257],{"class":256},"smZ65"," serverTimer",[236,259,261],{"class":260},"saISM","() {\n",[236,263,265,268,272,275],{"class":238,"line":264},3,[236,266,267],{"class":249},"  const",[236,269,271],{"class":270},"sPXB4"," entries",[236,273,274],{"class":249}," =",[236,276,277],{"class":260}," [];\n",[236,279,281,284],{"class":238,"line":280},4,[236,282,283],{"class":249},"  return",[236,285,286],{"class":260}," {\n",[236,288,290,293,296,299,303,306,309,311,313],{"class":238,"line":289},5,[236,291,292],{"class":249},"    async",[236,294,295],{"class":256}," time",[236,297,298],{"class":260},"(",[236,300,302],{"class":301},"sQw3B","name",[236,304,305],{"class":260},", ",[236,307,308],{"class":301},"fn",[236,310,305],{"class":260},[236,312,64],{"class":301},[236,314,315],{"class":260},") {\n",[236,317,319,322,325,327,330,333],{"class":238,"line":318},6,[236,320,321],{"class":249},"      const",[236,323,324],{"class":270}," t0",[236,326,274],{"class":249},[236,328,329],{"class":260}," performance.",[236,331,332],{"class":256},"now",[236,334,335],{"class":260},"();\n",[236,337,339,342,345,348,351,354],{"class":238,"line":338},7,[236,340,341],{"class":249},"      try",[236,343,344],{"class":260}," { ",[236,346,347],{"class":249},"return",[236,349,350],{"class":249}," await",[236,352,353],{"class":256}," fn",[236,355,356],{"class":260},"(); }\n",[236,358,360,363,366,369,371,375,377,380,382,385,388,390,393,396,398,401,404,406,408,411,414,416,419,422,424,427,430,433],{"class":238,"line":359},8,[236,361,362],{"class":249},"      finally",[236,364,365],{"class":260}," { entries.",[236,367,368],{"class":256},"push",[236,370,298],{"class":260},[236,372,374],{"class":373},"sZ8jY","`${",[236,376,302],{"class":260},[236,378,379],{"class":373},"};dur=${",[236,381,298],{"class":373},[236,383,384],{"class":260},"performance",[236,386,387],{"class":373},".",[236,389,332],{"class":256},[236,391,392],{"class":373},"() ",[236,394,395],{"class":249},"-",[236,397,324],{"class":260},[236,399,400],{"class":373},").",[236,402,403],{"class":256},"toFixed",[236,405,298],{"class":373},[236,407,68],{"class":270},[236,409,410],{"class":373},")",[236,412,413],{"class":373},"}${",[236,415,64],{"class":260},[236,417,418],{"class":249}," ?",[236,420,421],{"class":373}," `;desc=\"${",[236,423,64],{"class":260},[236,425,426],{"class":373},"}\"`",[236,428,429],{"class":249}," :",[236,431,432],{"class":373}," ''}`",[236,434,435],{"class":260},"); }\n",[236,437,439],{"class":238,"line":438},9,[236,440,441],{"class":260},"    },\n",[236,443,445,448,451,454,457,460,462,465],{"class":238,"line":444},10,[236,446,447],{"class":256},"    header",[236,449,450],{"class":260},": () ",[236,452,453],{"class":249},"=>",[236,455,456],{"class":260}," entries.",[236,458,459],{"class":256},"join",[236,461,298],{"class":260},[236,463,464],{"class":373},"', '",[236,466,467],{"class":260},"),\n",[236,469,471],{"class":238,"line":470},11,[236,472,473],{"class":260},"  };\n",[236,475,477],{"class":238,"line":476},12,[236,478,479],{"class":260},"}\n",[236,481,483],{"class":238,"line":482},13,[236,484,485],{"class":242},"\u002F\u002F Route: const t = serverTimer(); const p = await t.time('db-product', () => db.product(id));\n",[236,487,489],{"class":238,"line":488},14,[236,490,491],{"class":242},"\u002F\u002F res.setHeader('Server-Timing', t.header());\n",[236,493,495],{"class":238,"line":494},15,[236,496,497],{"class":242},"\u002F\u002F trade-off: headers must be set before the body is sent; with streaming, report\n",[236,499,501],{"class":238,"line":500},16,[236,502,503],{"class":242},"\u002F\u002F only the phases completed before the first flush, or use trailers where supported.\n",[222,505,507],{"id":506},"_2-add-cache-status-and-total","2. Add cache status and total",[15,509,510,511,514,515,518],{},"Report cache hits as entries (",[33,512,513],{},"cache;desc=\"hit\"",") and a total (",[33,516,517],{},"total;dur=...","), so you can segment field data by cache outcome.",[222,520,522],{"id":521},"_3-read-it-in-devtools","3. Read it in DevTools",[15,524,525],{},"In the Network panel, select the document request and open the Timing tab: Server-Timing entries appear under \"Server Timing\" with their durations.",[222,527,529],{"id":528},"_4-collect-it-in-rum","4. Collect it in RUM",[227,531,533],{"className":229,"code":532,"language":231,"meta":232,"style":232},"const [nav] = performance.getEntriesByType('navigation');\nconst st = Object.fromEntries((nav.serverTiming || []).map((e) => [e.name, Math.round(e.duration)]));\nnavigator.sendBeacon('\u002Frum', JSON.stringify({ ttfb: Math.round(nav.responseStart), ...st, path: location.pathname }));\n\u002F\u002F trade-off: cross-origin documents only expose Server-Timing with a\n\u002F\u002F Timing-Allow-Origin header; same-origin navigations need nothing extra.\n",[33,534,535,565,612,649,654],{"__ignoreMap":232},[236,536,537,540,543,546,549,552,554,557,559,562],{"class":238,"line":239},[236,538,539],{"class":249},"const",[236,541,542],{"class":260}," [",[236,544,545],{"class":270},"nav",[236,547,548],{"class":260},"] ",[236,550,551],{"class":249},"=",[236,553,329],{"class":260},[236,555,556],{"class":256},"getEntriesByType",[236,558,298],{"class":260},[236,560,561],{"class":373},"'navigation'",[236,563,564],{"class":260},");\n",[236,566,567,569,572,574,577,580,583,586,589,592,595,598,601,603,606,609],{"class":238,"line":246},[236,568,539],{"class":249},[236,570,571],{"class":270}," st",[236,573,274],{"class":249},[236,575,576],{"class":260}," Object.",[236,578,579],{"class":256},"fromEntries",[236,581,582],{"class":260},"((nav.serverTiming ",[236,584,585],{"class":249},"||",[236,587,588],{"class":260}," []).",[236,590,591],{"class":256},"map",[236,593,594],{"class":260},"((",[236,596,597],{"class":301},"e",[236,599,600],{"class":260},") ",[236,602,453],{"class":249},[236,604,605],{"class":260}," [e.name, Math.",[236,607,608],{"class":256},"round",[236,610,611],{"class":260},"(e.duration)]));\n",[236,613,614,617,620,622,625,627,630,632,635,638,640,643,646],{"class":238,"line":264},[236,615,616],{"class":260},"navigator.",[236,618,619],{"class":256},"sendBeacon",[236,621,298],{"class":260},[236,623,624],{"class":373},"'\u002Frum'",[236,626,305],{"class":260},[236,628,629],{"class":270},"JSON",[236,631,387],{"class":260},[236,633,634],{"class":256},"stringify",[236,636,637],{"class":260},"({ ttfb: Math.",[236,639,608],{"class":256},[236,641,642],{"class":260},"(nav.responseStart), ",[236,644,645],{"class":249},"...",[236,647,648],{"class":260},"st, path: location.pathname }));\n",[236,650,651],{"class":238,"line":280},[236,652,653],{"class":242},"\u002F\u002F trade-off: cross-origin documents only expose Server-Timing with a\n",[236,655,656],{"class":238,"line":289},[236,657,658],{"class":242},"\u002F\u002F Timing-Allow-Origin header; same-origin navigations need nothing extra.\n",[15,660,661],{},[44,662,51,665,51,668,51,671,51,673,51,690,51,693,51,695,51,701,51,708,51,711,51,715,51,718,51,722,51,725,51,729,51,737,51,740,51,743,51,746,51,752,51,758,51,762,51,767,51,770,51,775,51,780,51],{"viewBox":663,"width":47,"role":48,"ariaLabel":664,"style":50},"0 0 760 290","Sequence showing the server adding Server-Timing, the browser exposing it, and a RUM script reporting it.",[53,666],{"className":667,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,669,670],{},"Server-Timing from server to dashboard",[64,672,664],{},[674,675,676],"defs",{},[677,678,685],"marker",{"id":679,"viewBox":680,"refX":681,"refY":682,"markerWidth":683,"markerHeight":683,"orient":684},"fa62c36fbf","0 0 10 10","9","5","7","auto-start-reverse",[686,687],"path",{"d":688,"fill":73,"style":689},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[53,691],{"x":68,"y":68,"width":69,"height":692,"rx":71,"fill":72,"stroke":73,"style":74},"288",[76,694,670],{"x":78,"y":79,"fill":73,"style":80},[53,696],{"x":697,"y":91,"width":698,"height":79,"rx":699,"fill":73,"stroke":73,"style":700},"41.0","150.0","6","fill-opacity:0.06;stroke-opacity:0.4",[76,702,707],{"x":703,"y":704,"fill":73,"style":705,"textAnchor":706},"116.0","78.0","font-size:13px;font-weight:700","middle","Server",[53,709],{"x":710,"y":91,"width":698,"height":79,"rx":699,"fill":73,"stroke":73,"style":700},"217.0",[76,712,714],{"x":713,"y":704,"fill":73,"style":705,"textAnchor":706},"292.0","Browser",[53,716],{"x":717,"y":91,"width":698,"height":79,"rx":699,"fill":73,"stroke":73,"style":700},"393.0",[76,719,721],{"x":720,"y":704,"fill":73,"style":705,"textAnchor":706},"468.0","RUM script",[53,723],{"x":724,"y":91,"width":698,"height":79,"rx":699,"fill":73,"stroke":73,"style":700},"569.0",[76,726,728],{"x":727,"y":704,"fill":73,"style":705,"textAnchor":706},"644.0","Dashboard",[238,730],{"x1":703,"y1":731,"x2":703,"y2":732,"stroke":73,"strokeWidth":733,"strokeDashArray":734,"style":736},"90.0","268.0","1.5",[735,735],"4","stroke-opacity:0.35",[238,738],{"x1":713,"y1":731,"x2":713,"y2":732,"stroke":73,"strokeWidth":733,"strokeDashArray":739,"style":736},[735,735],[238,741],{"x1":720,"y1":731,"x2":720,"y2":732,"stroke":73,"strokeWidth":733,"strokeDashArray":742,"style":736},[735,735],[238,744],{"x1":727,"y1":731,"x2":727,"y2":732,"stroke":73,"strokeWidth":733,"strokeDashArray":745,"style":736},[735,735],[238,747],{"x1":703,"y1":748,"x2":749,"y2":748,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":751},"124.0","290.0","stroke-opacity:0.6","url(#fa62c36fbf)",[76,753,757],{"x":754,"y":755,"fill":73,"style":756,"textAnchor":706},"204.0","117.0","font-size:12px","HTML + Server-Timing",[238,759],{"x1":713,"y1":760,"x2":761,"y2":760,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":751},"164.0","466.0",[76,763,766],{"x":764,"y":765,"fill":73,"style":756,"textAnchor":706},"380.0","157.0","timing entries",[238,768],{"x1":720,"y1":754,"x2":769,"y2":754,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":751},"642.0",[76,771,774],{"x":772,"y":773,"fill":73,"style":756,"textAnchor":706},"556.0","197.0","beacon with phases + TTFB",[238,776],{"x1":727,"y1":777,"x2":778,"y2":777,"stroke":73,"strokeWidth":733,"strokeDashArray":779,"style":750,"markerEnd":751},"244.0","470.0",[682,735],[76,781,783],{"x":772,"y":782,"fill":73,"style":756,"textAnchor":706},"237.0","p75 per phase",[152,785,787],{"id":786},"verification","Verification",[15,789,790],{},"After fixing the slowest phase (for example, caching or parallelising a dependency), the corresponding Server-Timing entry should fall in the field at p75, along with TTFB for the affected template. Compare distributions, not just averages: tail latency of dependencies often matters more.",[152,792,794],{"id":793},"worked-example-a-product-page-with-a-slow-recommendations-api","Worked Example: A Product Page With a Slow Recommendations API",[15,796,797,798,800],{},"An online retailer saw product page TTFB p75 of 1.1 seconds for cache misses. Server-Timing entries collected in RUM showed ",[33,799,129],{}," at 410ms p75 (and over 1.2 seconds at p95), called sequentially after the product and pricing lookups. The team moved recommendations out of the critical path: the page rendered without them and fetched them client-side after load (they were below the fold). Pricing and product calls were parallelised. Server time p75 fell from 650ms to 160ms, TTFB p75 for misses to 520ms, and the recommendations still appeared before most users scrolled to them.",[15,802,803],{},[44,804,51,807,51,810,51,813,51,815,51,822,51,825,51,827,51,832,51,836,51,840,51,843,51,847,51,850,51,853,51,856,51,859,51,863,51,869,51,873,51,876,51,879,51,882,51,886,51,889,51,892,51,898,51,902,51,906,51],{"viewBox":805,"width":47,"role":48,"ariaLabel":806,"style":50},"0 0 760 149","Steps from collecting Server-Timing phases in the field to choosing and verifying a fix for the slowest phase.",[53,808],{"className":809,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,811,812],{},"From Server-Timing data to a fix",[64,814,806],{},[674,816,817],{},[677,818,820],{"id":819,"viewBox":680,"refX":681,"refY":682,"markerWidth":683,"markerHeight":683,"orient":684},"fa48e10bfe",[686,821],{"d":688,"fill":73,"style":689},[53,823],{"x":68,"y":68,"width":69,"height":824,"rx":71,"fill":72,"stroke":73,"style":74},"147",[76,826,812],{"x":78,"y":79,"fill":73,"style":80},[53,828],{"x":78,"y":91,"width":829,"height":830,"rx":699,"fill":95,"stroke":95,"style":831},"108.8","71.0","fill-opacity:0.14;stroke-opacity:0.9",[76,833,835],{"x":834,"y":704,"fill":73,"style":705,"textAnchor":706},"82.4","Instrument",[76,837,839],{"x":834,"y":838,"fill":73,"style":756,"textAnchor":706},"96.0","phases + cache",[53,841],{"x":842,"y":91,"width":829,"height":830,"rx":699,"fill":95,"stroke":95,"style":831},"176.8",[76,844,846],{"x":845,"y":704,"fill":73,"style":705,"textAnchor":706},"231.2","Collect",[76,848,849],{"x":845,"y":838,"fill":73,"style":756,"textAnchor":706},"RUM beacons",[53,851],{"x":852,"y":91,"width":829,"height":830,"rx":699,"fill":95,"stroke":95,"style":831},"325.6",[76,854,855],{"x":764,"y":704,"fill":73,"style":705,"textAnchor":706},"Rank",[76,857,858],{"x":764,"y":838,"fill":73,"style":756,"textAnchor":706},"p75 and p95 per",[76,860,862],{"x":764,"y":861,"fill":73,"style":756,"textAnchor":706},"112.0","phase",[53,864],{"x":865,"y":91,"width":829,"height":830,"rx":699,"fill":866,"stroke":867,"style":868},"474.4","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[76,870,872],{"x":871,"y":704,"fill":73,"style":705,"textAnchor":706},"528.8","Fix",[76,874,875],{"x":871,"y":838,"fill":73,"style":756,"textAnchor":706},"cache, parallelise,",[76,877,878],{"x":871,"y":861,"fill":73,"style":756,"textAnchor":706},"defer",[53,880],{"x":881,"y":91,"width":829,"height":830,"rx":699,"fill":95,"stroke":95,"style":831},"623.2",[76,883,885],{"x":884,"y":704,"fill":73,"style":705,"textAnchor":706},"677.6","Verify",[76,887,888],{"x":884,"y":838,"fill":73,"style":756,"textAnchor":706},"phase + TTFB",[76,890,891],{"x":884,"y":861,"fill":73,"style":756,"textAnchor":706},"drop",[238,893],{"x1":894,"y1":895,"x2":896,"y2":895,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":897},"136.8","91.5","174.8","url(#fa48e10bfe)",[238,899],{"x1":900,"y1":895,"x2":901,"y2":895,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":897},"285.6","323.6",[238,903],{"x1":904,"y1":895,"x2":905,"y2":895,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":897},"434.4","472.4",[238,907],{"x1":908,"y1":895,"x2":909,"y2":895,"stroke":73,"strokeWidth":733,"style":750,"markerEnd":897},"583.2","621.2",[152,911,913],{"id":912},"security-and-privacy-considerations","Security and Privacy Considerations",[15,915,916,917,920],{},"Server-Timing is visible to anyone who can load the page, including competitors and attackers. Avoid descriptions that reveal internal hostnames, query text, user identifiers or infrastructure details. Durations themselves can, in rare cases, leak information (for example, timing differences between existing and non-existing accounts). A common approach is to emit detailed timings only for sampled requests or for authenticated internal users, and coarse phases (cache, app, total) for everyone else. For cross-origin resources, ",[33,918,919],{},"Timing-Allow-Origin"," controls which origins can read the entries.",[152,922,924],{"id":923},"interpreting-the-numbers","Interpreting the Numbers",[15,926,927,928,930],{},"Server-Timing data is most useful when you compare distributions across phases and cache outcomes. A phase whose median is small but whose p95 is large is a tail-latency problem, usually caused by a dependency that sometimes times out or retries; adding a timeout with a fallback can remove the tail entirely. A phase that is consistently large is a throughput or design problem: add caching, precompute results, or move the work off the critical path. When the sum of reported phases is much smaller than ",[33,929,169],{},", the missing time is outside your instrumentation: network latency, load balancer queueing, or framework overhead before your handler runs. Add a phase that starts as early as possible in the request lifecycle to capture it.",[15,932,933],{},"Segment by cache status first. If cache hits are fast and misses slow, improving hit ratio is often cheaper than speeding up the miss path. If even hits are slow, the issue is in the edge or network path rather than the application.",[152,935,937],{"id":936},"building-a-dashboard","Building a Dashboard",[15,939,940],{},"A useful dashboard shows, per key template: TTFB p75, the p75 of each Server-Timing phase, the cache hit ratio, and the share of cold starts if you run on serverless. Plot them over time with deploy markers. When TTFB regresses, the phase that moved points at the cause within minutes rather than after a long investigation.",[152,942,944],{"id":943},"common-mistakes","Common Mistakes",[157,946,947,953,959,965],{},[160,948,949,952],{},[163,950,951],{},"Instrumenting only the total."," You need phases to find the culprit.",[160,954,955,958],{},[163,956,957],{},"Exposing sensitive descriptions."," Keep names generic.",[160,960,961,964],{},[163,962,963],{},"Measuring only in the lab."," Field p75 and p95 reveal tail latency that a few test runs miss.",[160,966,967,970],{},[163,968,969],{},"Ignoring queueing time."," Time before your handler runs is invisible unless you measure from request arrival.",[152,972,974],{"id":973},"edge-cases","Edge Cases",[15,976,977,980],{},[163,978,979],{},"CDN-added entries."," Some CDNs add their own Server-Timing entries (cache status, edge time); keep names distinct to avoid confusion.",[15,982,983,986],{},[163,984,985],{},"Streaming responses."," Headers are sent with the first flush; later phases can be reported via a separate beacon or trailers where supported.",[15,988,989,992],{},[163,990,991],{},"Serverless platforms."," Report initialisation time as a separate entry to see cold start impact.",[15,994,995,998],{},[163,996,997],{},"Multiple services."," Propagate timings from downstream services through a tracing system; Server-Timing summarises the top-level view.",[152,1000,1002],{"id":1001},"faq","FAQ",[1004,1005,1008,1012],"details",{"className":1006},[1007],"faq-item",[1009,1010,1011],"summary",{},"Is Server-Timing supported in all browsers?",[15,1013,1014],{},"All major browsers support reading Server-Timing via the Performance APIs. DevTools display support varies, but the data is available to scripts.",[1004,1016,1018,1021],{"className":1017},[1007],[1009,1019,1020],{},"Does Server-Timing add overhead?",[15,1022,1023],{},"Negligible — a short header and a few timer calls. Avoid very many entries per response.",[1004,1025,1027,1030],{"className":1026},[1007],[1009,1028,1029],{},"Can I use Server-Timing for subresources?",[15,1031,1032,1033,1035],{},"Yes. API and asset responses can include it; cross-origin resources need ",[33,1034,919],{}," for scripts to read the entries.",[1004,1037,1039,1042],{"className":1038},[1007],[1009,1040,1041],{},"How is this different from distributed tracing?",[15,1043,1044],{},"Tracing captures detailed spans across services for engineers. Server-Timing delivers a summary to the browser, where it can be correlated with user-facing metrics like LCP.",[1004,1046,1048,1051],{"className":1047},[1007],[1009,1049,1050],{},"Should I include a desc attribute?",[15,1052,1053],{},"Optional. It is useful for categorical data like cache hit or miss, but keep it free of sensitive information.",[1004,1055,1057,1060],{"className":1056},[1007],[1009,1058,1059],{},"What does a cache entry tell me?",[15,1061,1062],{},"Whether the response came from cache, so you can split TTFB into hit and miss distributions and see whether improving hit ratio or server time matters more.",[1004,1064,1066,1069],{"className":1065},[1007],[1009,1067,1068],{},"Can CDNs strip Server-Timing headers?",[15,1070,1071],{},"Some configurations remove unknown headers or replace them. Check the header at the browser, not just at the origin.",[152,1073,1075],{"id":1074},"related","Related",[157,1077,1078,1084,1091],{},[160,1079,1080,1083],{},[19,1081,1082],{"href":21},"Time to First Byte (TTFB) optimization"," — the full workflow.",[160,1085,1086,1090],{},[19,1087,1089],{"href":1088},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Ffixing-ttfb-from-serverless-cold-starts\u002F","Fixing TTFB from serverless cold starts"," — when initialisation dominates.",[160,1092,1093,1097],{},[19,1094,1096],{"href":1095},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fdiagnosing-cdn-cache-misses-from-response-headers\u002F","Diagnosing CDN cache misses from response headers"," — cache status in headers.",[1099,1100,1102],"script",{"type":1101},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Diagnose Slow TTFB with Server-Timing\",\n  \"description\": \"How to instrument server responses with Server-Timing, read the data in DevTools and field monitoring, and use it to fix slow server time.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Instrument the server\",\n      \"text\": \"Instrument the server\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Add cache status and total\",\n      \"text\": \"Report cache hits as entries (cache;desc=\\\"hit\\\") and a total (total;dur=...), so you can segment field data by cache outcome.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Read it in DevTools\",\n      \"text\": \"In the Network panel, select the document request and open the Timing tab: Server-Timing entries appear under \\\"Server Timing\\\" with their durations.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Collect it in RUM\",\n      \"text\": \"Collect it in RUM\"\n    }\n  ]\n}\n",[1099,1104,1105],{"type":1101},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Diagnose Slow TTFB with Server-Timing\",\n  \"description\": \"How to instrument server responses with Server-Timing, read the data in DevTools and field monitoring, and use it to fix slow server time.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing\u002F\"\n  }\n}\n",[1099,1107,1108],{"type":1101},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Network & Server Response Optimization\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Time to First Byte (TTFB) Optimization\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Diagnosing Slow TTFB with Server-Timing\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing\u002F\"\n    }\n  ]\n}\n",[1110,1111,1112],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":232,"searchDepth":246,"depth":246,"links":1114},[1115,1116,1117,1123,1124,1125,1126,1127,1128,1129,1130,1131],{"id":154,"depth":246,"text":155},{"id":191,"depth":246,"text":192},{"id":219,"depth":246,"text":220,"children":1118},[1119,1120,1121,1122],{"id":224,"depth":264,"text":225},{"id":506,"depth":264,"text":507},{"id":521,"depth":264,"text":522},{"id":528,"depth":264,"text":529},{"id":786,"depth":246,"text":787},{"id":793,"depth":246,"text":794},{"id":912,"depth":246,"text":913},{"id":923,"depth":246,"text":924},{"id":936,"depth":246,"text":937},{"id":943,"depth":246,"text":944},{"id":973,"depth":246,"text":974},{"id":1001,"depth":246,"text":1002},{"id":1074,"depth":246,"text":1075},"How to instrument server responses with Server-Timing, read the data in DevTools and field monitoring, and use it to fix slow server time.","md",{"slug":1135,"type":1136,"breadcrumb":1137,"datePublished":1146,"dateModified":1146},"diagnosing-slow-ttfb-with-server-timing","article",[1138,1141,1142,1144],{"name":1139,"url":1140},"Home","\u002F",{"name":27,"url":26},{"name":1143,"url":21},"Time to First Byte (TTFB) Optimization",{"name":5,"url":1145},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing\u002F","2026-10-06",true,"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing",{"title":1150,"description":1151},"Diagnosing Slow TTFB with the Server-Timing Header","Add Server-Timing to responses to break server time into database, API, cache and render phases, read it in DevTools and RUM, and find the slow dependency.","network-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing\u002Findex","zickj44V41HGV5GLnFtij8g6pHPPnnlRcdc0UnwfD88",[1155,1158],{"title":1143,"path":1156,"stem":1157,"children":-1},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization","network-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Findex",{"title":1159,"path":1160,"stem":1161,"children":-1},"Eliminating Redirect Chains","\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Feliminating-redirect-chains","network-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Feliminating-redirect-chains\u002Findex",1791308072196]