Files
nobat724_front/.claude/prompt/fix-sitemap-build-fetch-failed.md

111 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# رفع خطای `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 نداشته باشد؛ اگر ندارد دست‌نخورده بماند.