- Notifications
You must be signed in to change notification settings - Fork1
⚡️ 💾 Web Performance Snippets
License
tunetheweb/webperf-snippets
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A curated list of snippets to get Web Performance metrics to use in the browser console
- ⚡️💾 Web Performance Snippets
- Core Web Vitals
- Loading
- Time To First Byte
- Scripts Loading
- Resources hints
- Find Above The Fold Lazy Loaded Images
- Find non Lazy Loaded Images outside of the viewport
- Find render-blocking resources
- Image Info
- Fonts Preloaded, Loaded, and Used Above The Fold
- First And Third Party Script Info
- First And Third Party Script Timings
- Inline Script Info and Size
- Inline Script Info and Size Including
__NEXT_DATA__ - Get your
<head>in order
- Interaction
/** * PerformanceObserver */constpo=newPerformanceObserver((list)=>{letentries=list.getEntries();entries=dedupe(entries,"startTime");/** * Print all entries of LCP */entries.forEach((item,i)=>{console.dir(item);console.log(`${i+1} current LCP item :${item.element}:${item.startTime}`);/** * Highlight LCP elements on the page */item.element ?(item.element.style="border: 5px dotted blue;") :"";});/** * LCP is the lastEntry in getEntries Array */constlastEntry=entries[entries.length-1];/** * Print final LCP */console.log(`LCP is:${lastEntry.startTime}`);});/** * Start observing for largest-contentful-paint * buffered true getEntries prior to this script execution */po.observe({type:"largest-contentful-paint",buffered:true});functiondedupe(arr,key){return[...newMap(arr.map((item)=>[item[key],item])).values()];}
This script it's part of theWeb Vitals Chrome Extension and appear on theOptimize Largest Contentful Paint post.

constLCP_SUB_PARTS=['Time to first byte','Resource load delay','Resource load time','Element render delay',];newPerformanceObserver((list)=>{constlcpEntry=list.getEntries().at(-1);constnavEntry=performance.getEntriesByType('navigation')[0];constlcpResEntry=performance.getEntriesByType('resource').filter((e)=>e.name===lcpEntry.url)[0];constttfb=navEntry.responseStart;constlcpRequestStart=Math.max(ttfb,lcpResEntry ?lcpResEntry.requestStart||lcpResEntry.startTime :0);constlcpResponseEnd=Math.max(lcpRequestStart,lcpResEntry ?lcpResEntry.responseEnd :0);constlcpRenderTime=Math.max(lcpResponseEnd,lcpEntry ?lcpEntry.startTime :0);LCP_SUB_PARTS.forEach((part)=>performance.clearMeasures(part));constlcpSubPartMeasures=[performance.measure(LCP_SUB_PARTS[0],{start:0,end:ttfb,}),performance.measure(LCP_SUB_PARTS[1],{start:ttfb,end:lcpRequestStart,}),performance.measure(LCP_SUB_PARTS[2],{start:lcpRequestStart,end:lcpResponseEnd,}),performance.measure(LCP_SUB_PARTS[3],{start:lcpResponseEnd,end:lcpRenderTime,}),];// Log helpful debug information to the console.console.log('LCP value: ',lcpRenderTime);console.log('LCP element: ',lcpEntry.element,lcpEntry?.url);console.table(lcpSubPartMeasures.map((measure)=>({'LCP sub-part':measure.name,'Time (ms)':measure.duration,'% of LCP':`${Math.round((1000*measure.duration)/lcpRenderTime)/10}%`,})));}).observe({type:'largest-contentful-paint',buffered:true});
Context:Largest Contentful Paint change in Chrome 112 to ignore low-entropy images
This snippet is based on and with the permissionStoyan Stefanov, read his posthere.
With the script you can get a list of the BPP of all(1) images loaded on the site.
(1) the images with source "data:image" and third-party images are ignored.
console.table([...document.images].filter((img)=>img.currentSrc!=""&&!img.currentSrc.includes("data:image")).map((img)=>[img.currentSrc,(performance.getEntriesByName(img.currentSrc)[0]?.encodedBodySize*8)/(img.width*img.height),]).filter((img)=>img[1]!==0));
try{letcumulativeLayoutShiftScore=0;constobserver=newPerformanceObserver((list)=>{for(constentryoflist.getEntries()){if(!entry.hadRecentInput){cumulativeLayoutShiftScore+=entry.value;}}});observer.observe({type:"layout-shift",buffered:true});document.addEventListener("visibilitychange",()=>{if(document.visibilityState==="hidden"){observer.takeRecords();observer.disconnect();console.log(`CLS:${cumulativeLayoutShiftScore}`);}});}catch(e){console.error(`Browser doesn't support this API`);}
Measure the time to first byte, from the document
newPerformanceObserver((entryList)=>{const[pageNav]=entryList.getEntriesByType("navigation");console.log(`TTFB (ms):${pageNav.responseStart}`);}).observe({type:"navigation",buffered:true,});
Measure the time to first byte of all the resources loaded
newPerformanceObserver((entryList)=>{constentries=entryList.getEntries();constresourcesLoaded=[...entries].map((entry)=>{letobj={};// Some resources may have a responseStart value of 0, due// to the resource being cached, or a cross-origin resource// being served without a Timing-Allow-Origin header set.if(entry.responseStart>0){obj={"TTFB (ms)":entry.responseStart,Resource:entry.name,};}returnobj;});console.table(resourcesLoaded);}).observe({type:"resource",buffered:true,});
List all the<scripts> in the DOM and show a table to see if are loadedasync and/ordefer
constscripts=document.querySelectorAll("script[src]");constscriptsLoading=[...scripts].map((obj)=>{letnewObj={};newObj={src:obj.src,async:obj.async,defer:obj.defer,"render blocking":obj.async||obj.defer ?"" :"🟥",};returnnewObj;});console.table(scriptsLoading);
Check is the page has resources hints
constrels=["preload","prefetch","preconnect","dns-prefetch","preconnect dns-prefetch","prerender","modulepreload",];rels.forEach((element)=>{constlinkElements=document.querySelectorAll(`link[rel="${element}"]`);constdot=linkElements.length>0 ?"🟩" :"🟥";console.log(`${dot}${element}`);linkElements.forEach((el)=>console.log(el));});
List all images that haveloading="lazy" or[data-src](lazy loading via JS) above the fold
functionfindATFLazyLoadedImages(){constlazy=document.querySelectorAll('[loading="lazy"], [data-src]');letlazyImages=[];lazy.forEach((tag)=>{constposition=parseInt(tag.getBoundingClientRect().top);if(position<window.innerHeight&&position!==0){lazyImages=[...lazyImages,tag];}});returnlazyImages.length>0 ?lazyImages :false;}console.log(findATFLazyLoadedImages());
List all images that don't haveloading="lazy" or[data-src](lazy loading via JS) and are not in the viewport when the page loads. This script will help you find candidates for lazy loading.
// Execute it after the page has loaded without any user interaction (Scroll, click, etc)functionfindImgCanidatesForLazyLoading(){letnotLazyImages=document.querySelectorAll('img:not([data-src]):not([loading="lazy"])');returnArray.from(notLazyImages).filter((tag)=>!isInViewport(tag));}functionisInViewport(tag){letrect=tag.getBoundingClientRect();return(rect.bottom>=0&&rect.right>=0&&rect.top<=(window.innerHeight||document.documentElement.clientHeight)&&rect.left<=(window.innerWidth||document.documentElement.clientWidth));}console.log("Consider lazyloading the following images: ",findImgCanidatesForLazyLoading());
List all resources that are blocking rendering.
It's currently Chromium only
functionRenderBlocking({startTime, duration, responseEnd, name, initiatorType}){this.startTime=startTimethis.duration=durationthis.responseEnd=responseEndthis.name=namethis.initiatorType=initiatorType}functionfindRenderBlockingResources(){returnwindow.performance.getEntriesByType('resource').filter(({renderBlockingStatus})=>renderBlockingStatus==='blocking').map(({startTime, duration, responseEnd, name, initiatorType})=>newRenderBlocking({startTime, duration, responseEnd, name, initiatorType}));}console.table(findRenderBlockingResources())
List all image resources and sort by (name, transferSize, encodedBodySize, decodedBodySize, initiatorType)
functiongetImgs(sortBy){constimgs=[];constresourceListEntries=performance.getEntriesByType("resource");resourceListEntries.forEach(({ name, transferSize, encodedBodySize, decodedBodySize, initiatorType,})=>{if(initiatorType=="img"){imgs.push({ name, transferSize, decodedBodySize, encodedBodySize,});}});constimgList=imgs.sort((a,b)=>{returnb[sortBy]-a[sortBy];});returnimgList;}console.table(getImgs("encodedBodySize"));
List all the fonts preloaded via resources hints, all the fonts loaded via CSS, and all the fonts used in the viewport above the fold.
constlinkElements=document.querySelectorAll(`link[rel="preload"]`);constarrayLinks=Array.from(linkElements);constpreloadedFonts=arrayLinks.filter((link)=>link.as==="font");console.log("Fonts Preloaded via Resources Hints");preloadedFonts.forEach((font)=>console.log(`▸${font.href}`));console.log("");constloadedFonts=[ ...newSet(Array.from(document.fonts.values()).map((font)=>font).filter((font)=>font.status==="loaded").map((font)=>`${font.family} -${font.weight} -${font.style}`)),];console.log("Fonts and Weights Loaded in the Document");loadedFonts.forEach((font)=>console.log(`▸${font}`));console.log("");constchildrenSlector="body * > *:not(script):not(style):not(link):not(source)";constaboveFoldElements=Array.from(document.querySelectorAll(childrenSlector)).filter((elm)=>{constrect=elm.getBoundingClientRect();return(rect.top>=0&&rect.left>=0&&rect.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&rect.right<=(window.innerWidth||document.documentElement.clientWidth));});constusedFonts=Array.from(newSet([...aboveFoldElements].map((e)=>`${getComputedStyle(e).fontFamily} |${getComputedStyle(e).fontWeight} |${getComputedStyle(e).fontStyle}`)));console.log("Fonts and Weights Used Above the Fold");usedFonts.forEach((font)=>console.log(`▸${font}`));
List all scripts using PerformanceResourceTiming API and separating them by first and third party
// ex: katespade.com - list firsty party subdomains in HOSTS arrayconstHOSTS=["assets.katespade.com"];functiongetScriptInfo(){constresourceListEntries=performance.getEntriesByType("resource");// set for first party scriptsconstfirst=[];// set for third party scriptsconstthird=[];resourceListEntries.forEach((resource)=>{// check for initiator typeconstvalue="initiatorType"inresource;if(value){if(resource.initiatorType==="script"){const{ host}=newURL(resource.name);// check if resource url host matches location.host = first party scriptif(host===location.host||HOSTS.includes(host)){constjson=resource.toJSON();first.push({ ...json,type:"First Party"});}else{// add to third party scriptconstjson=resource.toJSON();third.push({ ...json,type:"Third Party"});}}}});constscripts={firstParty:[{name:"no data"}],thirdParty:[{name:"no data"}],};if(first.length){scripts.firstParty=first;}if(third.length){scripts.thirdParty=third;}returnscripts;}const{ firstParty, thirdParty}=getScriptInfo();console.groupCollapsed("FIRST PARTY SCRIPTS");console.table(firstParty);console.groupEnd();console.groupCollapsed("THIRD PARTY SCRIPTS");console.table(thirdParty);console.groupEnd();/*Choose which properties to displayhttps://developer.mozilla.org/en-US/docs/Web/API/console/tableconsole.groupCollapsed("FIRST PARTY SCRIPTS");console.table(firstParty, ["name", "nextHopProtocol"]);console.groupEnd();console.groupCollapsed("THIRD PARTY SCRIPTS", ["name", "nextHopProtocol"]);console.table(thirdParty);console.groupEnd();*/
This relies on the above script
Run First And Third Party Script Info in the console first, then run this
Info on CORS (why some values are 0)
Note: The properties which are returned as 0 by default when loading a resource from a domain other than the one of the web page itself: redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, and responseStart.
More Info on TAO header - Akamai Developer Resources
functioncreateUniqueLists(firstParty,thirdParty){functiongetUniqueListBy(arr,key){return[...newMap(arr.map((item)=>[item[key],item])).values()];}constfirstPartyList=getUniqueListBy(firstParty,["name"]);constthirdPartyList=getUniqueListBy(thirdParty,["name"]);return{ firstPartyList, thirdPartyList};}const{ firstPartyList, thirdPartyList}=createUniqueLists(firstParty,thirdParty);functioncalculateTimings(party,type){constpartyChoice=party==="first" ?firstParty :thirdParty;consttimingChoices={DNS_TIME:["domainLookupEnd","domainLookupStart"],TCP_HANDSHAKE:["connectEnd","connectStart"],RESPONSE_TIME:["responseEnd","responseStart"],SECURE_CONNECTION_TIME:["connectEnd","secureConnectionStart",0],FETCH_UNTIL_RESPONSE:["responseEnd","fetchStart",0],REQ_START_UNTIL_RES_END:["responseEnd","requestStart",0],START_UNTIL_RES_END:["responseEnd","startTime",0],REDIRECT_TIME:["redirectEnd","redirectStart"],};functionhandleChoices(timingEnd,timingStart,num){if(!num){returntimingEnd-timingStart;}if(timingStart>0){returntimingEnd-timingStart;}return0;}consttimings=partyChoice.map((script)=>{const[timingEnd,timingStart,num]=timingChoices[type];constendValue=script[timingEnd];conststartValue=script[timingStart];return{name:script.name,[type]:handleChoices(endValue,startValue,num),};});returntimings;}// Available OptionsconsttimingOptions=["DNS_TIME","TCP_HANDSHAKE","RESPONSE_TIME","SECURE_CONNECTION_TIME","FETCH_UNTIL_RESPONSE","REQ_START_UNTIL_RES_END","START_UNTIL_RES_END","REDIRECT_TIME",];// run em all!// https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API#timing_resource_loading_phasestimingOptions.forEach((timing)=>{console.groupCollapsed(`FIRST PARTY:${timing}`);console.table(calculateTimings("first",timing));console.groupEnd();console.groupCollapsed(`THIRD PARTY:${timing}`);console.table(calculateTimings("third",timing));console.groupEnd();});// choose your battle - arg1 is string either "first" or "third", arg2 is string timing option listed above.console.table(calculateTimings("first","REQ_START_UNTIL_RES_END"));
Find all inline scripts on the page and list the scripts and count. Find the total byte size of all the inline scripts in the console.
functionfindInlineScripts(){constinlineScripts=document.querySelectorAll(["script:not([async]):not([defer]):not([src])"])console.log(inlineScripts)console.log(`COUNT:${inlineScripts.length}`)lettotalByteSize=0letNEXT_DATA_SIZE=0for(constscriptof[...inlineScripts]){consthtml=script.innerHTMLconstsize=newBlob([html]).sizeif(script.id==="__NEXT_DATA__"){NEXT_DATA_SIZE+=size}totalByteSize+=sizereturn{totalByteSize:(totalByteSize/1000)+" kb",NEXT_DATA_SIZE:NEXT_DATA_SIZE===0 ?(NEXT_DATA_SIZE/1000) :0}}}console.log(findInlineScripts())
Find all inline scripts and their total size separately from__NEXT_DATA__ serialized JSON inline Script
functionfindInlineScriptsWithNextData(){constinlineScripts=document.querySelectorAll(["script:not([async]):not([defer]):not([src])"]);console.log(inlineScripts);console.log(`COUNT:${inlineScripts.length}`);constbyteSize={NEXT_DATA_SIZE:0,OTHER_SIZE:0};functiongetSize(script){consthtml=script.innerHTML;returnnewBlob([html]).size;}functionconvertToKb(bytes){returnbytes/1000;}for(constscriptof[...inlineScripts]){if(script.id=="__NEXT_DATA__"){byteSize.NEXT_DATA_SIZE+=getSize(script);}else{byteSize.OTHER_SIZE+=getSize(script);}}return{NEXT_DATA_SIZE:convertToKb(byteSize.NEXT_DATA_SIZE)+" kb",OTHER_SIZE:convertToKb(byteSize.OTHER_SIZE)+" kb",totalByteSize:convertToKb(byteSize.NEXT_DATA_SIZE)+convertToKb(byteSize.OTHER_SIZE)+" kb"};}console.log(findInlineScriptsWithNextData());
How you order elements in the can have an effect on the (perceived) performance of the page.
Usecapo.js theRick Viscomi script

To determine when long tasks happen, you can usePerformanceObserver and register to observe entries of typelongtask:
try{// Create the performance observer.constpo=newPerformanceObserver((list)=>{for(constentryoflist.getEntries()){// Log the entry and all associated details.console.table(entry.toJSON());}});// Start listening for `longtask` entries to be dispatched.po.observe({type:"longtask",buffered:true});}catch(e){console.error(`The browser doesn't support this API`);}
To find more specific information about layout shifts, you can usePerformanceObserver and register to observe entries of typelayout-shift:
functiongenColor(){letn=(Math.random()*0xfffff*1000000).toString(16);return"#"+n.slice(0,6);}// console.log(shifts) to see full list of shifts above thresholdconstshifts=[];// threshold ex: 0.05// Layout Shifts will be grouped by color.// All nodes attributed to the shift will have a border with the corresponding color// Shift value will be added above parent node.// Will have all details related to that shift in dropdown// Useful for single page applications and finding shifts after initial loadfunctionfindShifts(threshold){returnnewPerformanceObserver((list)=>{list.getEntries().forEach((entry)=>{if(entry.value>threshold&&!entry.hadRecentInput){constcolor=genColor();shifts.push(entry);console.log(shifts);constvalueNode=document.createElement("details");valueNode.innerHTML=` <summary>Layout Shift:${entry.value}</summary> <pre>${JSON.stringify(entry,null,2)}</pre> `;valueNode.style=`color:${color};`;entry.sources.forEach((source)=>{source.node.parentNode.insertBefore(valueNode,source.node);source.node.style=`border: 2px${color} solid`;});}});});}findShifts(0.05).observe({entryTypes:["layout-shift"]});
Print al the CLS metrics when load the page and the user interactive with the page:
newPerformanceObserver((entryList)=>{console.log(entryList.getEntries());}).observe({type:"layout-shift",buffered:true});
About
⚡️ 💾 Web Performance Snippets
Resources
License
Uh oh!
There was an error while loading.Please reload this page.