# رفع خطای `TypeError: fetch failed` در sitemap هنگام دیپلوی (Docker build) ## پروژه `nobat724_front` ## زمینه هنگام دیپلوی روی سرور (Coolify، Docker multi-stage build) در لاگ build این خطا ظاهر می‌شود: ``` #17 192.1 Error fetching sitemap data from /api/v1/clinics: TypeError: fetch failed ``` `#17` همان مرحله‌ی builder در `Dockerfile` است (`RUN npm run build`، خط ۵۵). یعنی این خطا در **زمان build** رخ می‌دهد، نه زمان اجرا. ## مشکل / هدف `app/sitemap.js` یک metadata route است. Next.js هنگام `next build` تلاش می‌کند این route را **prerender** کند و در همان لحظه تابع `sitemap()` اجرا می‌شود؛ این تابع از طریق `fetchAllPages()` به `NEXT_PUBLIC_API_URL` (یعنی `api.clinic-pro.ir`) درخواست `fetch` می‌زند. کانتینر build در Coolify به این API دسترسی شبکه‌ای ندارد (شبکه build ایزوله است / DNS در دسترس نیست) → `fetch` با `TypeError: fetch failed` شکست می‌خورد. خطا داخل `try/catch` تابع `fetchAllPages` گرفته می‌شود (خط ۷۸–۸۰)، پس build **کرش نمی‌کند** ولی نتیجه‌اش این است که sitemap تولیدشده **خالی** است (فقط صفحات استاتیک، بدون هیچ URL پزشک/کلینیک/بلاگ). این هم لاگ خطای آزاردهنده می‌دهد و هم SEO را خراب می‌کند. **هدف:** sitemap در زمان build اصلاً به API وصل نشود؛ داده‌ها در زمان **اجرا (request-time)** روی سرور production گرفته شوند — جایی که کانتینر runtime به API دسترسی دارد. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `app/sitemap.js` | تولید sitemap؛ محل fetch در زمان build | | `Dockerfile` | مرحله‌ی builder خط ۵۵ (`RUN npm run build`) جایی که خطا رخ می‌دهد — فقط برای درک، تغییر نمی‌کند | | `app/robots.js` | مشابه sitemap؛ ولی fetch ندارد — احتمالاً نیاز به تغییر نیست، فقط چک شود | ## وضعیت فعلی `app/sitemap.js` هیچ `export const dynamic` یا `revalidate` ندارد، پس Next آن را کاندید static prerender در build می‌داند. تابع fetch: ```js // app/sitemap.js — خط 56 async function fetchAllPages(path, extraParams = {}) { if (!API_URL) return []; const results = []; try { for (let page = 1; page <= MAX_PAGES; page++) { const search = new URLSearchParams({ ...extraParams, page: String(page), limit: String(PAGE_LIMIT), }); const res = await fetch(`${API_URL}${path}?${search.toString()}`, { next: { revalidate: 3600 }, }); if (!res.ok) break; // ... } } catch (error) { console.error(`Error fetching sitemap data from ${path}:`, error); } return results; } // خط 140 export default async function sitemap() { /* ... */ } ``` ## وظایف ### ۱. اجبار sitemap به رندر در زمان اجرا (نه build) بالای `app/sitemap.js` (بعد از importها) این دو خط را اضافه کن تا Next هرگز sitemap را در build فچ نکند و همیشه per-request روی سرور production تولید شود: ```js export const dynamic = 'force-dynamic'; export const revalidate = 3600; // کش ۱ ساعته در لایه‌ی سرور ``` > نکته: تابع فعلاً از `await headers()` استفاده می‌کند که باید route را dynamic کند، اما لاگ build ثابت می‌کند که همچنان در build اجرا می‌شود. `force-dynamic` این را قطعی می‌کند. بعد از افزودن آن، بلوک `next: { revalidate: 3600 }` داخل `fetch` را نگه‌دار — با `force-dynamic` هم بی‌ضرر است. ### ۲. مقاوم‌سازی fetch با timeout اگر در زمان اجرا API کند یا موقتاً down باشد، هر صفحه‌ی sitemap نباید بی‌نهایت منتظر بماند. به `fetch` یک timeout اضافه کن: ```js const res = await fetch(`${API_URL}${path}?${search.toString()}`, { next: { revalidate: 3600 }, signal: AbortSignal.timeout(8000), // 8s برای هر صفحه }); ``` `try/catch` موجود (خط ۷۸) خطای timeout را هم می‌گیرد، پس رفتار fail-safe فعلی (بازگشت آرایه‌ی جمع‌شده تا آن لحظه) حفظ می‌شود. ### ۳. (اختیاری) لاگ تمیزتر برای شبکه‌ی در دسترس‌نبودن پیام خطای فعلی خام است. برای اینکه در آینده گیج‌کننده نباشد، پیام را کمی روشن‌تر کن (فقط پیام، منطق دست‌نخورده): ```js } catch (error) { console.error(`[sitemap] failed to fetch ${path} (page skipped):`, error?.message || error); } ``` ## نکات مهم - **ریشه‌ی واقعی شبکه build**: `NEXT_PUBLIC_API_URL` در `Dockerfile` به‌عنوان build arg پاس داده می‌شود چون برای inline شدن در bundle کلاینت لازم است — این درست است و نباید حذف شود. مشکل صرفاً این است که *در حین build نباید به آن fetch زده شود*. راه‌حل بالا همین را حل می‌کند؛ **نیازی به تغییر Dockerfile نیست**. - بعد از این تغییر، sitemap فقط زمانی که خزنده یا کاربر `/sitemap.xml` را روی سرور production باز کند تولید می‌شود؛ آنجا کانتینر runtime به `api.clinic-pro.ir` دسترسی دارد. - معماری multi-domain حفظ شود: `sitemap()` از `await headers()` برای تشخیص host/شهر استفاده می‌کند — با `force-dynamic` این هدرها در زمان اجرا در دسترس‌اند (در build نبودند). این یک دلیل اضافه برای درست بودن `force-dynamic` است. - `output: 'standalone'` در `next.config.js` فعال است؛ route داینامیک در سرور standalone بدون مشکل کار می‌کند. - تست محلی: `npm run build` باید بدون خطای `fetch failed` تمام شود؛ سپس `npm run start` و باز کردن `http://yazd-nobat.localhost:3000/sitemap.xml` باید URLهای پزشک/کلینیک را نشان دهد (با API در دسترس). - `app/robots.js` را چک کن که fetch نداشته باشد؛ اگر ندارد دست‌نخورده بماند.