אין להילחם בסורק הטעינה מראש של הדפדפן

גלו מהו סורק הטעינה מראש של הדפדפן, איך הוא עוזר לביצועים ואיך תוכלו לא להפריע לו.

אחד מההיבט שאתם לא להתעלם ממנו בעת אופטימיזציה של מהירות דף הוא להכיר קצת את הרכיבים הפנימיים של הדפדפן. דפדפנים מבצעים אופטימיזציות מסוימות כדי לשפר את הביצועים בדרכים שאנחנו כמו המפתחים לא יכולים לעשות – אבל רק כל עוד ביצוע האופטימיזציות האלה לא אסור בכוונה.

אחת האופטימיזציה הפנימית של הדפדפן שכדאי להבין היא סורק הטעינה מראש של הדפדפן. במאמר הזה נסביר איך סורק ההטענה מראש פועל, ומה חשוב יותר, איך אפשר להימנע מלהפריע לו.

מהו סורק טעינה מראש?

לכל דפדפן יש מנתח HTML ראשי שמחלק את התגים לסמלים של תגי עיצוב גולמיים ומעבד אותם למודל אובייקטים. התהליך הזה נמשך עד שהמנתח מושהה כשהוא מוצא משאב חסימה, כמו גיליון סגנונות שנטען עם אלמנט <link> או סקריפט שנטען עם אלמנט <script> ללא מאפיין async או defer.

תרשים מנתח HTML.
איור 1: תרשים שמראה איך אפשר לחסום את מנתח ה-HTML הראשי של הדפדפן. במקרה כזה, המנתח מפעיל רכיב <link> של קובץ CSS חיצוני, ולכן הדפדפן לא יכול לנתח את שאר המסמך — או אפילו לעבד חלק ממנו — עד להורדה ולניתוח של שירות ה-CSS.

במקרה של קובצי CSS, העיבוד נחסם כדי למנוע הבהוב של תוכן ללא עיצוב (FOUC). כלומר, אפשר לראות לזמן קצר גרסה לא מעוצבת של דף לפני שמחילים עליו סגנונות.

דף הבית של web.dev במצב ללא עיצוב (בצד שמאל) ובמצב המסוגנן (בצד ימין).
איור 2: סימולציה של דוגמה של FOUC. מצד ימין הוא הדף הראשי של web.dev ללא סגנונות. בצד ימין נמצא אותו הדף שבו הוחלו הסגנונות. המצב ללא העיצוב עשוי להתרחש במהירות אם הדפדפן לא חוסם את העיבוד במהלך הורדה ועיבוד של גיליון סגנונות.

הדפדפן גם חוסם ניתוח ורינדור של הדף כשהוא נתקל ברכיבי <script> ללא המאפיין defer או async.

הסיבה לכך היא שהדפדפן לא יכול לדעת בוודאות אם סקריפט מסוים ישנה את ה-DOM בזמן שמנתח ה-HTML הראשי עדיין מבצע את פעולתו. זו הסיבה לכך שבדרך כלל מקובל לטעון את ה-JavaScript בסוף המסמך, כדי שההשפעות של חסימת הניתוח והעיבוד יהפכו לשוליים.

אלה סיבות טובות לכך שהדפדפן צריך לחסום גם את הניתוח וגם את העיבוד. עם זאת, חסימת כל אחד מהשלבים החשובים האלה היא לא רצויה, כי הם עלולים לעצור את ההצגה על ידי עיכוב בגילוי של משאבים חשובים אחרים. למרבה המזל, הדפדפנים עושים כמיטב יכולתם כדי לטפל בבעיות האלה באמצעות מנתח HTML משני שנקרא סורק טעינה מראש.

תרשים של מנתח ה-HTML הראשי (בצד שמאל) וגם של סורק הטעינה מראש (בצד ימין), שהוא מנתח ה-HTML המשני.
איור 3: תרשים שממחיש איך סורק הטעינה מראש פועל במקביל למנתח ה-HTML הראשי כדי לטעון נכסים באופן ספקולטיבי. כאן, מנתח ה-HTML הראשי חסום כי הוא נטען ומעבד CSS לפני שיוכל להתחיל לעבד תגי עיצוב של תמונות ברכיב <body>, אבל סורק הטעינה מראש יכול להסתכל קדימה בתגי העיצוב הגולמיים כדי למצוא את משאב התמונה ולהתחיל לטעון אותו לפני ביטול החסימה של מנתח ה-HTML הראשי.

התפקיד של סורק טעינה מראש הוא ספקולטיבי. כלומר, הוא בודק תגי עיצוב גולמיים כדי למצוא משאבים לאחזור מדי פעם לפני שמנתח ה-HTML הראשי יגלה אותם בדרך אחרת.

איך יודעים מתי סורק ההטענה מראש פועל

הסורק של הטעינה מראש קיים כי יש חסימה של העיבוד והניתוח. אם שתי בעיות הביצועים האלה לא היו קיימות אף פעם, סורק ההטענה מראש לא היה שימושי במיוחד. המפתח לבירור אם סורק הטעינה מראש מועיל לדף אינטרנט מסוים תלוי בתופעות החסימה האלה. כדי לעשות זאת, אפשר ליצור עיכוב מלאכותי של בקשות כדי לגלות איפה פועל סורק הטעינה מראש.

כדוגמה, צלם את הדף הזה שמכיל טקסט ותמונות בסיסיים באמצעות גיליון סגנונות. מכיוון שקובצי CSS חוסמים גם את העיבוד וגם את הניתוח, יוצרת עיכוב מלאכותי של שתי שניות בגיליון הסגנון באמצעות שירות proxy. עם העיכוב הזה, קל יותר לראות ב-Waterfall של הרשת איפה פועל סורק הטעינה מראש.

תרשים ה-Waterfall של רשת WebPageTest ממחיש עיכוב מלאכותי של 2 שניות על גיליון הסגנונות.
איור 4: WebPageTest תרשים Waterfall של רשת של דף אינטרנט שפועל ב-Chrome במכשיר נייד באמצעות סימולציה של חיבור 3G. למרות שגיליון הסגנון מתעכב באופן מלאכותי באמצעות שרת proxy כשתי שניות לפני שהוא מתחיל להיטען, התמונה שנמצאת מאוחר יותר במטען הייעודי (payload) של תגי העיצוב מגלה על ידי סורק הטעינה מראש.

כמו שאפשר לראות ב-Waterfall, סורק הטעינה מראש מגלה את הרכיב <img> גם כשהעיבוד וניתוח המסמך חסומים. בלי האופטימיזציה הזו, הדפדפן לא יכול לאחזר דברים באופן זמני במהלך תקופת החסימה, ובקשות משאבים רבות יותר יהיו עוקבות ולא בו-זמניות.

בהמשך לדוגמה של הצעצוע, נבחן כמה דפוסים בעולם האמיתי שבהם אפשר להביס את סורק הטעינה מראש ומה אפשר לעשות כדי לתקן אותם.

סקריפטים מושתלים של async

נניח שיש ב-<head> שלכם HTML שמכיל קוד JavaScript מוטבע, כמו בדוגמה הבאה:

<script>
  const scriptEl = document.createElement('script');
  scriptEl.src = '/yall.min.js';

  document.head.appendChild(scriptEl);
</script>

סקריפטים שהוחדרו הם async כברירת מחדל, ולכן כשמריצים את הסקריפט הזה, ההתנהגות שלו תתנהג כאילו שהוחל עליו המאפיין async. כלומר, הוא יופעל בהקדם האפשרי ולא יחסום את הרינדור. נשמע אופטימלי, נכון? עם זאת, אם מניחים שה-<script> המוטבע הזה מגיע אחרי רכיב <link> שטוען קובץ CSS חיצוני, תתקבל תוצאה לא אופטימלית:

תרשים WebPageTest הזה מציג את סריקת הטעינה מראש שהושבתה כאשר סקריפט הוחדר.
איור 5: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. הדף מכיל גיליון סגנונות יחיד וסקריפט async שהוחדר. סורק הטעינה מראש לא יכול לגלות את הסקריפט במהלך שלב חסימת העיבוד כי הוא מוחדר ללקוח.

ריכזנו כאן פירוט של מה שקרה:

  1. אחרי 0 שניות, המסמך הראשי יתבקש.
  2. אחרי 1.4 שניות מגיע הבייט הראשון של בקשת הניווט.
  3. אחרי 2.0 שניות, מתבצעת בקשה ל-CSS ולתמונה.
  4. מאחר שהמנתח חסום לטעינת גיליון הסגנונות, וקוד ה-JavaScript המוטמע שמחדיר את הסקריפט async מגיע אחרי גיליון הסגנונות הזה, אחרי 2.6 שניות, הפונקציונליות שהסקריפט מספק לא זמינה בהקדם האפשרי.

זוהי לא אופטימלית מכיוון שהבקשה עבור הסקריפט מתרחשת רק לאחר שההורדה של גיליון הסגנון מסתיימת. הפעולה הזו מעכבת את הפעלת הסקריפט בהקדם האפשרי. לעומת זאת, מכיוון שהרכיב <img> ניתן לגילוי בתגי העיצוב שסופקו על ידי השרת, הוא מזוהה על ידי סורק הטעינה מראש.

אז מה קורה אם משתמשים בתג <script> רגיל עם המאפיין async במקום להחדיר את הסקריפט ל-DOM?

<script src="/yall.min.js" async></script>

זו התוצאה:

רשימת אירועים של רשת ב-WebPageTest שמראה איך סקריפט אסינכררוני שנטען באמצעות רכיב הסקריפט של HTML עדיין ניתן לגילוי על ידי סורק ההטענה מראש של הדפדפן, למרות שמנתח ה-HTML הראשי של הדפדפן חסום בזמן ההורדה והעיבוד של גיליון סגנונות.
איור 6: תרשים רשימת גורמים של רשת ב-WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך חיבור 3G מדומה. הדף מכיל גיליון סגנונות אחד ורכיב async <script> אחד. סורק הטעינה מראש מאתר את הסקריפט במהלך שלב חסימת העיבוד וטוען אותו בו-זמנית עם ה-CSS.

יש מפתה לנסות לטעון שבאמצעות rel=preload אפשר לפתור את הבעיות האלה. זה בהחלט יעבוד, אבל עשויות להיות לכך כמה תופעות לוואי. אחרי הכול, למה להשתמש ב-rel=preload כדי לפתור בעיה שלא ניתן למנוע באמצעות החדרת רכיב <script> ל-DOM?

Waterfall של WebPageTest שבו מוצג האופן שבו הרמז של המשאב rel=preload משמש כדי לקדם גילוי של סקריפט שהוחדר אסינכרוני – גם אם באופן שעלול להיות תופעות לוואי לא מכוונות.
איור 7: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. הדף מכיל גיליון סגנונות יחיד וסקריפט async שהוחדר, אבל הסקריפט async נטען מראש כדי להבטיח שהוא יתגלה מוקדם יותר.

טעינה מראש של 'תיקונים' הבעיה כאן, אבל היא מציגה בעיה חדשה: הסקריפט async בשתי ההדגמות הראשונות — למרות שהוא נטען ב-<head> — נטען ב"נמוך" בעדיפות גבוהה, ואילו גיליון הסגנון נטען ב'גבוהה ביותר'. בעדיפות גבוהה. בהדגמה האחרונה שבה הסקריפט async נטען מראש, גיליון הסגנון עדיין נטען ב'גבוהה ביותר' אבל העדיפות של הסקריפט קודמה ל'גבוהה'.

כשעדיפות משאב מסוים, הדפדפן מקצה לו רוחב פס גדול יותר. המשמעות היא שלמרות שגיליון הסגנון הוא בעדיפות הגבוהה ביותר, העדיפות המוגברת של הסקריפט עשויה לגרום לתחרות רוחב הפס. זה יכול להשפיע על חיבורים איטיים, או במקרים שבהם המשאבים די גדולים.

התשובה כאן פשוטה: אם דרוש סקריפט במהלך ההפעלה, אל תבטלו את סורק הטעינה מראש על ידי החדרת אותו ל-DOM. כדאי להתנסות לפי הצורך עם מיקום של רכיב <script>, וגם עם מאפיינים כמו defer ו-async.

טעינה מדורגת באמצעות JavaScript

טעינה מדורגת היא שיטה נהדרת לשימור נתונים, שבדרך כלל מיושמת על תמונות. עם זאת, לפעמים טעינה מדורגת חלה באופן שגוי על תמונות שנמצאות "בחלק העליון והקבוע".

המצב הזה יוצר בעיות אפשריות ביכולת הגילוי של המשאבים הרלוונטיים לסורק הטעינה מראש, ויכול לעכב שלא לצורך את הזמן שנדרש כדי לגלות הפניה לתמונה, להוריד אותה, לפענח אותה ולהציג אותה. ניקח לדוגמה את תג העיצוב של התמונה:

<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">

השימוש בקידומת data- הוא דפוס נפוץ בעומסי טעינה מדורגת המבוססים על JavaScript. כשגוללים את התמונה לאזור התצוגה, הטוען העצלני מסיר את הקידומת data-, כלומר שבדוגמה הקודמת, הערך data-src הופך ל-src. העדכון הזה ינחה את הדפדפן לאחזר את המשאב.

הדפוס הזה אינו בעייתי עד שהוא מוחל על תמונות שנמצאות באזור התצוגה במהלך ההפעלה. סורק הטעינה מראש לא קורא את המאפיין data-src באותו אופן שבו הוא קורא את המאפיין src (או srcset), לכן ההפניה לתמונה לא נמצאה קודם. גרוע יותר, טעינת התמונה מתעכבת עד לאחר ההורדה, ההידור וההפעלה של JavaScript של הטוען העצלני.

תרשים מפל של רשת WebPageTest שבו מוצג האופן שבו תמונה שנטענה באופן מדורג ומופיעה באזור התצוגה במהלך ההפעלה מתעכבת בהכרח, כי סורק הטעינה מראש של הדפדפן לא יכול למצוא את משאב התמונה, ונטען רק כשקוד ה-JavaScript נדרש לטעינה מדורגת לצורך עבודה. גילית את התמונה מאוחר יותר מהרגיל.
איור 8: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. משאב התמונה נטען באופן מדורגת שלא לצורך, למרות שהוא גלוי באזור התצוגה במהלך ההפעלה. הפעולה הזו מבטלת את הסריקה של טעינת הנתונים מראש וגורמת לעיכוב מיותר.

בהתאם לגודל התמונה, שעשוי להיות תלוי בגודל אזור התצוגה, הוא יכול להיות אלמנט מועמד להמהירות שבה נטען רכיב התוכן הכי גדול (LCP). כשסורק הטעינה מראש לא יכול לאחזר מראש את משאב התמונה באופן ספקולטיבי – אולי בזמן העיבוד של בלוקים של דפי סגנון של הדף – מתרחשת בעיה ב-LCP.

הפתרון הוא לשנות את ה-Markup של התמונה:

<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">

זהו התבנית האופטימלית לתמונות שנמצאות באזור התצוגה בזמן ההפעלה, כי סורק ההטענה מראש יזהה ויאתר את משאב התמונה מהר יותר.

תרשים מפל של רשת WebPageTest שבו מוצג תרחיש טעינה של תמונה באזור התצוגה במהלך ההפעלה. התמונה לא נטענת באיטרציה, כלומר היא לא תלויה בסקריפט כדי להיטען, ולכן סורק הטעינה מראש יכול לגלות אותה מוקדם יותר.
איור 9: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. סורק ההטענה מראש מזהה את משאב התמונה לפני שה-CSS וה-JavaScript מתחילים להיטען, וכך נותן לדפדפן יתרון מוקדם לטעינת המשאב.

התוצאה בדוגמה הפשוטה הזו היא שיפור של 100 אלפיות שנייה ב-LCP בחיבור איטי. אולי זה לא נראה כמו שיפור ענק, אבל המשמעות היא שהפתרון הוא תיקון מהיר לתגי עיצוב, ורוב דפי האינטרנט מורכבים יותר מקבוצת הדוגמאות הזו. המשמעות היא שרכיבי LCP עשויים להתחרות על רוחב הפס עם משאבים רבים אחרים, ולכן אופטימיזציות כאלה הופכות להיות חשובות יותר ויותר.

תמונות רקע של שירות CSS

חשוב לזכור שסורק הטעינה מראש של הדפדפן סורק את תגי העיצוב. לא מתבצע סריקה של סוגי משאבים אחרים, כמו CSS, שעשוי לכלול אחזורים של תמונות שיש אליהן הפניה מהנכס background-image.

בדומה ל-HTML, דפדפנים מעבדים CSS לתוך מודל אובייקטים משלהם, שנקרא CSSOM. אם מתגלים משאבים חיצוניים תוך כדי הבנייה של ה-CSSOM, הבקשה למשאבים האלה מתבצעת בזמן הגילוי ולא על ידי סורק הטעינה מראש.

נניח שהמועמד ל-LCP של הדף הוא רכיב עם מאפיין CSS‏ background-image. מה קורה כשהמשאבים נטענים:

תרשים מפל של רשת WebPageTest שבו מוצג דף עם מועמד LCP שנטען מ-CSS באמצעות המאפיין של תמונת רקע. מאחר שהתמונה של מועמד ה-LCP נמצאת בסוג משאב שסורק הטעינה מראש של הדפדפן לא יכול לבדוק, טעינת המשאב מתעכבת עד להורדה ולעיבוד של ה-CSS, ולכן זמן העיבוד של מועמד ה-LCP מתעכב.
איור 10: תרשים רשימת גורמים של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך חיבור 3G מדומה. אלמנט ה-LCP של הדף הוא רכיב עם מאפיין CSS מסוג background-image (שורה 3). התמונה שהוא מבקש לא מתחילה לאחזר עד שמנתח ה-CSS ימצא אותה.

במקרה הזה, סורק הטעינה מראש פחות נוטש כי הוא לא מעורב. עם זאת, אם אחד מהמועמדים ל-LCP בדף מגיע מנכס CSS מסוג background-image, כדאי לטעון מראש את התמונה הזו:

<!-- Make sure this is in the <head> below any
     stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">

ההצעה rel=preload קטנה, אבל היא עוזרת לדפדפן לגלות את התמונה מוקדם יותר מאשר אחרת:

תרשים מפל של רשת WebPageTest שבו תמונת רקע של CSS (המועמדת ל-LCP) נטענת הרבה יותר מהר בגלל השימוש ברמז rel=preload. זמן ה-LCP משתפר בכ-250 אלפיות השנייה.
איור 11: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. אלמנט ה-LCP של הדף הוא רכיב עם מאפיין CSS מסוג background-image (שורה 3). הרמז rel=preload עוזר לדפדפן לגלות את התמונה כ-250 אלפיות שנייה יותר מאשר בלי הרמז.

עם הרמז rel=preload, אפשר לגלות את מועמד ה-LCP מוקדם יותר, וכך לקצר את זמן ה-LCP. הרמז הזה עוזר לפתור את הבעיה, אבל עדיף להעריך אם צריך לטעון את התמונה שלכם (LCP) מ-CSS או לא. עם תג <img> תהיה לכם יותר שליטה בטעינת תמונה שמתאימה לאזור התצוגה, ובמקביל תאפשר לסורק הטעינה מראש לגלות אותה.

הטמעת יותר מדי משאבים בקוד

הטבעה היא שיטה שמציבה משאב בתוך קוד ה-HTML. ניתן להטביע גיליונות סגנונות ברכיבי <style>, בסקריפטים ברכיבי <script> ובכל משאב אחר באמצעות קידוד base64.

הטמעת משאבים עשויה להיות מהירה יותר מאשר ההורדה שלהם, מפני שלא מונפקת בקשה נפרדת למשאב. הוא מופיע ממש במסמך ונטען מיד. עם זאת, יש חסרונות משמעותיים:

  • אם אתם לא שומרים במטמון את קוד ה-HTML, ואתם לא יכולים לעשות זאת אם תגובת ה-HTML היא דינמית, המשאבים שבשורה לא נשמרים במטמון אף פעם. האפשרות הזו משפיעה על הביצועים כי אי אפשר לעשות שימוש חוזר במשאבים שבגוף ההודעה.
  • גם אם אפשר לשמור HTML במטמון, משאבים בגוף ההודעה לא ישותפו בין מסמכים. כך פוחתת היעילות של שמירת הקבצים במטמון בהשוואה לקבצים חיצוניים שאפשר לשמור במטמון ולהשתמש בהם מחדש במקור כולו.
  • אם תוסיפו יותר מדי תוכן בקוד, הסריקה של טעינת הנתונים מראש תתעכב בזיהוי המשאבים בהמשך המסמך, כי הורדת התוכן הנוסף שמוטמע בקוד אורכת יותר זמן.

ניקח לדוגמה את הדף הזה. בתנאים מסוימים, אלמנט ה-LCP הוא התמונה בחלק העליון של הדף, וה-CSS נמצא בקובץ נפרד שנטען על ידי רכיב <link>. הדף משתמש גם בארבעה גופנים באינטרנט המבוקשים כקבצים נפרדים מהמשאב של שירות ה-CSS.

תרשים מפל של רשת WebPageTest של דף עם קובץ CSS חיצוני עם הפניה לארבעה גופנים. התמונה של מועמד ה-LCP נמצאה על ידי סורק הטעינה מראש בזמן הנכון.
איור 12: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. מועמד ה-LCP של הדף הוא תמונה שנטענה מאלמנט <img>, אבל סורק הטעינה מראש גילה אותה כי ה-CSS והגופנים הנדרשים לטעינת הדף במשאבים נפרדים לא מונעים מסורק הטעינה מראש לבצע את המשימה שלו.

מה קורה אם ה-CSS וכל הגופנים מוגדרים כמשאבי base64?

תרשים רשת של WebPageTest של דף עם קובץ CSS חיצוני עם ארבעה גופנים שמפנים אליו. יש עיכוב משמעותי בסורק הטעינה מראש שגילו את תמונת ה-LCP .
איור 13: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שפועל ב-Chrome במכשיר נייד דרך סימולציה של חיבור 3G. מועמד ה-LCP של הדף הוא תמונה שנטענה מאלמנט <img>, אבל ההוספה של ה-CSS וארבעת משאבי הגופנים שלו ב-`` מעכבת את סורק הטעינה מראש לגלות את התמונה עד שהמשאבים האלה יורדים במלואם.

ההשפעה של ההטבעה מובילה להשלכות שליליות על LCP בדוגמה הזו, ועל הביצועים באופן כללי. בגרסת הדף שבה לא מוטמעים פריטים, תמונת ה-LCP מוצגת תוך כ-3.5 שניות. בדף שמטמיע את כל הטקסט, לא מתבצעת צביעה של תמונת ה-LCP עד שהיא נמשכת רק יותר מ-7 שניות.

יש כאן עוד יתרונות מלבד סורק הטעינה מראש. הטמעת גופנים בקוד היא לא אסטרטגיה מומלצת, כי base64 הוא פורמט לא יעיל למשאבים בינאריים. גורם נוסף חשוב הוא שלא מתבצעת הורדה של משאבי גופנים חיצוניים, אלא אם נקבע שהם נחוצים על ידי ה-CSSOM. כשהגופנים האלה מסומנים כ-base64, תתבצע הורדה שלהם גם אם הם נחוצים לדף הנוכחי וגם אם לא.

האם טעינה מראש יכולה לשפר את המצב? ודאי. אתם יכולים לטעון מראש את תמונת ה-LCP ולצמצם את זמן ה-LCP, אבל להגדלת נפח ה-HTML שעשוי להיות בלתי ניתן לשמירה באמצעות משאבים מוטבעים יש השלכות שליליות אחרות על הביצועים. הדפוס הזה משפיע גם על First Contentful Paint (FCP). בגרסת הדף שבה שום דבר לא משולב, אורך ה-FCP הוא בערך 2.7 שניות. בגרסה שבה כל המידע הוא בכתב, ערך FCP הוא בערך 5.8 שניות.

חשוב מאוד להיזהר כשמזינים דברים בתוך HTML, במיוחד משאבים בקידוד base64. באופן כללי, לא מומלץ לעשות זאת, למעט במקרים של משאבים קטנים מאוד. רצוי כמה שפחות מדי כניסות, כי הטמעה ארוכה מדי עלולה לגרום לדליקה.

עיבוד תגי עיצוב באמצעות JavaScript בצד הלקוח

אין ספק: JavaScript בהחלט משפיע על מהירות הדף. לא רק שהמפתחים מסתמכים על היכולות האלה כדי לספק אינטראקטיביות, אלא גם יש אנשים נוטים להסתמך על התכונה הזו כדי להציג את התוכן עצמו. כך אפשר לשפר את חוויית המפתח בדרכים מסוימות. אבל היתרונות למפתחים לא תמיד מובילים ליתרונות למשתמשים.

דפוס אחד שיכול להביס את סורק הטעינה מראש הוא רינדור תגי עיצוב באמצעות JavaScript בצד הלקוח:

Waterfall של רשת WebPageTest שבו מוצג דף בסיסי עם תמונות וטקסט שעברו רינדור מלא על ידי הלקוח ב-JavaScript. מאחר שהתווים האלה נכללים ב-JavaScript, סורק הטעינה מראש לא יכול לזהות אף אחד מהמשאבים. כל המשאבים מתעכבים גם בגלל זמן הטיפול והזמן הנוסף שנדרש ברשת במסגרות JavaScript.
איור 14: תרשים Waterfall של רשת WebPageTest של דף אינטרנט שעבר עיבוד על ידי לקוח, שפועל ב-Chrome במכשיר נייד באמצעות סימולציה של חיבור 3G. מכיוון שהתוכן נכלל ב-JavaScript ומסתמך על מסגרת לעיבוד, משאב התמונה בסימני ה-Markup שעובדו על ידי הלקוח מוסתר מסורק הטעינה מראש. חוויית המשתמש המקבילה עם עיבוד בצד השרת מוצגת באיור 9.

כאשר מטענים ייעודיים (payloads) של תגי עיצוב נמצאים בתוך JavaScript ומעובדים באופן מלא על ידי JavaScript, המשאבים בתגי העיצוב האלו אינם גלויים בפועל לסורק הטעינה מראש. הפעולה הזו מעכבת את הגילוי של משאבים חשובים, וזה בהחלט משפיע על LCP. במקרה של הדוגמאות האלה, הבקשה לתמונה מסוג LCP מתעכבת משמעותית בהשוואה לחוויה המקבילה שמעובדת על ידי שרת שבה לא נדרש JavaScript כדי להופיע.

זה סותר קצת את הנושא של מאמר זה, אבל ההשפעות של עיבוד תגי העיצוב על הלקוח הרבה מעבר להבסת סורק הטעינה מראש. קודם כול, שימוש ב-JavaScript כדי ליהנות מחוויה שלא מחייבת זמן עיבוד מיותר, עלול להשפיע על האינטראקציה (Interaction to Next Paint) (INP). אם מעבדים כמויות גדולות מאוד של תגי עיצוב בלקוח, יש סיכוי גבוה יותר שייווצר משימות ארוכות בהשוואה לאותה כמות של תגי עיצוב שנשלחת על ידי השרת. הסיבה לכך – מלבד העיבוד הנוסף שכרוך ב-JavaScript – היא שדפדפנים מעבירים סטרימינג של רכיבי קוד מהשרת ומחלקים את העיבוד כך שמשימות ארוכות מוגבלות. לעומת זאת, תגי עיצוב שמעובדים על ידי לקוח מטופלים כמשימה מונוליתית אחת, שעלולה להשפיע על ה-INP של דף.

הפתרון לתרחיש הזה תלוי בתשובה לשאלה הזו: האם יש סיבה לכך שהשרת לא יכול לספק את תגי העיצוב של הדף, ולא להיות מוצג אצל הלקוח? אם התשובה היא 'לא', יש להביא בחשבון את העיבוד בצד השרת (SSR) או את הסימון שנוצר באופן סטטי ככל האפשר, מכיוון שהדבר יעזור לסורק הטעינה מראש לגלות ולאחזר משאבים חשובים מראש מדי פעם.

אם הדף כן צריך JavaScript כדי לצרף פונקציונליות לחלקים מסוימים של ה-Markup בדף, עדיין תוכלו לעשות זאת באמצעות SSR, באמצעות JavaScript רגיל או באמצעות הידרציה כדי ליהנות מהיתרונות של שני העולמות.

עוזרים לסורק הטעינה מראש לעזור

סורק הטעינה מראש הוא אופטימיזציה יעילה במיוחד של הדפדפן, שעוזרת לדפים להיטען מהר יותר במהלך ההפעלה. הימנעות מדפוסים שעלולים לבטל את היכולת של המערכת לגלות משאבים חשובים מראש, לא רק הופכים את תהליך הפיתוח לפשוט יותר, אלא גם משפרים את חוויות המשתמש שיניבו תוצאות טובות יותר במדדים רבים, כולל חלק מתפקוד האפליקציה.

לסיכום, הנה כמה דברים שכדאי להסיר מהפוסט הזה:

  • סורק הטעינה מראש של הדפדפן הוא מנתח HTML משני שסורק לפני הסורק הראשי אם הוא חסום כדי לגלות מדי פעם משאבים שניתן לאחזר מוקדם יותר.
  • סורקי ההטענה מראש לא יכולים לזהות משאבים שלא נמצאים בסימני ה-Markup שסופקו על ידי השרת בבקשת הניווט הראשונית. דרכים שבהן ניתן להביס את סורק הטעינה מראש כוללות (בין היתר):
    • החדרת משאבים אל ה-DOM באמצעות JavaScript, בין אם מדובר בסקריפטים, בתמונות, בגיליונות סגנונות או בכל דבר אחר שמתאים יותר למטען הייעודי (payload) הראשוני של תגי העיצוב מהשרת.
    • טעינה מדורגת של תמונות או iframes בחלק העליון והקבוע, באמצעות פתרון של JavaScript.
    • עיבוד תגי עיצוב בלקוח שעשויים להכיל הפניות למשאבי משנה של מסמכים באמצעות JavaScript.
  • סורק הטעינה מראש סורק רק HTML. הוא לא בודק את התוכן של משאבים אחרים, במיוחד CSS, שעשויים לכלול הפניות לנכסים חשובים, כולל נכסים שעומדים בקריטריונים ל-LCP.

אם מסיבה כלשהי אי אפשר להימנע מדפוס שמשפיע לרעה על היכולת של סורק הטעינה מראש לזרז את ביצועי הטעינה, כדאי להשתמש ברמז למשאב rel=preload. אם אתם כן משתמשים ב-rel=preload, כדאי לבדוק בכלים לשיעור ה-Lab כדי לוודא שהוא מניב את האפקט הרצוי. לבסוף, אל תטעינו מראש יותר מדי משאבים, כי אם תתנו עדיפות לכל דבר, לא תהיה עדיפות לאף דבר.

משאבים

תמונה ראשית (Hero) מ-Unbounce, מאת Mohammad Rahmani