Created
June 22, 2021 13:10
-
-
Save ms-fadaei/d2645b461f008dd96036165e0433e454 to your computer and use it in GitHub Desktop.
Debug TTFB Like Google
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
// | |
// With this file, you can debug the TTFB | |
// and figure out what's going on | |
// | |
// Install dependecies: npm i web-vitals | |
// | |
// Use it like this: | |
// Import {getRDT, getBC, getDNS, getHS, getREQ} from "./debug-ttfb.js"; | |
// getDNS(console.log); // see: https://github.com/GoogleChrome/web-vitals#usage | |
// | |
// getRDT: Redirects duration | |
// getBC: Unload event, redirects and app cache duration | |
// getDNS: DNS lookup duration | |
// getHS: Initial connetctio duration (TCP + SSH) | |
// getREQ: The time between request start and respons start (Time To First Byte) | |
// | |
// See this image for more detail: https://w3c.github.io/navigation-timing/timestamp-diagram.svg | |
// | |
*/ | |
// please intall web-vital package from npm | |
import {initMetric} from "web-vitals/dist/modules/lib/initMetric"; | |
const afterLoad = (callback) => { | |
if (document.readyState === "complete") { | |
// Queue a task so the callback runs after `loadEventEnd`. | |
setTimeout(callback, 0); | |
} else { | |
// Use `pageshow` so the callback runs after `loadEventEnd`. | |
addEventListener("pageshow", callback); | |
} | |
}; | |
const getNavigationEntryFromPerformanceTiming = () => { | |
// Really annoying that TypeScript errors when using `PerformanceTiming`. | |
const timing = performance.timing; | |
const navigationEntry = { | |
entryType: "navigation", | |
startTime: 0, | |
}; | |
for (const key in timing) { | |
if (key !== "navigationStart" && key !== "toJSON") { | |
navigationEntry[key] = Math.max(timing[key] - timing.navigationStart, 0); | |
} | |
} | |
return navigationEntry; | |
}; | |
export const getRDT = (onReport) => { | |
const metric = initMetric("RDT"); | |
afterLoad(() => { | |
try { | |
// Use the NavigationTiming L2 entry if available. | |
const navigationEntry = | |
performance.getEntriesByType("navigation")[0] || getNavigationEntryFromPerformanceTiming(); | |
metric.value = navigationEntry.redirectEnd - navigationEntry.redirectStart; | |
metric.delta = metric.value; | |
// In some cases the value reported is negative. Ignore these cases: | |
if (metric.value < 0) return; | |
metric.entries = [navigationEntry]; | |
onReport(metric); | |
} catch (error) { | |
// Do nothing. | |
} | |
}); | |
}; | |
export const getBC = (onReport) => { | |
const metric = initMetric("BC"); | |
afterLoad(() => { | |
try { | |
// Use the NavigationTiming L2 entry if available. | |
const navigationEntry = | |
performance.getEntriesByType("navigation")[0] || getNavigationEntryFromPerformanceTiming(); | |
metric.value = navigationEntry.domainLookupStart; | |
metric.delta = metric.value; | |
// In some cases the value reported is negative. Ignore these cases: | |
if (metric.value < 0) return; | |
metric.entries = [navigationEntry]; | |
onReport(metric); | |
} catch (error) { | |
// Do nothing. | |
} | |
}); | |
}; | |
export const getDNS = (onReport) => { | |
const metric = initMetric("DNS"); | |
afterLoad(() => { | |
try { | |
// Use the NavigationTiming L2 entry if available. | |
const navigationEntry = | |
performance.getEntriesByType("navigation")[0] || getNavigationEntryFromPerformanceTiming(); | |
metric.value = navigationEntry.domainLookupEnd - navigationEntry.domainLookupStart; | |
metric.delta = metric.value; | |
// In some cases the value reported is negative. Ignore these cases: | |
if (metric.value < 0) return; | |
metric.entries = [navigationEntry]; | |
onReport(metric); | |
} catch (error) { | |
// Do nothing. | |
} | |
}); | |
}; | |
export const getHS = (onReport) => { | |
const metric = initMetric("HS"); | |
afterLoad(() => { | |
try { | |
// Use the NavigationTiming L2 entry if available. | |
const navigationEntry = | |
performance.getEntriesByType("navigation")[0] || getNavigationEntryFromPerformanceTiming(); | |
metric.value = navigationEntry.connectEnd - navigationEntry.connectStart; | |
metric.delta = metric.value; | |
// In some cases the value reported is negative. Ignore these cases: | |
if (metric.value < 0) return; | |
metric.entries = [navigationEntry]; | |
onReport(metric); | |
} catch (error) { | |
// Do nothing. | |
} | |
}); | |
}; | |
export const getREQ = (onReport) => { | |
const metric = initMetric("REQ"); | |
afterLoad(() => { | |
try { | |
// Use the NavigationTiming L2 entry if available. | |
const navigationEntry = | |
performance.getEntriesByType("navigation")[0] || getNavigationEntryFromPerformanceTiming(); | |
metric.value = navigationEntry.responseStart - navigationEntry.requestStart; | |
metric.delta = metric.value; | |
// In some cases the value reported is negative. Ignore these cases: | |
if (metric.value < 0) return; | |
metric.entries = [navigationEntry]; | |
onReport(metric); | |
} catch (error) { | |
// Do nothing. | |
} | |
}); | |
}; | |
export default { | |
getRDT, | |
getBC, | |
getDNS, | |
getHS, | |
getREQ, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment