From 862544b5a19ed29c3643c014f20a6c5fd50f47dd Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Sun, 12 Jul 2026 20:23:47 +0300 Subject: [PATCH] feat: add dynamic sitemap and robots crawler config --- app/robots.ts | 30 ++++++++++++++++++++++ app/sitemap.ts | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 app/robots.ts create mode 100644 app/sitemap.ts diff --git a/app/robots.ts b/app/robots.ts new file mode 100644 index 0000000..f2d5f78 --- /dev/null +++ b/app/robots.ts @@ -0,0 +1,30 @@ +import type { MetadataRoute } from 'next'; + +const PROTECTED = ['/admin/', '/api/']; + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: '*', + allow: '/', + disallow: PROTECTED, + }, + // AI arama / citation botları + { userAgent: 'OAI-SearchBot', allow: '/', disallow: PROTECTED }, + { userAgent: 'ChatGPT-User', allow: '/', disallow: PROTECTED }, + { userAgent: 'PerplexityBot', allow: '/', disallow: PROTECTED }, + { userAgent: 'Perplexity-User', allow: '/', disallow: PROTECTED }, + { userAgent: 'Claude-SearchBot', allow: '/', disallow: PROTECTED }, + { userAgent: 'Claude-User', allow: '/', disallow: PROTECTED }, + // Training crawler'ları engelle + { userAgent: 'GPTBot', disallow: '/' }, + { userAgent: 'ClaudeBot', disallow: '/' }, + { userAgent: 'Google-Extended', disallow: '/' }, + { userAgent: 'CCBot', disallow: '/' }, + { userAgent: 'Bytespider', disallow: '/' }, // ByteDance/TikTok + { userAgent: 'Meta-ExternalAgent', disallow: '/' }, // Meta training + ], + sitemap: 'https://marmarislocal.com/sitemap.xml', + }; +} diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 0000000..264a1e1 --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,70 @@ +import { MetadataRoute } from 'next' +import { mockDb } from '@/lib/mockDb' + +export default async function sitemap(): Promise { + const baseUrl = 'https://marmarislocal.com' + const locales = ['tr', 'en', 'ru'] + + // Static routes + const staticRoutes = [ + '', + '/apartlar', + '/restoranlar', + '/isletmeler', + '/isletme-ekle', + '/iletisim', + '/hakkinda' + ] + + const sitemapEntries: MetadataRoute.Sitemap = [] + + // 1. Generate static routes for each locale + for (const route of staticRoutes) { + for (const locale of locales) { + sitemapEntries.push({ + url: `${baseUrl}/${locale}${route}`, + lastModified: new Date(), + changeFrequency: 'weekly', + priority: route === '' ? 1.0 : 0.8, + }) + } + } + + // 2. Dynamic listing routes + try { + const listings = await mockDb.getListings() + for (const listing of listings) { + // Find the category slug dynamically + const catSlug = listing.category?.slug || 'isletmeler' + for (const locale of locales) { + sitemapEntries.push({ + url: `${baseUrl}/${locale}/${catSlug}/${listing.slug}`, + lastModified: listing.updatedAt || new Date(), + changeFrequency: 'weekly', + priority: 0.6, + }) + } + } + } catch (e) { + console.error('Sitemap listings fetch error:', e) + } + + // 3. Dynamic neighborhood routes + try { + const neighborhoods = await mockDb.getNeighborhoods() + for (const neighborhood of neighborhoods) { + for (const locale of locales) { + sitemapEntries.push({ + url: `${baseUrl}/${locale}/mahalle/${neighborhood.slug}`, + lastModified: new Date(), + changeFrequency: 'monthly', + priority: 0.4, + }) + } + } + } catch (e) { + console.error('Sitemap neighborhoods fetch error:', e) + } + + return sitemapEntries +}