برو به محتوای اصلی
برگرد به همه‌ی یادداشت‌ها
10 دقیقه برای خوندن

چند تصمیم ساده برای اینکه پروژه‌ی Next.js از نفس نیفته

چیزهایی که در پروژه‌های واقعی Next.js به دردم خوردن؛ از Server Component و درخواست موازی تا متادیتا و خروجی standalone.

nextjsreactعملکردمعماری

بعد از چند بار بردن پروژه‌های Next.js به محیط واقعی، یک چیز برام روشن شد: بیشتر دردسرها به خاطر کمبود ابزار نیست، از تصمیم‌های اشتباه اول کار میاد. چند انتخاب ساده می‌تونه کاری کنه که پروژه ماه‌ها بعد هم سریع، قابل‌فهم و بی‌دردسر قابل‌تغییر بمونه.

از Server Component شروع می‌کنم

هر کامپوننت رو اول روی سرور می‌سازم و فقط وقتی واقعاً به تعامل مرورگر نیاز داشته باشم سراغ use client می‌رم. این‌طوری جاوااسکریپت کمتری به مرورگر می‌فرستم، دسترسی به داده امن‌تر می‌مونه و کد هم ساده‌تر می‌شه.

async function ProjectList() {
  const projects = await getProjects();

  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.title}</li>
      ))}
    </ul>
  );
}

این کامپوننت برای گرفتن داده هیچ جاوااسکریپتی به مرورگر نمی‌فرسته. اگر بعداً جست‌وجو یا فیلتر تعاملی لازم شد، فقط همون قسمت کوچیک رو Client Component می‌کنم.

درخواست‌های مستقل رو پشت سر هم نمی‌فرستم

اگر یک صفحه به چند منبع داده نیاز داشته باشه، فرستادن درخواست‌ها پشت سر هم فقط زمان تلف می‌کنه. وقتی نتیجه‌ی یکی به اون یکی وابسته نیست، از Promise.all استفاده می‌کنم:

const [posts, projects] = await Promise.all([
  getPosts(),
  getProjects(),
]);

با همین تغییر کوچک، زمان انتظار صفحه دیگه مجموع زمان دو درخواست نیست و تقریباً به اندازه‌ی کندترین درخواست می‌شه. شاید روی کاغذ فرق زیادی به نظر نرسه، ولی توی یک صفحه‌ی واقعی خیلی زود خودش رو نشون می‌ده.

پارامترهای مسیر در Next.js جدید Promise هستند

در App Router جدید، params و searchParams به شکل Promise میان و باید منتظرشون بمونیم:

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await getPost(slug);

  return <article>{post.title}</article>;
}

وقتی خود فریم‌ورک داخل پروژه نصبه، مستندات همون نسخه بهترین مرجعه. Next.js سریع تغییر می‌کنه و اگر به حافظه‌ی نسخه‌های قبلی تکیه کنیم، خیلی راحت سراغ API قدیمی می‌ریم.

متادیتا رو برای آخر کار نمی‌ذارم

عنوان و توضیح صفحه نباید آخر کار و با عجله به پروژه اضافه بشن. Metadata API اجازه می‌ده اطلاعات هر صفحه رو همان‌جایی تعریف کنیم که خود صفحه ساخته شده:

export const metadata = {
  title: "پروژه‌ها",
  description: "کارهایی که از صفر به محصول رسیده‌اند",
  openGraph: {
    images: ["/og-projects.jpg"],
  },
};

برای صفحه‌های پویا هم generateMetadata می‌تونه از همون داده‌ای استفاده کنه که خود صفحه باهاش ساخته می‌شه. این‌طوری احتمال عنوان اشتباه، لینک قدیمی یا توضیح جاافتاده کمتره.

لازم نیست همه‌ی صفحه منتظر کندترین بخش بمونه

اگر بخشی از صفحه کندتره، لازم نیست کل پوسته پشت اون منتظر بمونه:

<Suspense fallback={<ProjectSkeleton />}>
  <ProjectGrid />
</Suspense>

پوسته‌ی اصلی زودتر دیده می‌شه و بخش سنگین هر وقت آماده شد سر جاش میاد. فقط باید حواسمون باشه Suspense رو جایی بذاریم که برای کاربر معنی داشته باشه، نه اینکه دور هر کامپوننت یک مرز تصادفی بکشیم.

خروجی standalone یک نکته‌ی دردسرساز داره

output: "standalone" برای ساخت ایمیج کوچک Docker عالیه، ولی .next/static و public رو داخل خروجی نهایی نمی‌گذاره. اگر این دو پوشه کنار سرور کپی نشن، HTML پاسخ می‌ده اما فایل‌های CSS و جاوااسکریپت ۴۰۴ می‌شن و سایت بدون ظاهر و تعامل درست بالا میاد.

برای همین کپی این فایل‌ها رو بخشی از Dockerfile یا اسکریپت بعد از build می‌کنم. کاری که برای انتشار لازمه نباید به یک دستور دستی وابسته باشه که ممکنه هر لحظه فراموشش کنیم.

جمع‌بندی

معماری خوب معمولاً از حرکت‌های عجیب نمیاد. شروع با Server Component، گرفتن موازی داده، نگه داشتن متادیتا کنار صفحه، انتخاب درست مرزهای Suspense و انتشار قابل‌تکرار، تصمیم‌های ساده‌ای هستن که بعداً کلی دردسر کم می‌کنن. برای من جذابیت اصلی همین‌جاست: چند ماه بعد هنوز بتونم بدون ترس پروژه رو تغییر بدم.