ما توسعهدهندهها همیشه عاشق ساختن هستیم. از طراحی رابط کاربری گرفته تا تعامل با APIها و دیتابیسها؛ اما یه بخش خیلی مهم از پروژههامون که بعضی وقتها بیخیالش میشیم، همون چیزیه که میتونه موفقیت یا شکست یه سایت رو توی نتایج گوگل رقم بزنه: سئو (SEO).
فرض کن یه فروشگاه آنلاین، وبلاگ یا حتی یک پلتفرم خدماتی راهاندازی کردی و کلی محتوای با ارزش تولید کردی، اما کسی سایتت رو توی گوگل پیدا نمیکنه! خب اینجاست که میفهمی بدون سئو، حتی بهترین پروژهها هم ممکنه دیده نشن.
یکی از ابزارهای خیلی مهم در سئو، که مخصوصاً برای سایتهای داینامیک کاربرد داره، فایل sitemap.xml هست. فایلی که به گوگل میگه چه صفحاتی داری، کی آخرین بار آپدیت شدن و اصلاً کدومها برای ایندکس شدن ارزشمندترن.

ما تو این مقاله از Next.js 13+ با App Router به همراه TypeScript استفاده میکنیم. پس اگه شما از pages استفاده میکنید، ساختارها کمی متفاوته.
سئو یا به صورت کامل تر Search Engine Optimization یعنی بهینه سازی سایت برای دیده شدن بهتر در موتور های جستجو مثل گوگل. هدفش خیلی ساده هست:
وقتی کسی دنبال یه چیزی میگرده، سایت تو توی نتایج بالا باشه!
اما موتور جستجو چطور باید بفهمه توی سایت تو چه خبره؟ از کجا بدونه چه صفحاتی ساختی؟ یا چه محتوایی جدیداً اضافه کردی؟
اینجاست که sitemap وارد ماجرا میشه.
Sitemap یک فایل XML هست که لیستی از آدرس های مهم سایتت رو به همراه اطلاعاتی مثل تاریخ آخرین بروزرسانی، اولویت، نسخه موبایل و حتی تصاویر و ویدیوها در خودش نگه میداره. گوگل با خوندن این فایل، ساختار سایتت رو بهتر میفهمه و میتونه صفحات جدید یا آپدیت شده رو سریعتر ایندکس کنه.
به زبان ساده تر:
sitemap مثل یه نقشه راه برای گوگله که بهش میگه: «بیا، اینم همه صفحه های من، اینا رو دقیق تر بررسی کن، این یکی تازه آپدیت شده، اون یکی خیلی مهمه...»
همه ی ما میدونیم که ساخت یک فایل sitemap.xml به صورت دستی شاید برای یک سایت ثابت با تعداد صفحات کم، کار سختی نباشه. مثلاً یه سایت شرکتی که فقط ۴–۵ تا صفحه داره: «خانه، درباره ما، تماس با ما، خدمات». خب میشه یه بار فایل sitemap رو ساخت و تا مدت ها دست نزد.
اما اگه پروژه ی شما یکی از این ها باشه چی؟
یه فروشگاه اینترنتی که هر روز چند تا محصول جدید اضافه میکنه.
یه سایت بلاگ یا مجله که مدام مقاله های تازه منتشر میکنه.
یه پلتفرم آموزشی که هر هفته دوره یا ویدیو جدید آپلود میکنه.
یا یه مارکت پلیس که کاربرانش خودشون محتوا منتشر میکنن.
توی این سناریو ها، اگه بخوایم به روش دستی sitemap رو بسازیم و هر بار خودمون اون فایل XML رو آپدیت کنیم، هم وقت گیره، هم احتمال خطا زیاده و هم ممکنه بعضی صفحات مهم از قلم بیفتن و اصلاً ایندکس نشن.
ما با سایتی طرف هستیم که مطالب، صفحات یا محصولات اون به صورت داینامیک و مداوم در حال اضافه شدن هستن. پس:
نمیتونیم فایل sitemap رو به صورت ثابت و دستی مدیریت کنیم.
نمیخوایم بابت هر تغییر کوچیک، دستی فایل XML رو ویرایش کنیم.
میخوایم گوگل همیشه از آخرین وضعیت صفحات سایت مطلع باشه.
بنابراین: باید سیستمی بسازیم که sitemap ما رو داینامیک تولید کنه.
و بهتر از اون، با کش کردن هوشمند یا زمان بندی مناسب، اون رو به صورت خودکار هم بروزرسانی کنه.
برای اینکه فایل sitemap.xml ما به صورت داینامیک در مسیر اصلی سایت در دسترس باشه، باید از قابلیت App Router در Next.js استفاده کنیم و یک مسیر اختصاصی براش بسازیم.
مسیر پیشنهادی برای ساخت فایل:
app/sitemap.xml/route.ts
app/ پوشه ی اصلی App Router در پروژه های Next.js هست.
sitemap.xml/ یک پوشه داخل app هست که قرار فایل مربوط به این مسیر رو در خودش نگه داره.
فایل route.ts وظیفه داره که در زمان درخواست مسیر /sitemap.xml، فایل XML داینامیک رو بسازه و برگردونه.
با این ساختار، وقتی کاربر یا ربات گوگل آدرس زیر رو باز کنه:
https://example.com/sitemap.xml
کدی که داخل route.ts نوشتیم اجرا میشه و پاسخ XML مناسب برمیگردونه.
اگر از جاوااسکریپت استفاده میکنی، به جای
route.tsبایدroute.jsبسازی.دقت کن اسم پوشه حتماً
sitemap.xmlباشه (با همین دات و فرمت)، چون Next.js این مسیرها رو براساس اسم پوشه ها مچ میکنه.در حالت توسعه (localhost)، مسیرت باید از طریق آدرس زیر کار کنه:
http://localhost:3000/sitemap.xml
ما یک API در بک اند داریم (ساخته شده با هر زبانی که باشه؛ مثلاً Node.js، PHP، Python یا حتی CMS هایی مثل وردپرس) که خروجی اون یک فایل XML معتبر مطابق با استاندارد sitemap هست.
به این صورت برای دریافت XML از بک اند مون درخواست میزنیم:
const response = await fetch( "https://example.com/api/sitemap" ); if (!response.ok) { return new Response("خطا در دریافت XML", { status: 500 }); } const xml = await response.text();
چیزی شبیه به این در ریسپانس درخواست مون به بک اند باید دریافت کنیم:

محتوای sitemap باید همیشه دقیق، بهروز و بر اساس دادههای واقعی سایت باشه؛ مثل آدرس صفحات، تاریخ آپدیت و وضعیت انتشار. این اطلاعات فقط در بکاند در دسترسه، چون اونجاست که به دیتابیس وصلیم و میتونیم تصمیم بگیریم کدوم صفحات وارد فایل بشن. همچنین، بکاند میتونه خروجی XML رو دقیقاً طبق استاندارد گوگل تولید کنه و بهصورت مستقیم در مسیر /sitemap.xml ارائه بده، چیزی که در فرانتاند ممکن نیست.
خب حالا با یه فایل XML طرفیم که پردازش مستقیمش خیلی راحت نیست.
پس با استفاده از کتابخونه ی معروف fast-xml-parser، اون رو به JSON قابل پردازش در جاوااسکریپت تبدیل میکنیم.
مزیت این کار چیه؟
میتونیم خیلی راحت با جاوااسکریپت دیتا ها رو پیمایش کنیم.
میتونیم با متدهایی مثل ()map و ()filter لیست URL ها رو بسازیم.
برای نصبش کافیه در ترمینال پروژه ی Next.js خودتون این دستور رو وارد کنید:
npm install fast-xml-parser
یا اگه از yarn استفاده میکنید:
yarn add fast-xml-parser
این کتابخونه هم برای Node.js و هم برای مرورگر بهینه شده، پس به خوبی با API Routes در Next.js سازگار هست.
به این صورت XML هامون رو به Json تبدیل میکنیم:
import { XMLParser } from "fast-xml-parser"; type UrlItem = { loc: string; lastmod?: string; }; const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "", }); const json = parser.parse(xml); const urls: UrlItem[] = Array.isArray(json.urlset.url) ? json.urlset.url : [json.urlset.url];
مثلاً خروجی JSON به این شکل میشه:

حالا که دیتاها رو داریم، وقتشه خروجی نهایی رو بسازیم.
ما با استفاده از Template Literal و یه ()map ساده، برای هر URL یک بلاک <url> میسازیم و در نهایت همش رو داخل یک <urlset> با xmlnsهای استاندارد قرار میدیم.
ساخت XML نهایی طبق استاندارد:
function escapeXML(str: string) { return str .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } const sitemap = `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"> ${urls .map(({ loc, lastmod }) => { const safeLoc = escapeXML(loc || ""); const safeLastmod = lastmod ? escapeXML(lastmod) : null; return ` <url> <loc>${safeLoc}</loc> ${safeLastmod ? `<lastmod>${safeLastmod}</lastmod>` : ""} </url>`; }) .join("\n")} </urlset>`;
توی همین مرحله مطمئن میشیم که کاراکتر های خاص مثل > ، & ، < ، " ، ' با استفاده از یک تابع
()escapeXMLبه فرم ایمن تبدیل میشن تا XML خراب نشه.
نمونه خروجی نهایی فایل sitemap به فرمت XML:

حالا که فایل نهایی آماده هست، باید کاری کنیم که:
گوگل یا مرورگرها نیاز نداشته باشن هر بار درخواست جدید بدن.
اما اگه تغییراتی توی سایت انجام شده، بعد از مدت مشخصی فایل جدید دریافت کنن.
برای این کار از هدر HTTP زیر استفاده میکنیم:
return new Response(sitemap, { headers: { "Content-Type": "application/xml", "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=59", }, });
public: این فایل رو همه (مرورگر، سرور، CDN و ربات ها) میتونن کش کنن.
اگه اینجا
privateبود، فقط مرورگر کاربر کَشش میکرد. ولی ما میخوایم فایل رو همه بتونن نگه دارن، پسpublicقرار دادیم.
s-maxage=3600: روی سرورهای CDN یا لایه کش سمت سرور، این فایل به مدت 3600 ثانیه (یعنی ۱ ساعت) معتبر بمونه.
مثلاً اگر از Vercel یا Cloudflare استفاده میکنی، فایل sitemap تا یک ساعت روی سرورشون ذخیره میمونه و نیاز نیست دوباره از سرور اصلی بخونن.
stale-while-revalidate=59اینو شاید خیلیا نمیدونن چیه ولی خیلی مهمه:
اگه از عمر کش بیشتر از s-maxage گذشته باشه (مثلاً بعد از یک ساعت)، همون نسخه قدیمی رو فعلاً نمایش بده.
در همین حین، برو یه نسخه جدید از سرور اصلی بگیر و توی بک گراند بروزرسانیش کن.
با این تکنیک، کاربر هیچ وقت منتظر نمیمونه (چون نسخه کش شده فوری لود میشه) ولی در بک گراند، نسخه تازه هم آماده میشه و در آینده جایگزین میشه.
در این مرحله، میخوایم کل کدی که برای ساخت فایل Sitemap داینامیک نیاز داریم رو در اختیار شما بذاریم.
تمام مراحلی که بالا توضیح دادیم، حالا در اینجا در قالب یک فایل پیاده سازی شدن تا بتونی دقیقاً همین کد رو در پروژهت استفاده کنی یا با نیاز خودت شخصی سازیش کنی.
همون طور که قبل تر گفته شد این فایل باید با نام route.ts در مسیر زیر قرار بگیره:
app/sitemap.xml/route.ts
با این کار، هر زمان که ربات گوگل یا مرورگر شما به مسیر /sitemap.xml مراجعه کنن، این فایل به صورت کاملاً داینامیک تولید میشه، اطلاعات بروز از بک اند گرفته میشه، و به شکل XML معتبر برگردونده میشه.
حالا بریم سراغ کد نهایی:
import { XMLParser } from "fast-xml-parser"; type UrlItem = { loc: string; lastmod?: string; }; function escapeXML(str: string) { return str .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } export async function GET() { try { const response = await fetch( "https://rasadent-app-ws.liara.run/api/sitemap" ); if (!response.ok) { return new Response("خطا در دریافت XML", { status: 500 }); } const xml = await response.text(); const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "", }); const json = parser.parse(xml); const urls: UrlItem[] = Array.isArray(json.urlset.url) ? json.urlset.url : [json.urlset.url]; const sitemap = `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"> ${urls .map(({ loc, lastmod }) => { const safeLoc = escapeXML(loc || ""); const safeLastmod = lastmod ? escapeXML(lastmod) : null; return ` <url> <loc>${safeLoc}</loc> ${safeLastmod ? `<lastmod>${safeLastmod}</lastmod>` : ""} </url>`; }) .join("\n")} </urlset>`; return new Response(sitemap, { headers: { "Content-Type": "application/xml", "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=59", }, }); } catch (error) { console.error("Sitemap Error:", error); return new Response("خطای داخلی سرور", { status: 500 }); } }
ساخت یک فایل Sitemap داینامیک و همیشه بهروز، فقط یه قابلیت تکنیکی ساده نیست؛
بلکه نشونهی اینه که شما به سئو، کارایی سایت و توسعه حرفهای پروژهت اهمیت میدی.
توی این مقاله دیدیم که چطور با استفاده از Next.js (App Router)، دریافت دیتا از API بکاند، و مدیریت هوشمند کش، میتونیم یه فایل sitemap بسازیم که خودش به صورت خودکار هر ساعت آپدیت بشه.
فرقی نمیکنه تازهکاری یا حرفهای؛ داشتن این قابلیت باعث میشه:
سایتت سریعتر و بهتر توی گوگل ایندکس بشه.
رباتهای موتور جستجو راحتتر ساختار صفحاتت رو درک کنن.
و حتی رزومهات حرفهایتر و چشمگیرتر بهنظر بیاد.
اگه این مقاله برات مفید بود و حس کردی به دردت خورده، خوشحال میشم نظرت رو بدونم. میتونی اون رو با بقیه به اشتراک بذاری یا برای مطالب بعدی دنبالم کنی.
اگه سوالی داشتی یا تجربهای در این زمینه داری، خوشحال میشم تو کامنتا بخونمش :)