بازگشت به وبلاگ

سریع‌کردن یه اپ Next.js: تکنیک‌هایی که واقعاً اهمیت دارن

۲ مرداد ۱۴۰۵3 دقیقه مطالعه
سریع‌کردن یه اپ Next.js: تکنیک‌هایی که واقعاً اهمیت دارن

بیشتر کار پرفورمنس توی Next.js زرنگ‌بازی نیست — یه چک‌لیسته. این‌جا اونی که واقعاً اثر داره رو می‌بینیم، تقریباً به ترتیب نسبت زحمت به بازده.

۱. عکس‌ها و فونت‌ها — بردهای تضمینی

  • next/image خودکار AVIF/WebP سرو می‌کنه و تا وقتی width/height (یا fill + والد سایزدار) تنظیم شده باشه باعث layout shift نمی‌شه — همین به‌تنهایی معمولاً CLS رو درست می‌کنه.

  • next/font فایل فونت رو self-host می‌کنه و موقع build تزریقش می‌کنه، پس نه درخواست render-blocking به یه CDN فونت هست، نه layout shift ناشی از swap شدن فونت.

۲. پیش‌فرض استاتیک، انتخابی داینامیک

هرجا داده request-specific نیست با ISR رندر استاتیک کن، و فقط جایی سراغ رندر کاملاً داینامیک برو که صفحه واقعاً به داده‌ی هر-درخواست نیاز داره (وضعیت auth، شخصی‌سازی). یه پست بلاگ یا صفحه‌ی پروژه تقریباً هیچ‌وقت نیازی به داینامیک بودن نداره.

export const revalidate = 3600; // حداکثر هر یه ساعت دوباره تولید شه

export async function generateStaticParams() {
  const posts = await postRepository.findAllPublished();
  return posts.map((post) => ({ slug: post.slug }));
}

۳. داده‌ی کند و غیرحیاتی رو با Suspense استریم کن

هرچی که کند و برای اولین paint لازم نیست رو توی Suspense boundary خودش با یه fallback سبک بپیچون، تا بقیه‌ی صفحه فوری تحویل داده بشه به‌جای اینکه منتظر کندترین کوئری بمونه.

<Suspense fallback={<CommentsSkeleton />}>
  <Comments postId={post.id} />
</Suspense>

۴. موازی fetch کن، نه پشت‌سرهم

// کند — fetch دوم بدون هیچ دلیلی منتظر تموم‌شدن اولی می‌مونه.
const project = await getProject(slug);
const related = await getRelatedProjects(project.tags);

// سریع — هر دو درخواست هم‌زمان شلیک می‌شن.
const [project, allProjects] = await Promise.all([getProject(slug), getAllProjects()]);
const related = allProjects.filter((p) => p.tags.some((t) => project.tags.includes(t)));

۵. باندل کلاینت رو کوچیک نگه دار

  • "use client" رو تا جای ممکن پایین درخت هل بده — یه صفحه می‌تونه ۹۵٪ Server Component باشه با فقط یه برگ تعاملی کوچیک؛ کل صفحه یا layout رو client نکن فقط چون یه دکمه به onClick نیاز داره.

  • کامپوننت‌های کلاینت سنگین زیر fold (چارت‌ها، ادیتورهای متن غنی، مودال‌ها) رو با next/dynamic لیزی‌لود کن تا جاوااسکریپتشون توی باندل اولیه نباشه.

  • آیکون‌ها و utilityها رو با نام از کتابخونه‌هایی که پشتیبانی می‌کنن import کن، و برای اونایی که خودشون تمیز tree-shake نمی‌شن، experimental.optimizePackageImports رو توی next.config تنظیم کن.

۶. خواندن‌ها رو کش و دی‌داپلیکیت کن

fetch() توی App Router خودکار به‌ازای هر درخواست memoize می‌شه. برای هرچی که از fetch رد نمی‌شه — یه فراخوانی ORM، یه کوئری مستقیم دیتابیس — با cache() از React بپیچونش تا همون دی‌داپلیکیت به‌ازای هر درخواست رو بگیری، پس پنج تا Server Component که یه پروژه‌ی یکسان می‌خوان، فقط یه‌بار می‌رن سراغ دیتابیس.

import { cache } from "react";

export const getProject = cache(async (slug: string) => {
  return prisma.project.findUnique({ where: { slug } });
});

۷. قبل از بهینه‌سازی، اندازه بگیر

سایز روت‌ها رو توی خروجی next build بخون، وقتی چیزی سنگین حس شد @next/bundle-analyzer رو اجرا کن، و Core Web Vitals واقعی (LCP، INP، CLS) رو چک کن نه اینکه حدس بزنی. بهینه‌سازی یه روتی که از قبل سریعه، وقتی رو هدر می‌ده که یه روت واقعاً کند بهش نیاز داشت.