/** * Server-side project data fetching for Next.js Server Components. * * Uses `fetch` directly (no browser-only helpers) so it can run * inside `generateMetadata`, `generateStaticParams`, `sitemap()`, etc. */ import type { ProjectDetail, ProjectSummary, PaginatedResult } from './du-an-api'; const API_BASE_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:3001/api/v1'; /** * Fetch a single project by slug — server-only. * Returns `null` when the project is not found (404) so callers can `notFound()`. */ export async function fetchProjectBySlug(slug: string): Promise { try { const res = await fetch(`${API_BASE_URL}/projects/${slug}`, { next: { revalidate: 300 }, // ISR: re-validate every 5 min }); if (!res.ok) return null; return (await res.json()) as ProjectDetail; } catch { return null; } } /** * Fetch active projects — server-only, used by the dynamic sitemap. */ export async function fetchProjects(params: { page?: number; limit?: number; city?: string; status?: string; }): Promise> { const query = new URLSearchParams({ page: String(params.page ?? 1), limit: String(params.limit ?? 100), }); if (params.city) query.append('city', params.city); if (params.status) query.append('status', params.status); try { const res = await fetch(`${API_BASE_URL}/projects?${query}`, { next: { revalidate: 3600 }, // re-validate every hour for sitemap }); if (!res.ok) { return { data: [], total: 0, page: 1, limit: 100, totalPages: 0 }; } return (await res.json()) as PaginatedResult; } catch { return { data: [], total: 0, page: 1, limit: 100, totalPages: 0 }; } }