# رفع خطای `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 نداشته باشد؛ اگر ندارد دست‌نخورده بماند.
