במאמר הזה מוסבר איך להשתמש בממשקי ה-API של Navigation ו-Resource Timing כדי להעריך את ביצועי הטעינה בשטח.
פורסם: 8 באוקטובר 2021
אם השתמשתם בוויסות נתונים של חיבור בחלונית "רשת" בכלי למפתחים של דפדפן (או ב-Lighthouse ב-Chrome) כדי להעריך את ביצועי הטעינה, אתם יודעים כמה נוח להשתמש בכלים האלה כדי לשפר את הביצועים. אתם יכולים למדוד במהירות את ההשפעה של אופטימיזציות הביצועים באמצעות מהירות חיבור עקבית ויציבה כנקודת בסיס. הבעיה היחידה היא שמדובר בבדיקה סינתטית, שמניבה נתוני מעבדה ולא נתונים מהשטח.
בדיקות סינתטיות הן לא רעות כשלעצמן, אבל הן לא מייצגות את מהירות הטעינה של האתר עבור משתמשים אמיתיים. לשם כך נדרשים נתוני שדות, שאפשר לאסוף באמצעות ממשקי ה-API של Navigation Timing ו-Resource Timing.
ממשקי API שיעזרו לכם להעריך את ביצועי הטעינה בשטח
Navigation Timing ו-Resource Timing הם שני ממשקי API דומים עם חפיפה משמעותית, שמודדים שני דברים שונים:
- תזמון הניווט מודד את מהירות הבקשות למסמכי HTML (כלומר, בקשות ניווט).
- Resource Timing (תזמון משאבים) מודד את מהירות הבקשות למשאבים שתלויים במסמך, כמו CSS, JavaScript, תמונות וסוגים אחרים של משאבים.
הנתונים של ממשקי ה-API האלה מוצגים במאגר זמני של נתוני ביצועים, שאפשר לגשת אליו בדפדפן באמצעות JavaScript. יש כמה דרכים לשאילתות לגבי מאגר ביצועים, אבל דרך נפוצה היא באמצעות performance.getEntriesByType:
// Get Navigation Timing entries:
performance.getEntriesByType('navigation');
// Get Resource Timing entries:
performance.getEntriesByType('resource');
performance.getEntriesByType מקבלת מחרוזת שמתארת את סוג הרשומות שרוצים לאחזר ממאגר הרשומות של נתוני הביצועים. 'navigation' ו-'resource' מאחזרים תזמונים עבור Navigation Timing API ו-Resource Timing API, בהתאמה.
כמות המידע שממשקי ה-API האלה מספקים יכולה להיות גדולה מדי, אבל הם המפתח למדידת ביצועי הטעינה בשטח, כי אפשר לאסוף את נתוני התזמון האלה מהמשתמשים בזמן שהם מבקרים באתר שלכם.
התוקף והתזמון של בקשת רשת
איסוף וניתוח של נתוני תזמון של ניווט ומשאבים דומים לארכיאולוגיה, כי אתם משחזרים את משך החיים הקצר של בקשת רשת אחרי שהיא כבר בוצעה. לפעמים כדאי להמחיש מושגים, ובמקרה של בקשות רשת, כלי הפיתוח של הדפדפן יכולים לעזור.
לבקשה ברשת יש שלבים נפרדים, כמו חיפוש DNS, יצירת חיבור, משא ומתן של TLS ומקורות אחרים של זמן אחזור. התזמונים האלה מיוצגים כDOMHighResTimestamp. בהתאם לדפדפן, רמת הפירוט של התזמונים יכולה להיות עד מיקרו-שנייה, או שהיא מעוגלת למעלה למילי-שנייה. כדאי לבדוק את השלבים האלה בפירוט, ואת הקשר שלהם ל-Navigation Timing ול-Resource Timing.
חיפוש DNS
כשמשתמש עובר לכתובת URL, מתבצעת שאילתה במערכת DNS כדי לתרגם דומיין לכתובת IP. התהליך הזה עשוי להימשך זמן רב – כדאי למדוד את הזמן הזה בשטח. התזמון של הניווט והתזמון של המשאבים חושפים שני תזמונים שקשורים ל-DNS:
-
domainLookupStartהוא הזמן שבו מתחיל חיפוש ה-DNS. -
domainLookupEndהוא הזמן שבו מסתיים חיפוש ה-DNS.
כדי לחשב את הזמן הכולל של חיפוש DNS, מפחיתים את מדד ההתחלה ממדד הסיום:
// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
משא ומתן על חיבור
גורם נוסף שמשפיע על ביצועי הטעינה הוא משא ומתן על חיבור, שהוא זמן האחזור שנוצר כשמתחברים לשרת אינטרנט. אם נעשה שימוש ב-HTTPS, התהליך הזה יכלול גם את זמן המשא ומתן של TLS. שלב החיבור מורכב משלושה פרקי זמן:
-
connectStartהוא הזמן שבו הדפדפן מתחיל לפתוח חיבור לשרת אינטרנט. -
secureConnectionStartמציין מתי הלקוח מתחיל משא ומתן בפרוטוקול TLS. connectEndהוא הזמן שבו החיבור לשרת האינטרנט נוצר.
חישוב הזמן הכולל של החיבור דומה לחישוב הזמן הכולל של חיפוש DNS: מפחיתים את זמן ההתחלה מזמן הסיום. עם זאת, יש מאפיין נוסף secureConnectionStart שיכול להיות 0 אם לא נעשה שימוש ב-HTTPS או אם החיבור הוא קבוע. אם רוצים למדוד את הזמן של משא ומתן על TLS, צריך לזכור את הנקודות הבאות:
// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with
// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
// Awesome! Calculate it!
tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}
אחרי שמסתיימת בדיקת ה-DNS והמשא ומתן על החיבור, נכנסים לתמונה נתונים שקשורים לאחזור מסמכים ולמשאבים שתלויים בהם.
בקשות ותגובות
הביצועים של הטעינה מושפעים משני סוגים של גורמים:
- גורמים חיצוניים: אלה דברים כמו זמן אחזור ורוחב פס. מעבר לבחירת חברת אירוח ואולי CDN, אנחנו לא יכולים לשלוט בהם (ברוב המקרים), כי משתמשים יכולים לגשת לאינטרנט מכל מקום.
- גורמים פנימיים: אלה דברים כמו ארכיטקטורות בצד השרת ובצד הלקוח, וגם גודל המשאבים והיכולת שלנו לבצע אופטימיזציה לדברים האלה, שנמצאים בשליטה שלנו.
שני סוגי הגורמים משפיעים על ביצועי הטעינה. התזמונים שקשורים לגורמים האלה הם חיוניים, כי הם מתארים כמה זמן לוקח להוריד משאבים. המדדים הבאים מתארים את ביצועי הטעינה של Navigation Timing ו-Resource Timing:
- הסימון
fetchStartמציין מתי הדפדפן מתחיל לאחזר משאב (תזמון משאבים) או מסמך לבקשת ניווט (תזמון ניווט). הפעולה הזו קודמת לבקשה בפועל, ובשלב הזה הדפדפן בודק את המטמון (לדוגמה, HTTP ומופעים שלCache). - הסימון
workerStartמציין מתי מתחילה הטיפול בבקשה בתוך ה-handler של האירועfetchשל Service Worker. הערך יהיה0אם אין קובץ שירות (service worker) ששולט בדף הנוכחי. -
requestStartהוא הזמן שבו הדפדפן שולח את הבקשה. -
responseStartהוא הרגע שבו מגיע הבייט הראשון של התגובה. -
responseEndהוא הרגע שבו מגיע הבייט האחרון של התשובה.
התזמונים האלה מאפשרים לכם למדוד כמה היבטים של ביצועי הטעינה, כמו חיפוש במטמון בתוך Service Worker וזמן ההורדה:
// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;
// Service worker time plus response time
let workerTime = 0;
if (pageNav.workerStart > 0) {
workerTime = pageNav.responseEnd - pageNav.workerStart;
}
אפשר גם למדוד היבטים אחרים של זמן האחזור של בקשות ותגובות:
const [pageNav] = performance.getEntriesByType('navigation');
// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;
// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;
// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;
מדידות נוספות שאפשר לבצע
התזמון של הניווט והתזמון של המשאבים שימושיים למטרות נוספות מעבר למה שמתואר בדוגמאות הקודמות. הנה כמה מצבים נוספים עם תזמונים רלוונטיים שכדאי לבדוק:
- הפניות אוטומטיות בדף: הפניות אוטומטיות הן מקור שקל לפספס כשמנסים להבין למה יש השהיה, במיוחד שרשראות של הפניות אוטומטיות. השהיה מתווספת בכמה דרכים, כמו מעברים מ-HTTP ל-HTTPS, וגם הפניות אוטומטיות מסוג 302 או 301 שלא נשמרו במטמון. הנתונים לגבי הזמנים של
redirectStart, redirectEndו-redirectCountעוזרים להעריך את זמן האחזור של ההפניה האוטומטית. - פריקת מסמך: בדפים שמופעל בהם קוד ב
unloadגורם מטפל באירועים, הדפדפן צריך להריץ את הקוד הזה לפני שהוא יכול לעבור לדף הבא. unloadEventStartו-unloadEventEndמודדים את פריקת המסמך. - עיבוד מסמכים: משך הזמן של עיבוד המסמכים לא משמעותי בדרך כלל, אלא אם האתר שולח מטען ייעודי (payload) גדול מאוד של HTML. אם זה המצב שלכם, יכול להיות שתתעניינו בתזמונים של
domInteractive,domContentLoadedEventStart,domContentLoadedEventEndו-domComplete.
איך מקבלים תזמונים בקוד
בכל הדוגמאות שמוצגות עד עכשיו נעשה שימוש ב-performance.getEntriesByType, אבל יש דרכים אחרות לשאילתות במאגר הזמני של נתוני הביצועים, כמו performance.getEntriesByName ו-performance.getEntries. השיטות האלה מתאימות כשצריך ניתוח קל בלבד. במצבים אחרים, הם יכולים לגרום לעומס יתר על ה-main thread על ידי איטרציה על מספר גדול של רשומות, או אפילו על ידי בדיקה חוזרת של מאגר הביצועים כדי למצוא רשומות חדשות.
הגישה המומלצת לאיסוף נתונים ממאגר הזמני של רשומות הביצועים היא שימוש ב-PerformanceObserver. PerformanceObserver מקשיב לרשומות של ביצועים ומספק אותן כשהן מתווספות למאגר:
// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
// Get all resource entries collected so far:
const entries = observedEntries.getEntries();
// Iterate over entries:
for (let i = 0; i < entries.length; i++) {
// Do the work!
}
});
// Run the observer for Navigation Timing entries:
perfObserver.observe({
type: 'navigation',
buffered: true
});
// Run the observer for Resource Timing entries:
perfObserver.observe({
type: 'resource',
buffered: true
});
יכול להיות שהשיטה הזו של איסוף נתוני תזמון תיראה לכם מסורבלת בהשוואה לגישה ישירה למאגר נתונים זמני של נתוני הביצועים, אבל היא עדיפה על הקצאת משאבים לשרשור הראשי למשימות שלא משרתות מטרה קריטית שקשורה למשתמש.
איך מתקשרים הביתה
אחרי שתאספו את כל נתוני התזמון שאתם צריכים, תוכלו לשלוח אותם לנקודת קצה לצורך ניתוח נוסף. אפשר לעשות את זה בשתי דרכים: באמצעות navigator.sendBeacon או באמצעות fetch עם האפשרות keepalive. שתי השיטות ישלחו בקשה לנקודת קצה שצוינה באופן לא חוסם, והבקשה תתווסף לתור באופן שיאפשר לה להתקיים גם אחרי סיום הסשן הנוכחי בדף, אם יהיה צורך בכך:
// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
// Caution: If you have lots of performance entries, don't
// do this. This is an example for illustrative purposes.
const data = JSON.stringify(performance.getEntries());
// Send the data!
navigator.sendBeacon('/analytics', data);
}
בדוגמה הזו, מחרוזת ה-JSON תגיע במטען ייעודי (payload) של POST שאפשר לפענח, לעבד ולאחסן בשרת העורפי של האפליקציה לפי הצורך.
סיכום
אחרי שתאספו מדדים, תצטרכו להבין איך לנתח את הנתונים בשדה. כשמנתחים נתונים של שדות, חשוב להקפיד על כמה כללים כלליים כדי להבטיח שתסיקו מסקנות משמעותיות:
- אל תשתמשו בממוצעים, כי הם לא מייצגים את חוויית השימוש של אף משתמש, ויכול להיות שהם מוטים בגלל ערכים חריגים.
- להסתמך על אחוזונים. במערכי נתונים של מדדי ביצועים שמבוססים על זמן, ערך נמוך יותר הוא טוב יותר. כלומר, כשנותנים עדיפות לאחוזונים נמוכים, מתמקדים רק בחוויות המהירות ביותר.
- מתמקדים בערכים שמופיעים בתחתית הרשימה. כשנותנים עדיפות לחוויות בערך האחוזון ה-75 ומעלה, מתמקדים במקומות הנכונים: בחוויות הכי איטיות.
המדריך הזה לא נועד להיות מקור מידע מקיף על ניווט או על תזמון משאבים, אלא נקודת התחלה. הנה כמה מקורות מידע נוספים שיכולים לעזור:
- Navigation Timing Spec.
- Resource Timing Spec.
- ResourceTiming in Practice.
- Navigation Timing API (MDN)
- Resource Timing API (MDN)
בעזרת ממשקי ה-API האלה והנתונים שהם מספקים, תוכלו להבין טוב יותר את חוויית הטעינה של משתמשים אמיתיים, וכך תוכלו לאבחן ולפתור בעיות בביצועי הטעינה בשטח בצורה יעילה יותר.