import { MetadataRoute } from 'next' import { mockDb } from '@/lib/mockDb' import { SITE_URL, LOCALES } from '@/lib/seo' function localizedEntries( pathSuffix: string, opts: { lastModified?: Date changeFrequency?: NonNullable priority?: number } ): MetadataRoute.Sitemap { const languages: Record = {} for (const locale of LOCALES) { languages[locale] = `${SITE_URL}/${locale}${pathSuffix}` } languages['x-default'] = `${SITE_URL}/tr${pathSuffix}` return LOCALES.map((locale) => ({ url: `${SITE_URL}/${locale}${pathSuffix}`, lastModified: opts.lastModified || new Date(), changeFrequency: opts.changeFrequency, priority: opts.priority, alternates: { languages }, })) } export default async function sitemap(): Promise { const entries: MetadataRoute.Sitemap = [] // 1. Home entries.push(...localizedEntries('', { changeFrequency: 'weekly', priority: 1.0 })) // 2. Static routes const staticRoutes = ['/about', '/contact', '/add-business', '/collections', '/blog', '/events'] for (const route of staticRoutes) { entries.push(...localizedEntries(route, { changeFrequency: 'weekly', priority: 0.7 })) } // 3. Category landing pages (slugs are DB-driven, not hardcoded) try { const categories = await mockDb.getCategories() for (const category of categories) { entries.push(...localizedEntries(`/${category.slug}`, { changeFrequency: 'daily', priority: 0.8 })) } } catch (e) { console.error('Sitemap categories fetch error:', e) } // 4. Listing detail pages try { const listings = await mockDb.getListings() for (const listing of listings) { const catSlug = listing.category?.slug || 'isletme' entries.push( ...localizedEntries(`/${catSlug}/${listing.slug}`, { lastModified: listing.updatedAt, changeFrequency: 'weekly', priority: 0.6, }) ) } } catch (e) { console.error('Sitemap listings fetch error:', e) } // 5. Neighborhood pages & Programmatic Neighborhood x Category landing pages try { const neighborhoods = await mockDb.getNeighborhoods() const categories = await mockDb.getCategories() for (const neighborhood of neighborhoods) { entries.push( ...localizedEntries(`/neighborhood/${neighborhood.slug}`, { changeFrequency: 'monthly', priority: 0.5 }) ) // Programmatic Neighborhood x Category combinations for (const category of categories) { const matchingListings = await mockDb.getListings({ neighborhoodId: neighborhood.id, categoryId: category.id, }) // Only include indexable programmatic pages in sitemap (at least 2 listings) if (matchingListings.length >= 2) { entries.push( ...localizedEntries(`/neighborhood/${neighborhood.slug}/${category.slug}`, { changeFrequency: 'weekly', priority: 0.7, }) ) } } } } catch (e) { console.error('Sitemap neighborhoods fetch error:', e) } // 6. Collections try { const collections = await mockDb.getCollections() for (const collection of collections) { entries.push( ...localizedEntries(`/collection/${collection.slug}`, { lastModified: collection.updatedAt, changeFrequency: 'monthly', priority: 0.5, }) ) } } catch (e) { console.error('Sitemap collections fetch error:', e) } // 7. Blog posts try { const posts = await mockDb.getBlogPosts(true) for (const post of posts) { entries.push( ...localizedEntries(`/blog/${post.slug}`, { lastModified: post.updatedAt, changeFrequency: 'monthly', priority: 0.5, }) ) } } catch (e) { console.error('Sitemap blog posts fetch error:', e) } // 8. AI-generated itineraries (long-tail SEO pages, PRD §3.3) // Each plan is generated in a single language (see params.locale) — unlike // other content types it has no translated counterpart, so it gets one // sitemap entry for its own locale rather than the tr/en/ru trio. try { entries.push(...localizedEntries('/plan-olustur', { changeFrequency: 'monthly', priority: 0.6 })) const itineraries = await mockDb.getItineraries() for (const itinerary of itineraries) { const itinLocale = LOCALES.includes((itinerary.params as any)?.locale) ? (itinerary.params as any).locale : 'tr' entries.push({ url: `${SITE_URL}/${itinLocale}/plan/${itinerary.id}`, lastModified: itinerary.createdAt, changeFrequency: 'yearly', priority: 0.3, }) } } catch (e) { console.error('Sitemap itineraries fetch error:', e) } return entries }