מהו סורק הטעינה מראש בדפדפן, איך הוא משפר את הביצועים ואיך אפשר להימנע מהפרעה לפעולה שלו.
היבט אחד של אופטימיזציה של מהירות הדף שמתעלמים ממנו הוא ידע מסוים על הפנימיות של הדפדפן. דפדפנים מבצעים אופטימיזציות מסוימות כדי לשפר את הביצועים בדרכים שאנחנו כמפתחים לא יכולים – אבל רק אם האופטימיזציות האלה לא נכשלות בטעות.
אחת מהאופטימיזציות של הדפדפן הפנימי שכדאי להכיר היא סורק הטעינה מראש של הדפדפן. במאמר הזה נסביר איך פועל סורק הטעינה מראש, וחשוב מכך, איך אפשר למנוע הפרעה לפעולה שלו.
מה זה סורק טרום-טעינה?
לכל דפדפן יש מנתח HTML ראשי שמייצר טוקנים מתגי עיצוב גולמיים ומעבד אותם למודל אובייקט. התהליך הזה נמשך עד שהניתוח מושהה כשהוא מוצא משאב שחוסם את העיבוד, כמו גיליון סגנונות שנטען באמצעות אלמנט <link>, או סקריפט שנטען באמצעות אלמנט <script> ללא מאפיין async או defer.
<link> של קובץ CSS חיצוני, הוא מונע מהדפדפן לנתח את שאר המסמך – או אפילו לעבד חלק ממנו – עד שקובץ ה-CSS יורד וינותח.
במקרה של קובצי CSS, העיבוד נחסם כדי למנוע הצגה של תוכן לא מעוצב (FOUC), שמתרחשת כשגרסה לא מעוצבת של דף מוצגת לזמן קצר לפני שהסגנונות מוחלים עליה.
הדפדפן גם חוסם את הניתוח והעיבוד של הדף כשהוא נתקל ברכיבי <script> בלי מאפיין defer או async.
הסיבה לכך היא שהדפדפן לא יכול לדעת בוודאות אם סקריפט נתון ישנה את ה-DOM בזמן שמנתח ה-HTML הראשי עדיין מבצע את העבודה שלו. לכן, נהוג לטעון את ה-JavaScript בסוף המסמך כדי שההשפעות של חסימת הניתוח והרינדור יהיו מינוריות.
אלה סיבות טובות לכך שהדפדפן צריך לחסום את הניתוח והעיבוד. עם זאת, לא מומלץ לחסום את אחד מהשלבים החשובים האלה, כי הם עלולים לעכב את הצגת התוכן על ידי עיכוב האיתור של משאבים חשובים אחרים. למזלנו, הדפדפנים עושים כמיטב יכולתם כדי לצמצם את הבעיות האלה באמצעות מנתח HTML משני שנקרא סורק טרום-טעינה.
<body>, אבל סורק הטעינה מראש יכול להסתכל קדימה בתגי העיצוב הגולמיים כדי למצוא את משאב התמונה ולהתחיל לטעון אותו לפני שחסימת מנתח ה-HTML הראשי מוסרת.
התפקיד של סורק טעינה מראש הוא ספקולטיבי, כלומר הוא בודק תגי עיצוב גולמיים כדי למצוא משאבים לאחזור אופטימלי לפני שמנתח ה-HTML הראשי יגלה אותם.
איך יודעים מתי סורק הטעינה מראש פועל
סורק הטעינה מראש קיים בגלל חסימה של עיבוד וניתוח. אם שתי בעיות הביצועים האלה לא היו קיימות, סורק הטעינה מראש לא היה שימושי במיוחד. כדי להבין אם דף אינטרנט נהנה מהסורק של הטעינה מראש, צריך להבין את תופעות החסימה האלה. כדי לעשות את זה, אפשר להוסיף השהיה מלאכותית לבקשות כדי לגלות איפה סורק הטעינה מראש פועל.
לדוגמה, הדף הזה מכיל טקסט ותמונות בסיסיים עם גיליון סגנונות. מכיוון שקובצי CSS חוסמים גם את הרינדור וגם את הניתוח, אתם יוצרים עיכוב מלאכותי של שתי שניות בגיליון הסגנונות באמצעות שירות פרוקסי. העיכוב הזה מאפשר לראות בקלות רבה יותר בטבלת הנתונים של הרשת איפה סורק הטעינה מראש פועל.
כפי שאפשר לראות בתרשים, סורק הטעינה מראש מגלה את רכיב <img> גם בזמן שהעיבוד והניתוח של המסמך חסומים. בלי האופטימיזציה הזו, הדפדפן לא יכול לאחזר דברים באופן אופטימלי במהלך תקופת החסימה, ויותר בקשות למשאבים יהיו עוקבות ולא מקבילות.
אחרי שסיימנו עם הדוגמה הפשוטה, נבחן כמה דפוסים מהעולם האמיתי שבהם אפשר לעקוף את סורק הטעינה מראש, ומה אפשר לעשות כדי לתקן אותם.
סקריפטים מוחדרים async
נניח שיש לכם קוד HTML ב-<head> שכולל קוד JavaScript מוטבע כמו זה:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
סקריפטים שמוזרקים הם async כברירת מחדל, ולכן כשמזריקים את הסקריפט הזה, הוא יתנהג כאילו המאפיין async הוחל עליו. המשמעות היא שהסקריפט יפעל בהקדם האפשרי ולא יחסום את הרינדור. נשמע אופטימלי, נכון? עם זאת, אם מניחים שהסגנון המוטבע <script> מופיע אחרי רכיב <link> שמעמיס קובץ CSS חיצוני, התוצאה תהיה לא אופטימלית:
async מוזרק. סורק הטעינה מראש לא יכול לגלות את הסקריפט במהלך השלב של חסימת העיבוד, כי הוא מוזרק בצד הלקוח.
בואו נפרט מה קרה כאן:
- בשנייה 0, מתבצעת בקשה למסמך הראשי.
- אחרי 1.4 שניות, הבייט הראשון של בקשת הניווט מגיע.
- אחרי 2.0 שניות, מתבצעת בקשה ל-CSS ולתמונה.
- מכיוון שהניתוח חסום בטעינת גיליון הסגנונות וקוד ה-JavaScript המוטבע שמזריק את סקריפט
asyncמגיע אחרי גיליון הסגנונות הזה אחרי 2.6 שניות, הפונקציונליות שהסקריפט מספק לא זמינה ברגע שהיא יכולה להיות זמינה.
זה לא אופטימלי כי הבקשה לסקריפט מתרחשת רק אחרי שההורדה של גיליון הסגנונות מסתיימת. הפעולה הזו מעכבת את הפעלת הסקריפט מוקדם ככל האפשר. לעומת זאת, מכיוון שרכיב <img> ניתן לגילוי בסימון שסופק על ידי השרת, סורק הטעינה מראש מגלה אותו.
אז מה קורה אם משתמשים בתג <script> רגיל עם המאפיין async במקום להוסיף את הסקריפט ל-DOM?
<script src="/yall.min.js" async></script>
זו התוצאה:
async <script> יחיד. סורק הטעינה מראש מגלה את הסקריפט במהלך השלב של חסימת העיבוד, וטוען אותו במקביל ל-CSS.
יכול להיות שיהיה פיתוי להציע שניתן לפתור את הבעיות האלה באמצעות rel=preload. השיטה הזו תעבוד, אבל יכול להיות שיהיו לה תופעות לוואי. למה להשתמש ב-rel=preload כדי לפתור בעיה שאפשר למנוע אם לא מזריקים רכיב <script> ל-DOM?
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.
בהתאם לגודל התמונה – שעשוי להיות תלוי בגודל אזור התצוגה – יכול להיות שהיא תהיה רכיב מועמד להצגת חלק התוכן הגדול ביותר (LCP). כשסורק הטעינה מראש לא יכול לאחזר את משאב התמונה מראש – יכול להיות שבמהלך הנקודה שבה גיליונות הסגנונות של הדף חוסמים את הרינדור – ה-LCP נפגע.
הפתרון הוא לשנות את תגי העיצוב של התמונה:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
זהו הדפוס האופטימלי לתמונות שנמצאות באזור התצוגה במהלך ההפעלה, כי סורק הטעינה מראש יגלה ויאחזר את משאב התמונה מהר יותר.
התוצאה בדוגמה הפשוטה הזו היא שיפור של 100 אלפיות השנייה ב-LCP בחיבור איטי. יכול להיות שזה לא נראה כמו שיפור משמעותי, אבל זה כן שיפור משמעותי כשחושבים על כך שהפתרון הוא תיקון מהיר של תגי העיצוב, ורוב דפי האינטרנט מורכבים יותר מקבוצת הדוגמאות הזו. המשמעות היא שמועמדים ל-LCP עשויים להתחרות על רוחב הפס עם הרבה משאבים אחרים, ולכן אופטימיזציות כאלה הופכות לחשובות יותר ויותר.
CSS – תמונות רקע
חשוב לזכור שהסורק לטעינה מראש של הדפדפן סורק תגי עיצוב. הוא לא סורק סוגים אחרים של משאבים, כמו CSS, שעשויים לכלול אחזור של תמונות שההפניה אליהן מופיעה במאפיין background-image.
בדומה ל-HTML, הדפדפנים מעבדים את ה-CSS למודל אובייקטים משלו, שנקרא CSSOM. אם מתגלים משאבים חיצוניים בזמן בניית ה-CSSOM, המשאבים האלה מבוקשים בזמן הגילוי, ולא על ידי סורק הטעינה מראש.
נניח שהמועמד ל-LCP בדף הוא רכיב עם מאפיין CSS background-image. אלה הפעולות שמתבצעות בזמן הטעינה של מקורות המידע:
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 קטן, אבל הוא עוזר לדפדפן לגלות את התמונה מוקדם יותר ממה שהיה קורה אחרת:
background-image (שורה 3). הרמז rel=preload עוזר לדפדפן לגלות את התמונה בערך 250 מילישניות מוקדם יותר מאשר בלי הרמז.
בעזרת rel=preloadההצעהrel=preload, המועמד ל-LCP מתגלה מוקדם יותר, וכך זמן ה-LCP מתקצר. ההצעה הזו עוזרת לפתור את הבעיה, אבל יכול להיות שהאפשרות הטובה יותר היא לבדוק אם מועמד ה-LCP של התמונה חייב להיטען מ-CSS. תג <img> מאפשר לכם לשלוט יותר בטעינה של תמונה שמתאימה לאזור התצוגה, וגם מאפשר לסורק הטעינה מראש לגלות אותה.
הוספת יותר מדי משאבים בשורת קוד
הטמעה בשורה היא שיטה שבה משאב מוצב בתוך ה-HTML. אפשר להטמיע גיליונות סגנונות באלמנטים <style>, סקריפטים באלמנטים <script> וכמעט כל משאב אחר באמצעות קידוד base64.
הוספת משאבים בתוך הקוד יכולה להיות מהירה יותר מהורדה שלהם, כי לא מונפקת בקשה נפרדת למשאב. הוא נמצא ישירות במסמך ונטען באופן מיידי. עם זאת, יש כמה חסרונות משמעותיים:
- אם אתם לא שומרים את ה-HTML במטמון – ואי אפשר לעשות את זה אם תגובת ה-HTML היא דינמית – המשאבים שמוטמעים בשורה אף פעם לא נשמרים במטמון. הדבר משפיע על הביצועים כי אי אפשר לעשות שימוש חוזר במשאבים שמוטמעים בתוך הקוד.
- גם אם אפשר לשמור HTML במטמון, משאבים מוטבעים לא משותפים בין מסמכים. כתוצאה מכך, יעילות השמירה במטמון נמוכה יותר בהשוואה לקבצים חיצוניים שאפשר לשמור במטמון ולעשות בהם שימוש חוזר בכל המקור.
- אם משתמשים ביותר מדי תוכן מוטבע, הסורק של טעינה מראש מתעכב בגילוי משאבים בהמשך המסמך, כי הורדת התוכן המוטבע הנוסף אורכת זמן רב יותר.
ניקח לדוגמה את הדף הזה. בתנאים מסוימים, המועמד ל-LCP הוא התמונה בחלק העליון של הדף, ורכיב ה-CSS נמצא בקובץ נפרד שנטען על ידי רכיב <link>. בנוסף, הדף משתמש בארבעה גופנים לאינטרנט, שמתבקשים כקבצים נפרדים ממשאב ה-CSS.
<img>, אבל סורק הטעינה מראש מגלה אותה כי ה-CSS והגופנים שנדרשים לטעינת הדף נמצאים במשאבים נפרדים, ולכן אין עיכוב בביצוע העבודה של סורק הטעינה מראש.
מה קורה עכשיו אם ה-CSS וגם כל הגופנים מוטמעים כמשאבי base64?
<img>, אבל הטמעת ה-CSS וארבעת משאבי הגופן שלו ב-`` מעכבת את סורק הטעינה מראש, כך שהוא לא יכול לגלות את התמונה עד שהמשאבים האלה יורדו במלואם.
ההשפעה של הטמעת ה-CSS בדף גורמת לתוצאות שליליות ב-LCP בדוגמה הזו – ובביצועים באופן כללי. בגרסה של הדף שבה לא מוטמעים רכיבים, התמונה של ה-LCP מוצגת תוך 3.5 שניות בערך. בדף שבו כל התוכן מוטבע, התמונה של ה-LCP מוצגת רק אחרי קצת יותר מ-7 שניות.
יש כאן יותר ממה שרואים, ולא מדובר רק בסורק הטעינה מראש. הטמעה של גופנים לא מומלצת כי base64 הוא פורמט לא יעיל למשאבים בינאריים. גורם נוסף שמשפיע הוא שמשאבי גופנים חיצוניים לא מורדים אלא אם הם נחשבים חיוניים על ידי CSSOM. כשגופנים מוטבעים כ-base64, הם מורדים גם אם הם לא נדרשים לדף הנוכחי.
האם טעינה מראש יכולה לשפר את המצב כאן? ודאי. אפשר לבצע טעינה מראש של תמונת ה-LCP כדי לקצר את זמן ה-LCP, אבל הוספה של משאבים מוטבעים ל-HTML שייתכן שלא ניתן לשמור במטמון עלולה להוביל להשלכות שליליות אחרות על הביצועים. גם הצגת תוכן ראשוני (FCP) מושפעת מהדפוס הזה. בגרסה של הדף שבה לא מוטמעים רכיבים, משך הזמן עד ל-FCP הוא בערך 2.7 שניות. בגרסה שבה כל הרכיבים מוטמעים, משך הזמן עד ל-FCP הוא בערך 5.8 שניות.
צריך להיזהר מאוד כשמכניסים דברים ל-HTML, במיוחד משאבים בקידוד base64. באופן כללי, לא מומלץ להשתמש בשיטה הזו, אלא אם מדובר במשאבים קטנים מאוד. מומלץ להשתמש בהטמעה מוטמעת כמה שפחות, כי הטמעה מוטמעת של יותר מדי קוד עלולה לגרום לבעיות.
רינדור של תגי עיצוב באמצעות JavaScript בצד הלקוח
אין ספק ש-JavaScript משפיע על מהירות הדף. המפתחים לא רק מסתמכים על הפלאש כדי לספק אינטראקטיביות, אלא גם נוטים להסתמך עליו כדי להציג את התוכן עצמו. השינוי הזה מוביל לשיפורים מסוימים בחוויית המפתחים, אבל לא תמיד שיפורים בחוויית המפתחים מובילים לשיפורים בחוויית המשתמשים.
דפוס אחד שיכול להטעות את סורק הטעינה מראש הוא עיבוד תגי עיצוב באמצעות JavaScript בצד הלקוח:
כשמטענים ייעודיים של תגי עיצוב נכללים ב-JavaScript בדפדפן ומוצגים על ידו באופן מלא, כל המשאבים בתגי העיצוב האלה לא גלויים למעשה לסורק הטעינה מראש. העיכוב הזה גורם לכך שייקח יותר זמן למצוא משאבים חשובים, וזה כמובן משפיע על LCP. בדוגמאות האלה, הבקשה לתמונת ה-LCP מתעכבת באופן משמעותי בהשוואה לחוויה המקבילה שמוצגת בצד השרת ולא דורשת JavaScript כדי להופיע.
זה קצת חורג מהנושא של המאמר הזה, אבל ההשפעות של עיבוד תגי עיצוב בצד הלקוח הן הרבה מעבר לביטול הסורק של הטעינה מראש. לדוגמה, הוספת JavaScript כדי להפעיל חוויה שלא דורשת אותה מוסיפה זמן עיבוד מיותר שיכול להשפיע על מהירות התגובה לאינטראקציה באתר (INP). הצגה של כמויות גדולות מאוד של תגי עיצוב בלקוח עשויה ליצור משימות ארוכות בהשוואה לאותה כמות של תגי עיצוב שנשלחת מהשרת. הסיבה לכך – מלבד העיבוד הנוסף ש-JavaScript כרוך בו – היא שהדפדפנים מעבירים את ה-markup מהשרת ומחלקים את הרינדור כך שבדרך כלל הוא מוגבל למשימות ארוכות. לעומת זאת, סימון שמעובד בצד הלקוח מטופל כמשימה אחת גדולה, מה שעשוי להשפיע על מדד ה-INP של הדף.
הפתרון לתרחיש הזה תלוי בתשובה לשאלה הבאה: האם יש סיבה לכך שהשרת לא יכול לספק את התגיות של הדף, במקום שהן יעברו עיבוד בצד הלקוח? אם התשובה היא 'לא', כדאי לשקול שימוש ב-SSR (רינדור בצד השרת) או בסימון שנוצר באופן סטטי, אם אפשר, כי זה יעזור לסורק הטעינה מראש לגלות ולאחזר משאבים חשובים מראש.
אם הדף כן צריך JavaScript כדי לצרף פונקציונליות לחלקים מסוימים בסימון הדף, עדיין אפשר לעשות זאת באמצעות SSR, עם JavaScript רגיל או hydration כדי ליהנות מהיתרונות של שתי הגישות.
איך עוזרים לסורק הטעינה מראש לעזור לכם
סורק הטעינה מראש הוא אופטימיזציה יעילה מאוד לדפדפן, שעוזרת לטעון דפים מהר יותר במהלך ההפעלה. אם תמנעו משימוש בדפוסים שפוגעים ביכולת של Googlebot לגלות מראש משאבים חשובים, לא רק שתפשטו את תהליך הפיתוח, אלא גם תיצרו חוויות משתמש טובות יותר שיניבו תוצאות טובות יותר במדדים רבים, כולל חלק מהמדדים החיוניים לאתרים.
לסיכום, הנה הדברים העיקריים שכדאי לזכור מהפוסט הזה:
- סורק הטעינה מראש של הדפדפן הוא מנתח משני של HTML שסורק לפני המנתח הראשי אם הוא חסום, כדי לגלות משאבים שאפשר לאחזר מוקדם יותר.
- אי אפשר לגלות משאבים שלא מופיעים בתגי העיצוב שהשרת מספק בבקשת הניווט הראשונית באמצעות סורק הטעינה מראש. הדרכים לעקוף את הסורק של טעינה מראש עשויות לכלול (אבל לא רק):
- הזרקת משאבים ל-DOM באמצעות JavaScript, בין אם מדובר בסקריפטים, בתמונות, בגיליונות סגנונות או בכל דבר אחר שעדיף שיופיע במטען הייעודי (payload) של סימון ראשוני מהשרת.
- טעינה מדורגת של תמונות או של iframe בחלק העליון של הדף באמצעות פתרון JavaScript.
- עיבוד של תגי עיצוב בלקוח שעשויים להכיל הפניות למשאבי משנה של המסמך באמצעות JavaScript.
- הסורק של הטעינה מראש סורק רק HTML. הוא לא בודק את התוכן של משאבים אחרים – במיוחד CSS – שעשויים לכלול הפניות לנכסים חשובים, כולל מועמדים ל-LCP.
אם מסיבה כלשהי אי אפשר להימנע מדפוס שמשפיע לרעה על היכולת של סורק הטעינה מראש לשפר את ביצועי הטעינה, כדאי להשתמש ברמז המשאב rel=preload. אם כן משתמשים ב-rel=preload, כדאי לבדוק בכלי מעבדה כדי לוודא שהאפקט הרצוי מתקבל. בנוסף, אל תטענו מראש יותר מדי משאבים, כי אם תתנו עדיפות לכל דבר, לא תהיה עדיפות לאף דבר.
משאבים
- סקריפטים אסינכרוניים שהוחדרו לסקריפט נחשבים למזיקים
- איך טוען מראש הדפדפן גורם לטעינת הדפים מהר יותר
- טעינה מראש של נכסים קריטיים כדי לשפר את מהירות הטעינה
- יצירת חיבורים לרשת מוקדם ככל האפשר כדי לשפר את מהירות הדף
- אופטימיזציה של Largest Contentful Paint (LCP)
תמונה ראשית (Hero) מ-Unsplash, מאת Mohammad Rahmani .