כדי לשפר Core Web Vitals ב-Next.js, ספקו את התוכן החשוב מוקדם, צמצמו JavaScript בדפדפן ושמרו מקום לרכיבים שמגיעים בהמשך. Next.js מספקת אבני בניין שימושיות, אבל אסטרטגיית הרינדור והגבולות בין הרכיבים עדיין קובעים כמה עבודה נדרש המכשיר של המבקר לבצע. אבחנו את המדד האיטי לפני שינוי הגדרות המסגרת.
הדוגמאות מיועדות ל-Next.js 16 עם App Router ונבדקו מול תיעוד שהציג את הגרסה v16.4.2. הן מדגימות דפוסי מימוש, ולא תוצאות של מבחני ביצועים. יישומים המבוססים על Pages Router ויישומי React ללא Next.js זקוקים לנקודות שילוב אחרות, אף שהבעיות בדפדפן נשארות דומות.
התחילו בנתיב ובמדד
בחרו נתיב אמיתי עם בעיה שאפשר למדוד, ולא את העמוד שהכי קל לשפר. תעדו את סוג המכשיר, סוג הניווט, גרסת הפריסה ומצב המשתמש. למאמר ציבורי שנשמר במטמון, ללוח בקרה לאחר התחברות ולעמוד מוצר עם המלצות אישיות אין אותו מסלול טעינה קריטי.
LCP מתאר מתי מוצג התוכן הגלוי הגדול ביותר שעומד בתנאי המדד. תגובת שרת מאוחרת, בקשת תמונה שמתחילה באיחור או עבודה בתהליכון הראשי עלולות לדחות אותו. INP מתייחס לתגובתיות לאורך הביקור. הידרציה, פונקציית סינון ועבודת רינדור לאחר עדכון מצב עשויות להשפיע בזמנים שונים. CLS מודד תזוזות לא צפויות, גם לאחר הופעת המסך הראשון.
השתמשו בנתוני שטח כדי לזהות את קבוצת המשתמשים שנפגעה, ובהקלטת ביצועים בדפדפן כדי להסביר את העיכוב. בדיקת ניווט רגילה של Lighthouse אינה קובעת את INP בסביבת הייצור. גם לחיצה מהירה במחשב של המפתח אינה מוכיחה שהמשתמשים האמיתיים נהנים מתגובתיות טובה. המדריך לאבחון INP מסביר כיצד להפריד בין עיכובי קלט, עיבוד והצגה.
לפני העריכה, שמרו רישום בסיסי: הנתיב, רכיב LCP שנצפה, המשימות הרלוונטיות הארוכות ביותר, מקורות לתזוזות והסקריפטים שנטענים בנתיב. כך הופכים ניסיון שיפור רחב לטענה שאפשר לבדוק. אם תמונת הפתיחה מתחילה להיטען מאוחר, השינוי הראשון צריך לטפל בגילוי שלה או בתעדוף, ולא בניקוי חבילות שאינו קשור לבעיה.
התאימו את הרינדור לצרכים האמיתיים של התוכן
יצירה סטטית, רינדור לכל בקשה, הזרמה ורינדור בצד הלקוח מתארים חלקים שונים של אספקת העמוד. Server Components מתארים היכן קוד הרכיב מתבצע. רכיב שרת יכול להשתתף בנתיב סטטי או בעבודה שמבוצעת לכל בקשה. עצם הגדרתו כרכיב שרת אינה קובעת את מדיניות המטמון.
אלה השיקולים המעשיים:
| גישה | יתרון אפשרי | סיכון שצריך לבדוק |
|---|---|---|
| HTML סטטי או מהמטמון | תוכן מוקדם ללא חזרה על עבודה בשרת המקור | תוכן מיושן אם ביטול המטמון אינו תקין |
| רינדור שרת לכל בקשה | HTML עדכני או מותאם אישית | תלות בנתונים איטיים שמעכבת את התגובה |
| הזרמה | מקטעים שימושיים מגיעים לפני מקטעים איטיים | תוכן LCP עדיין ממתין מאחורי גבול רכיב |
| תוכן המרונדר בלקוח | תצוגות אינטראקטיביות מושכות נתונים לפי צורך | שרשרת סקריפטים ונתונים לפני תוכן שימושי |
אל תבחרו בעבודה לכל בקשה רק משום שהיישום משתמש ב-React. תיעוד ציבורי שמשתנה רק בפרסום יכול לעיתים להיווצר מראש. תוכן אישי עשוי לדרוש עבודה דינמית, אבל הניווט והטקסט שמסביבו אינם חייבים להמתין לכל תלות אישית.
קל במיוחד לפרש הזרמה בצורה שגויה. שליחת שלד מוקדם יותר יכולה לשפר את הרושם הראשון בלי להקדים את רכיב LCP הסופי. בדקו מתי התוכן עצמו מגיע, ולא רק את הבייט הראשון. שמרו על ממדים דומים בין מצב הטעינה לתוכן שמחליף אותו כדי שהאספקה ההדרגתית לא תיצור קפיצה.
התנהגות המטמון תלויה בגרסת Next.js וביכולות שהופעלו. בדקו את הנתיב שנוצר ואת התנהגות הפריסה במקום להעתיק הנחות מגרסה ישנה. סביבת פיתוח שבה המשאבים כבר נטענו אינה ראיה להתנהגות בקשה קרה בייצור. בדקו גם טעינה ישירה של המסמך וגם ניווט בצד הלקוח, משום שהם עשויים לבקש משאבים שונים.
שמרו על גבולות לקוח מצומצמים וברורים
ב-App Router, עמודים ופריסות הם Server Components כברירת מחדל. השתמשו ב-Client Components עבור מצב, אירועים וממשקי דפדפן. מקמו 'use client' בגבול האינטראקטיבי הקטן ביותר שמתאים למטרה, למשל מסנן או רכיב נפתח, במקום להפוך פריסה שלמה ועתירת תוכן לקוד לקוח בגלל כפתור אחד.
הגבול משפיע על גרף המודולים המיובאים. ספרייה כבדה שמיובאת ממודול הלקוח יכולה להיכלל בחבילת הדפדפן גם אם רוב העמוד נראה סטטי. בדקו מה הרכיב האינטראקטיבי מייבא והעבירו רק נתונים שהוא צריך. העברת אובייקט גדול כמאפיינים שעוברים סריאליזציה יוצרת עלויות העברה ועיבוד גם אם שינוי הנתונים נעשה בשרת.
Client Component אינו שם נרדף ל-HTML שנוצר רק בלקוח. Next.js יכולה ליצור מראש את הפלט הראשוני ולבצע הידרציה בדפדפן. ההבחנה חשובה כשעמוד נראה מוכן לפני שהפקדים מגיבים. ה-HTML הגיע, אבל JavaScript וטיפול באירועים עדיין עשויים להיות בהכנה.
השתמשו בניתוח חבילת הייצור כדי לזהות מדוע תלות מגיעה לנתיב מסוים. לאחר מכן בדקו בהקלטת ביצועים אם זמן הביצוע שלה משמעותי. גודל קובץ דחוס וזמן מעבד עונים על שאלות שונות. המדריך לצמצום זמן ביצוע JavaScript מתאים כאשר שיפור ההורדה אינו מקצר משימות ארוכות.
הגדירו תוכנית טעינה לתמונת LCP האמיתית
השתמשו ב-next/image כדי לספק ממדים וחלופות רספונסיביות, ותארו במדויק את רוחב התמונה המוצג. המאפיינים הפנימיים width ו-height שומרים על יחס הממדים; CSS קובע את הגודל בפועל. עם fill, רכיב האב זקוק למיקום ולגודל או ליחס ממדים מוגדרים. היעדר הגדרה גאומטרית יכול להפוך שיפור תמונה לבעיית פריסה.
הדוגמה ב-app/page.jsx מניחה שקיים הקובץ public/images/storefront.webp ושהוא התמונה החשובה באזור התצוגה הראשוני:
// app/page.jsx
import Image from 'next/image';
export default function Page() {
return (
<main style={{ maxWidth: 960, margin: '0 auto' }}>
<h1>A guide to faster storefronts</h1>
<Image
src="/images/storefront.webp"
alt="A storefront with a wide display window"
width={1600}
height={900}
sizes="(max-width: 960px) 100vw, 960px"
loading="eager"
fetchPriority="high"
style={{ width: '100%', height: 'auto' }}
/>
</main>
);
}הערך sizes תואם למכל בדוגמה, שתופס את הרוחב הזמין עד 960 פיקסלי CSS. שנו אותו כאשר הפריסה משתנה. כרטיס ברשת עם כמה עמודות לא צריך להצהיר על כל רוחב החלון רק משום שקל להעתיק את המחרוזת. בדקו בלוח הרשת איזו חלופה נבחרת ברוחבי מסך ובצפיפויות פיקסלים מייצגים.
ב-Next.js 16, המאפיין priority הוצא משימוש מומלץ לטובת preload. אין פירוש הדבר שכל שימוש ישן ב-priority צריך להפוך לטעינה מוקדמת ללא תנאי. התיעוד של רכיב Image מעדיף בדרך כלל טעינה מיידית או עדיפות משיכה גבוהה, ומנחה לא לשלב preload עם loading או fetchPriority.
לכן הדוגמה משלבת טעינה מיידית ורמז עדיפות, ללא preload. טעינה מוקדמת היא אפשרות נפרדת כאשר תמונה קריטית ידועה צריכה להתגלות קודם. אל תטענו מראש כמה חלופות מתחרות לנייד ולמחשב בלי להבין באיזה משאב הדפדפן ישתמש. השאירו תמונות לא חיוניות מחוץ למסך בטעינה עצלה, ובדקו שוב את רכיב LCP לאחר השינוי.
בדקו גם את שירות אספקת התמונות. מקורות מרוחקים זקוקים ל-remotePatterns מתאים, והאופטימייזר המובנה דורש סביבת ריצה תואמת. ייצוא סטטי זקוק לאסטרטגיה מפורשת, כגון loader מותאם או קבצים שעברו אופטימיזציה מראש. המדריך לאופטימיזציית תמונות מכסה את בחירת הפורמט, הממדים והאיכות שנותרת מחוץ לרכיב.
בחנו גופנים ויציבות פריסה יחד
next/font יכול להגיש קובצי גופנים מתוך פריסת היישום. עם next/font/google, דפדפן המבקר אינו צריך לבקש את הגופן מ-Google. עם next/font/local, הקבצים מגיעים מהפרויקט. אף אפשרות אינה מבטלת את הצורך לבדוק כיסוי תווים, משקלים, מדדי גופן חלופי וכמות נתונים.
פריסת הבסיס הבאה מניחה גופן לטיני בעל רישיון ב-app/fonts/body-latin.woff2 ואת רכיב המדידה שיופיע בהמשך:
// app/layout.jsx
import localFont from 'next/font/local';
import { WebVitals } from './web-vitals';
const bodyFont = localFont({
src: './fonts/body-latin.woff2',
display: 'swap',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={bodyFont.className}>
<body>
<WebVitals />
{children}
</body>
</html>
);
}זו פריסה באנגלית, ולא פתרון אוניברסלי לגופנים רב-לשוניים. עמודים בקוריאנית ובעברית זקוקים לכיסוי תווים, להגדרות שפה ולכיווניות מתאימים. קובץ לטיני בלבד לא יכול להציג כל שפה. הימנעו מהורדת כמה משפחות גופנים מלאות בכל נתיב כאשר המבקר משתמש בשפה אחת בלבד.
display: 'swap' מאפשר להציג טקסט בגופן חלופי בזמן ההמתנה. בדקו אם ההחלפה משנה שבירת שורות, רוחב ניווט או גובה כרטיסים. התאמות הגופן החלופי של המסגרת עוזרות, אך אינן מוכיחות CLS אפס לעמוד כולו. מודעות, הטמעות, באנרים שמוכנסים מאוחר ומקטעים מוזרמים עדיין צריכים מקום שמור.
השוו את העמוד בחיבור איטי לפני טעינת הגופן ואחריה. בחנו את הכותרת שקובעת את LCP, ולא רק פסקת דוגמה. אם הטקסט מסתדר מחדש, התאימו את הגופן החלופי והפריסה או צמצמו את התלות בגופן. המדריך לאופטימיזציית גופנים מרחיב על הבדיקה הזאת.
תזמנו סקריפטים בלי לבלבל דחייה עם הסרה
next/script שולט במועד הטעינה, אבל אינו מבטל את עלות הביצוע. בחרו את היקף השימוש בזהירות כמו את האסטרטגיה: רכיב שנדרש בנתיב אחד לא צריך לרוץ אוטומטית מפריסת הבסיס בכל הנתיבים.
לאסטרטגיות הנתמכות מטרות שונות:
| אסטרטגיה | מועד מיועד | נקודת זהירות |
|---|---|---|
beforeInteractive |
סקריפטים קריטיים מוקדמים בפריסת הבסיס | שמרו לקוד שכל היישום באמת צריך מוקדם |
afterInteractive |
אחרי חלק מההידרציה; ברירת המחדל | עדיין עשויה להתחרות באינטראקציות מוקדמות |
lazyOnload |
בזמן פנוי בדפדפן לאחר הטעינה | ביצוע מאוחר עדיין יכול להפריע למבקר |
worker |
טעינה ניסיונית ב-worker | אינה נתמכת עם App Router בתיעוד שנבדק |
טעינה מאוחרת יכולה להגן על מסלול הטעינה הראשון ובמקביל להאט אינטראקציה בהמשך. שחזרו את הפעולה שמתרחשת בזמן אתחול הרכיב. כאשר מתאים, טענו יכולת לא חיונית רק לבקשת המבקר וספקו משוב גלוי בזמן ההמתנה. דרישות הסכמה נפרדות מתזמון טעינה: דחיית סקריפט אנליטיקה אינה הופכת את השילוב אוטומטית לתלוי הסכמה.
בדקו את ההידרציה ואת העבודה שאחריה
הידרציה מחברת התנהגות React לפלט שנוצר מראש. שמרו על עקביות בין פלט השרת לפלט הראשוני בלקוח. מצב שקיים רק בדפדפן, ערכים לא דטרמיניסטיים או ענפי תוכן שונים עלולים ליצור אי-התאמות. הסתרת כל האזהרות אינה מתקנת את מודל אספקת התוכן.
משימת הידרציה גדולה יכולה לעכב קלט, אבל לא כל בעיית INP נגרמת מהידרציה. עדכוני מצב מאוחרים יכולים לרנדר מחדש תתי-עצים גדולים, להפעיל חישובי פריסה יקרים או לבצע חישוב סינכרוני. הקליטו את הפעולה האיטית גם אחרי שהעמוד התייצב וגם בזמן הטעינה. בחנו את הזמן מקלט המשתמש ועד הציור הבא.
כלי התזמון של React יכולים לתעדף חלק מהעדכונים, אך אינם מוציאים JavaScript סינכרוני שרירותי מהתהליכון הראשי. תחילה הסירו חישובים מיותרים, צמצמו את תת-העץ שמתעדכן והימנעו מעבודת פריסה חוזרת. בעבודת מעבד משמעותית, שקלו הפרדה מתאימה ל-worker. בדקו תקינות בכל שינוי: מסנן מהיר שמציג לרגע תשובה שגויה אינו שיפור מוצלח.
מדדו גרסת ייצור עם callback יציב
ב-App Router משתמשים ב-useReportWebVitals מתוך next/web-vitals ברכיב לקוח קטן. שמירה על הפניה יציבה ל-callback מונעת דיווחים כפולים הנוצרים מהחלפת הפונקציה ברינדור חוזר. הדוגמה כותבת תצפיות למסוף הדפדפן ואינה משדרת נתוני משתמש:
// app/web-vitals.jsx
'use client';
import { useReportWebVitals } from 'next/web-vitals';
function reportMetric({ id, name, value, rating }) {
console.info('web-vital', { id, name, value, rating });
}
export function WebVitals() {
useReportWebVitals(reportMetric);
return null;
}הפריסה הקודמת טוענת את הרכיב פעם אחת. בדקו גרסת ייצור, בצעו פעולות בעמוד ושנו את הנראות שלו כאשר בוחנים מדדים המדווחים בשלב מאוחר יותר. אין לצפות שכל המדדים יופיעו מיד לאחר הטעינה. ביקור ללא אינטראקציה מתאימה עשוי להסתיים ללא ערך INP; אין להפוך ערכים חסרים לאפסים.
לאיסוף נתוני משתמשים, החליפו את הרישום במערך טלמטריה שתוכנן במפורש. הגדירו דגימה, הסכמה כשנדרשת, צמצום נתונים ומניעת כפילויות. שמרו על מזהה המדד ושמו וטפלו בעדכונים לפי גרסת הספרייה. LCP ו-INP נמדדים בזמן; CLS חסר יחידות. אין למצע יחידות שונות או לספור כמה עדכונים כאילו היו ביקורים עצמאיים.
מעברי נתיב דורשים זהירות נוספת. מדידת ניווטים רכים תלויה בתמיכת הדפדפן והספרייה. אל תניחו ששינוי כתובת בצד הלקוח מאפס כל Core Web Vital תקני, ואל תשייכו מדד מאוחר לכתובת שבמקרה מוצגת כשהוא מגיע. תעדו את כללי הניווט של הגרסה שנפרסה בפועל.
פרסמו שינוי אחד עם בדיקת קבלה ברורה
בחרו את צוואר הבקבוק, בצעו את השינוי הרלוונטי הקטן ביותר וחזרו על אותו מסלול בתנאים דומים. בתיקון תמונה, בדקו את מועד הבקשה, החלופה שנבחרה ו-LCP. בשינוי גבול לקוח, בדקו הכללה בחבילה, זמן תגובה ותקינות. בתיקון פריסה, בדקו טעינה ועדכוני תוכן מאוחרים.
השתמשו בבודק Core Web Vitals כדי לפרש ערכים שנמדדו, ולאחר מכן עקבו אחר הנתיבים וקבוצות המכשירים שנפגעו בסביבת הייצור. שמרו את מדריכי האופטימיזציה לפלטפורמות לצד רשימת בדיקות הפרסום. הצעד הבא הוא שיפור מתועד שקשור לגורם אחד, ואחריו בדיקת נסיגות בשאר חוויית השימוש.