פיצול קוד עם ייבוא דינמי ב-Next.js

איך להאיץ את אפליקציית Next.js בעזרת פיצול קוד ואסטרטגיות טעינה חכמות.

מיליקה מיהאג'ליג'ה
מיליקה מיהאג'ליג'ה

מה נלמד?

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

פיצול קוד מבוסס-מסלול ומבוסס-רכיבים

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

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

ב-Next.js יש תמיכה ב-import() דינמי, שמאפשר לייבא מודולים של JavaScript (כולל רכיבי React) באופן דינמי ולטעון כל ייבוא כמקטע נפרד. כך אפשר לפצל את הקוד ברמת הרכיב ולשלוט בטעינת המשאבים, כך שהמשתמשים יורידו את הקוד שהם צריכים רק עבור החלק באתר שהם צופים. ב-Next.js, הרכיבים האלה מעובדים בצד השרת (SSR) כברירת מחדל.

ייבוא דינמי בפעולה

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

בגרסה הראשונה של האפליקציה, הכלבלב גר ב-components/Puppy.js. כדי להציג את הגור בדף, האפליקציה מייבאת את הרכיב Puppy ב-index.js עם הצהרת ייבוא סטטית:

import Puppy from "../components/Puppy";

כדי לראות איך האפליקציה Next.js אוספת את האפליקציה, אתם יכולים לבדוק את מעקב הרשת בכלי הפיתוח:

  1. כדי לראות תצוגה מקדימה של האתר, מקישים על View App ואז על Fullscreen מסך מלא.

  2. לוחצים על 'Control+Shift+J' (או 'Command+Option+J' ב-Mac) כדי לפתוח את כלי הפיתוח.

  3. לוחצים על הכרטיסייה רשתות.

  4. מסמנים את התיבה Disable cache (השבתת המטמון).

  5. לטעון מחדש את הדף.

כשטוענים את הדף, כל הקוד הדרוש, כולל הרכיב Puppy.js, נמצא בחבילה index.js:

כרטיסיית DevTools Network שמוצגים בה שישה קובצי JavaScript: index.js, app.js, webpack.js, main.js, 0.js וקובץ dll (ספריית קישורים דינמיים).

כשלוחצים על הלחצן Click me (לוחצים עליי), רק הבקשה של ה-Puppy JPEG מתווספת לכרטיסייה Network:

אחרי הלחיצה על הלחצן, מוצגים בכרטיסייה DevTools Network (רשת) מופיעים אותם שישה קובצי JavaScript ותמונה אחת.

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

עכשיו בדקו גרסה שנייה של האפליקציה, שבה הייבוא הסטטי יוחלף בייבוא דינמי. השדה Next.js כולל את השדה next/dynamic, שמאפשר להשתמש בייבוא דינמי לכל הרכיבים ב-Next:

import Puppy from "../components/Puppy";
import dynamic from "next/dynamic";

// ...

const Puppy = dynamic(import("../components/Puppy"));

פועלים לפי השלבים שבדוגמה הראשונה כדי לבדוק את מעקב הרשת.

בטעינה הראשונה של האפליקציה, ירדה רק index.js. הפעם קטן יותר ב-0.5KB (הוא ירד מ-37.9KB ל-37.4KB) כי הוא לא כולל את הקוד של הרכיב Puppy:

אותם שישה קובצי JavaScript מוצגים ברשת DevTools Network, מלבד index.js קטן עכשיו ב-0.5KB.

הרכיב Puppy נמצא עכשיו בקבוצת נתונים נפרדת, 1.js, שנטען רק כשלוחצים על הלחצן:

בכרטיסייה DevTools Network (רשת) אחרי הלחיצה על הלחצן, מוצגים קובץ ה-1.js הנוסף והתמונה שנוספה בסוף רשימת הקבצים.

באפליקציות בעולם האמיתי, הרכיבים בדרך כלל גדולים יותר, וטעינה מדורגת שלהם עלולה לקצר את המטען הייעודי (payload) הראשוני של JavaScript במאות קילובייט (KB).

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

כשאתם טוענים משאבים באופן הדרגתי, מומלץ לספק סימן ויזואלי של מצב הטעינה למקרה שיהיו עיכובים. ב-Next.js אפשר לעשות זאת באמצעות הוספת ארגומנט נוסף לפונקציה dynamic():

const Puppy = dynamic(() => import("../components/Puppy"), {
  loading: () => <p>Loading...</p>
});

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

  1. כדי לראות תצוגה מקדימה של האתר, מקישים על View App ואז על Fullscreen מסך מלא.

  2. לוחצים על 'Control+Shift+J' (או 'Command+Option+J' ב-Mac) כדי לפתוח את כלי הפיתוח.

  3. לוחצים על הכרטיסייה רשתות.

  4. מסמנים את התיבה Disable cache (השבתת המטמון).

  5. ברשימה הנפתחת Throttling, בוחרים באפשרות Fast 3G.

  6. לוחצים על הלחצן אני רוצה ללחוץ.

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

מסך כהה עם הטקסט

ייבוא דינמי ללא SSR

אם אתם צריכים לעבד רכיב רק בצד הלקוח (לדוגמה, ווידג'ט של צ'אט), תוכלו לעשות זאת על ידי הגדרת האפשרות ssr ל-false:

const Puppy = dynamic(() => import("../components/Puppy"), {
  ssr: false,
});

סיכום

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