ahmad hasanzadeh branding logoahmad hasanzadeh branding logo
    خانهپروژه هامقالاتدرباره منتکنولوژی هاارتباط با من
ahmad hasanzadeh branding logoahmad hasanzadeh branding logo

ممنون که سر زدی ッ

© ۱۴۰۵

احمد حسن زاده. تمامی حقوق محفوظ است.

مدیریت خطای 404 در Next.js App Router (راهنمای جامع)

مدیریت خطای 404 در Next.js App Router (راهنمای جامع)

آموزش کامل نحوه ساخت و مدیریت صفحات ۴۰۴ در Next.js به همراه نکات SEO و تجربه کاربری (UX).

مدیریت خطای 404 در Next.js 16 (App Router): راهنمای کامل 2026

Next.js 16 دو قابلیت متفاوت برای مدیریت 404 ارائه می‌دهد: not-found.js و قابلیت تازه‌معرفی‌شده global-not-found.js. این دو، دو مسئلهٔ متفاوت را حل می‌کنند. بیشتر آموزش‌ها هنوز 404 را به‌عنوان یک فایل ساده می‌بینند که هم قدیمی است و هم در برخی ساختارها اصلاً درست کار نمی‌کند. در این مقاله هر دو روش، نحوهٔ صحیح فراخوانی آن‌ها در مسیرهای داینامیک، و نکتهٔ SEO‌ ای که واقعاً تعیین می‌کند گوگل صفحهٔ شما را 404 واقعی بداند یا نه، بررسی می‌شود.

۱. مدیریت 404 در سطح مسیر: app/not-found.tsx

این فایل زمانی رندر می‌شود که notFound() داخل یک route segment فراخوانی شود، یا وقتی URLای با هیچ‌کدام از segmentهای زیرمجموعهٔ آن بخش از DOM تطابق نداشته باشد.

app/not-found.tsxtsx
import Link from "next/link";
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  title: "404 — صفحه پیدا نشد",
  robots: { index: false, follow: true },
};
 
export default function NotFound() {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center gap-3 px-4 text-center">
      <h1 className="text-3xl font-bold">404 — صفحه پیدا نشد</h1>
      <p className="max-w-md text-muted-foreground">
        صفحه‌ای که دنبالش هستید وجود ندارد یا جابه‌جا شده است.
      </p>
      <Link href="/" className="mt-4 underline">
        بازگشت به صفحهٔ اصلی
      </Link>
    </div>
  );
}

دو نکتهٔ مهم:

  • not-found.tsx به‌طور پیش‌فرض یک Server Component است — همین‌طور نگهش دارید، مگر واقعاً نیاز به تعامل کاربر داشته باشید. دلیلی ندارد برای یک صفحهٔ خطای استاتیک، JS سمت کلاینت ارسال کنید.
  • حتماً robots: { index: false } را در متادیتای این صفحه تنظیم کنید. صفحهٔ 404 هیچ جایگاهی در ایندکس گوگل ندارد و فراموش کردن این مورد یکی از رایج‌ترین نشتی‌های SEO در سایت‌های App Router است.

۲. مدیریت 404 در سطح کل اپلیکیشن: app/global-not-found.tsx (جدید در Next.js 16)

not-found.tsx فقط URLهای تطابق‌نیافته‌ای را می‌گیرد که درون همان درخت layout قرار دارند که خودش در آن است. اگر اپلیکیشن شما چند root layout دارد (مثلاً app/(admin)/layout.tsx و app/(shop)/layout.tsx)، یا root layout شما زیر یک segment داینامیک قرار دارد (مثل app/[locale]/layout.tsx در پروژه‌های چندزبانه)، هیچ layout واحدی برای رندر کردن 404 وجود ندارد — پس درخواست برای URLای که اصلاً در هیچ‌جای اپلیکیشن وجود ندارد، جایی برای fallback شدن ندارد.

global-not-found.tsx دقیقاً همین مشکل را حل می‌کند. این فایل کاملاً مسیر رندر معمول layoutهای شما را دور می‌زند، به همین دلیل باید یک سند کامل HTML برگرداند:

app/global-not-found.tsxtsx
import "./globals.css";
import { Vazirmatn } from "next/font/google";
import type { Metadata } from "next";
 
const vazirmatn = Vazirmatn({ subsets: ["arabic"] });
 
export const metadata: Metadata = {
  title: "404 — صفحه پیدا نشد",
  description: "صفحه‌ای که دنبالش هستید وجود ندارد.",
  robots: { index: false, follow: true },
};
 
export default function GlobalNotFound() {
  return (
    <html lang="fa" dir="rtl" className={vazirmatn.className}>
      <body>
        <div className="flex min-h-screen flex-col items-center justify-center gap-3 px-4 text-center">
          <h1 className="text-3xl font-bold">404 — صفحه پیدا نشد</h1>
          <p>این صفحه وجود ندارد.</p>
          <a href="/" className="mt-4 underline">
            بازگشت به صفحهٔ اصلی
          </a>
        </div>
      </body>
    </html>
  );
}

از not-found.tsx استفاده کنید اگر اپلیکیشن شما فقط یک root layout دارد — ساده‌تر است و از پوستهٔ UI مشترک شما استفاده می‌کند. از global-not-found.tsx استفاده کنید اگر چند root layout دارید یا root layout شما زیر یک segment داینامیک است (رایج در پروژه‌های i18n). از آنجا که این فایل مسیر رندر معمول را دور می‌زند، dependencyهایش (فونت‌ها، CSS) را حداقلی نگه دارید — این فایل مستقیماً روی مسیر بحرانی هر لینک شکسته‌ای قرار می‌گیرد که اپلیکیشن شما دریافت می‌کند.


۳. فراخوانی notFound() در مسیرهای داینامیک — و رفع یک باگ رایج

اینجا جایی‌ست که بیشتر نمونه‌کدها (از جمله نسخه‌ای که با آن شروع کردید) اشتباه می‌کنند. fetch() در پاسخ‌های 4xx یا 5xx نه throw می‌کند و نه مقدار falsy برمی‌گرداند — با موفقیت resolve می‌شود، در حالی که res.ok === false است. شرط if (!res) هیچ‌وقت یک پست گم‌شده را تشخیص نمی‌دهد؛ فقط خطاهای سطح شبکه را می‌گیرد.

همچنین در Next.js 16، پراپ params به‌صورت async است و باید await شود:

app/posts/[id]/page.tsxtsx
import { notFound } from "next/navigation";
 
type Props = {
  params: Promise<{ id: string }>;
};
 
export default async function PostPage({ params }: Props) {
  const { id } = await params;
 
  const res = await fetch(`https://api.example.com/posts/${id}`, {
    next: { revalidate: 3600 },
  });
 
  if (!res.ok) {
    notFound();
  }
 
  const post = await res.json();
 
  return <article>{post.title}</article>;
}

notFound() به‌صورت داخلی throw می‌کند تا رندر را متوقف کرده و کنترل را به نزدیک‌ترین not-found.tsx بسپارد — آن را داخل try/catchای که ممکن است این throw را قورت بدهد قرار ندهید، و آن را به‌صورت شرطی بعد از side effectهایی که باید skip شوند فراخوانی نکنید.


۴. نکتهٔ SEOای که تقریباً هیچ‌کس به آن اشاره نمی‌کند: status code و streaming

این بخش واقعاً روی رتبه‌بندی تأثیر می‌گذارد و مختص نحوهٔ stream کردن پاسخ‌ها در App Router است:

  • در پاسخ‌های غیر-stream شده، Next.js به‌درستی status کد 404 را هنگام رندر not-found.tsx برمی‌گرداند.
  • در پاسخ‌های stream شده، پاسخ اولیه با status 200 ارسال می‌شود (چون headerها قبل از اینکه Next.js بداند notFound() پایین‌تر در DOM فراخوانی می‌شود یا نه، ارسال می‌شوند)، و معنای 404 فقط در محتوای رندرشده منعکس می‌شود.

در عمل، این موضوع به‌ندرت Googlebot را گیج می‌کند — چون محتوا را رندر کرده و می‌خواند — اما می‌تواند crawlerهای ساده، ابزارهای uptime monitoring، یا اسکریپت‌های سفارشی‌ای را که response.status === 404 را روی یک مسیر stream شده چک می‌کنند، دچار مشکل کند. اگر status code 404 برای یک integration خارجی اهمیت دارد (یک قانون purge در CDN، یک مانیتور redirect، یک bot غیر-JS)، حتماً header واقعی را با curl -I روی مسیر deploy‌شده تست کنید، نه صرفاً بر اساس مستندات، چون رفتار streaming به render mode مسیر شما بستگی دارد.


۵. چک‌لیست SEO برای صفحات 404

  • robots: { index: false } روی هر صفحهٔ 404 — هم در سطح مسیر و هم global.
  • یک H1 واضح و یکتا که بگوید صفحه پیدا نشده — آن را زیر متن برندینگ عمومی پنهان نکنید.
  • لینک واقعی بازگشت به ناوبری (صفحهٔ اصلی و در صورت امکان جستجو)، نه فقط یک بن‌بست.
  • بدون صفحات تکراری یا soft-404. اگر یک منبع برای همیشه حذف شده، 404 (یا 410 برای «برای همیشه حذف شده») برگردانید، نه ریدایرکت بی‌صدا به صفحهٔ اصلی — ریدایرکت بی‌صدای محتوای گمشده مستقیماً به بهره‌وری crawl لطمه می‌زند.
  • مانیتورش کنید. ترافیک 404 را در Search Console یا ابزار آنالیتیکس خودتان دنبال کنید تا لینک‌های داخلی شکسته را هم بگیرید، نه فقط لینک‌های خارجی را.

۶. خلاصهٔ بهترین روش‌ها

روشدلیل
نگه‌داشتن not-found.tsx به‌صورت Server Componentدلیلی برای ارسال JS کلاینت برای یک حالت خطای استاتیک نیست
استفاده از global-not-found.tsx فقط در صورت داشتن چند root layoutدر غیر این صورت تکرار غیرضروری پوستهٔ UI است
چک کردن res.ok به‌جای truthiness قبل از فراخوانی notFound()fetch() روی status کدهای خطای HTTP reject نمی‌شود
تنظیم صریح noindexNext.js این مورد را خودکار تشخیص نمی‌دهد
ترجیح notFound() بر redirect() برای محتوای گمشدهمعنای صحیح را برای SEO و آنالیتیکس حفظ می‌کند

نتیجه‌گیری

در Next.js 16، مدیریت درست 404 صرفاً ساختن not-found.tsx با کمی متن نیست — انتخاب درست بین قابلیت سطح مسیر و سطح global بر اساس ساختار layout شما، fetch کردن داده با چک res.ok به‌جای تکیه بر throw کردن fetch()، و علامت‌گذاری صریح صفحه با noindex تا هرگز برای رتبه‌بندی با محتوای واقعی رقابت نکند. اگر این سه مورد را درست انجام دهید، بقیهٔ موارد — bounce rate، crawl budget، تجربهٔ کاربری — به‌طور طبیعی درست می‌شوند.


پیشنهاد من

اگر این را برای یک سایت production می‌سازید (نه یک دمو)، پیشنهادم این است که به‌صورت پیش‌فرض از not-found.tsx استفاده کنید، مگر از قبل بدانید که به چند root layout نیاز دارید — سراغ global-not-found.tsx نروید مگر واقعاً لازم باشد، چون شما را از layout مشترک و فونت‌لودینگ مشترک خارج می‌کند که در یک اپلیکیشن با یک root layout، فقط نگهداری بیشتر است بدون هیچ سودی. همچنین پیشنهاد می‌کنم یک input جستجوی سبک یا لیست «صفحات پرطرفدار» به خود UI صفحهٔ 404 اضافه کنید، نه فقط یک لینک به صفحهٔ اصلی — مخصوصاً برای یک سایت پورتفولیو/بلاگ، بخش زیادی از ترافیک 404 شما کاربرانی هستند که slug یک پست بلاگ را اشتباه تایپ کرده‌اند، و دادن راهی برای پیدا کردن مستقیم پست درست، bounce rate را خیلی بیشتر از یک لینک سادهٔ «برو به خانه» کاهش می‌دهد.

ادمین

نویسنده:

ادمین
دقیقه:7
انتشار :۱۴۰۵/۴/۲۲
آپدیت :۱۴۰۵/۴/۲۲

دسته بندی ها

فرانت‌اند

تگ ها

#Next.js#App Router#404 Error#React#Frontend#SEO