معماری Canonical در وب‌سایت‌های بزرگ

معماری Canonical در وب‌سایت‌های بزرگ؛ چرا Canonical فقط یک تگ HTML نیست؟

از سیاست URL و اصلاح Slug تا Redirect، Sitemap، لینک‌سازی داخلی، SSR و پایش خودکار

در بسیاری از وب‌سایت‌ها، پیاده‌سازی Canonical به افزودن یک تگ در بخش <head> صفحه محدود می‌شود:

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

وجود این تگ ضروری و مفید است، اما به‌تنهایی نشان‌دهنده وجود یک معماری صحیح Canonical نیست.

ممکن است یک صفحه تگ Canonical داشته باشد، اما هم‌زمان:

  • از چند URL مختلف با پاسخ 200 OK در دسترس باشد؛
  • Sitemap نسخه دیگری از URL را معرفی کند؛
  • لینک‌های داخلی به آدرس متفاوتی اشاره کنند؛
  • نسخه دارای Slug اشتباه بدون Redirect نمایش داده شود؛
  • پارامترهای Query باعث تولید تعداد زیادی URL مشابه شوند؛
  • Frontend و Backend از دو الگوریتم متفاوت برای ساخت URL استفاده کنند؛
  • Canonical در SSR با خروجی Client متفاوت باشد؛
  • یا Cache، Canonical یک صفحه را به صفحه دیگری منتقل کند.

در چنین شرایطی، تگ Canonical وجود دارد، اما سامانه پیام منسجمی درباره URL اصلی محتوا ارسال نمی‌کند.

موتور جست‌وجو از میان مجموعه‌ای از URLهای تکراری یا بسیار مشابه، یک URL را به‌عنوان نسخه نماینده انتخاب می‌کند. URL اعلام‌شده از سوی سایت یکی از سیگنال‌های این تصمیم است، اما موتور جست‌وجو ممکن است بر اساس مجموعه سیگنال‌های موجود، URL دیگری را انتخاب کند.

بنابراین، Canonical را نباید فقط یک تگ HTML در نظر گرفت.

Canonical Architecture مجموعه‌ای از سیاست‌ها، قراردادها و سازوکارهای فنی است که باعث می‌شود تمام اجزای سامانه درباره هویت اصلی یک منبع و URL رسمی آن، پیام یکسانی ارسال کنند.

در این مقاله بررسی خواهیم کرد که چنین معماری‌ای چگونه طراحی می‌شود، چه لایه‌هایی در آن نقش دارند و چرا تصمیم Canonical باید از مدل کسب‌وکار آغاز شود و تا Monitoring و CI/CD ادامه پیدا کند.


Canonicalization دقیقاً چه مسئله‌ای را حل می‌کند؟

یک محتوا ممکن است به دلایل مختلف از چند URL در دسترس باشد.

برای مثال:

https://example.com/products/125
https://example.com/products/125/
https://example.com/Products/125
https://example.com/products/125/industrial-pump
https://example.com/products/125/wrong-slug
https://example.com/products/125/industrial-pump?utm_source=newsletter
https://example.com/products/125/industrial-pump?ref=homepage

از دید سامانه، ممکن است تمام این URLها به یک رکورد محصول با شناسه 125 منتهی شوند.

از دید کاربر نیز شاید همه آن‌ها محتوای تقریباً یکسانی نمایش دهند.

اما از دید پروتکل HTTP، هر URL یک نشانی مستقل است. اگر همه این آدرس‌ها پاسخ موفق دریافت کنند، موتور جست‌وجو باید تشخیص دهد که آیا با چند صفحه مستقل مواجه است یا چند نسخه از یک محتوا.

Canonicalization فرایند انتخاب URL نماینده برای مجموعه‌ای از صفحات تکراری یا بسیار مشابه است. موتور جست‌وجو تلاش می‌کند یک نسخه را به‌عنوان URL اصلی انتخاب و سایر نسخه‌ها را در آن تجمیع کند.

مسئله فقط جلوگیری از نمایش چند URL مشابه در نتایج نیست.

یک Canonicalization ضعیف ممکن است پیامدهای زیر را داشته باشد:

  • تقسیم سیگنال‌های لینک میان چند URL؛
  • Crawl شدن مکرر نسخه‌های کم‌ارزش؛
  • دشوارشدن تحلیل داده‌ها؛
  • نمایش URL نامطلوب در نتایج؛
  • بی‌ثباتی گزارش‌های Indexing؛
  • کاهش قابلیت پیش‌بینی رفتار موتور جست‌وجو؛
  • افزایش پیچیدگی مهاجرت و تغییر مسیرها.

محتوای تکراری درون یک سایت الزاماً به‌معنای تخلف از سیاست‌های Spam نیست؛ اما می‌تواند تجربه کاربری نامناسبی ایجاد کند و منابع Crawl را صرف URLهایی کند که برای مالک سایت اهمیت ندارند.


Canonical URL با Canonical Tag یکسان نیست

یکی از مهم‌ترین تمایزها در این حوزه، تفاوت میان این دو مفهوم است:

Canonical URL

URLی که به‌عنوان نسخه اصلی و نماینده محتوا انتخاب می‌شود.

Canonical Tag

یکی از روش‌هایی که مالک سایت برای اعلام URL ترجیحی خود استفاده می‌کند.

برای مثال:

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

این تگ می‌گوید:

مالک سایت ترجیح می‌دهد این URL به‌عنوان نسخه اصلی این محتوا در نظر گرفته شود.

اما این تگ نمی‌گوید:

موتور جست‌وجو موظف است بدون توجه به سایر سیگنال‌ها همین URL را انتخاب کند.

Canonical یک Hint یا نشانه قوی است، نه فرمانی که تمام تناقض‌های معماری سایت را نادیده بگیرد.

اگر تگ Canonical یک URL را معرفی کند، اما Redirectها، Sitemap، لینک‌های داخلی، Protocol یا محتوای صفحات پیام متفاوتی ارسال کنند، موتور جست‌وجو باید میان این سیگنال‌های متناقض تصمیم‌گیری کند.

به همین دلیل، هدف معماری Canonical افزایش تعداد تگ‌ها نیست.

هدف، کاهش تناقض میان سیگنال‌هاست.


معماری Canonical از کجا آغاز می‌شود؟

معماری Canonical از تابع تولید تگ <link> آغاز نمی‌شود.

نقطه آغاز آن، پاسخ به این پرسش است:

منبع اصلی یا Entity اصلی این صفحه چیست؟

برای مثال، در یک Marketplace ممکن است URL زیر وجود داشته باشد:

/supplier-products/125/industrial-electric-motor

سامانه باید بداند:

  • شناسه 125 به چه موجودیتی اشاره می‌کند؟
  • آیا این موجودیت Product است یا SupplierProduct؟
  • آیا همین موجودیت در دسته‌بندی دیگری نیز نمایش داده می‌شود؟
  • آیا تغییر تأمین‌کننده، نام یا دسته‌بندی هویت آن را تغییر می‌دهد؟
  • آیا صفحه دیگری برای همین موجودیت وجود دارد؟
  • آیا URL فقط مسیر دسترسی است یا بخشی از هویت عمومی منبع؟

این تصمیم‌ها به مدل Domain وابسته هستند.

اگر موجودیت اصلی با شناسه پایدار شناسایی شود، URLهای مختلف را می‌توان به همان موجودیت Resolve کرد و سپس URL رسمی آن را ساخت.

اما اگر سامانه فقط بر اساس متن Slug یا مسیر فعلی دسته‌بندی تصمیم بگیرد، کوچک‌ترین تغییر در نام یا ساختار درختی می‌تواند هویت URL را تغییر دهد.

معماری پیشنهادی را می‌توان چنین نمایش داد:

Incoming Request
       ↓
Route Parsing
       ↓
Stable Entity Resolution
       ↓
Entity State Validation
       ↓
Canonical URL Policy
       ↓
Slug and Path Generation
       ↓
Redirect Decision
       ↓
SSR Metadata Output
       ↓
Sitemap and Internal Links

اصل اول: هر Canonical باید به یک موجودیت یا منبع پایدار متصل باشد

Canonical نباید صرفاً از window.location.href یا URL فعلی درخواست ساخته شود.

برای مثال، این رویکرد از نظر معماری ضعیف است:

const canonical = `${baseUrl}${route.fullPath}`

چرا؟

زیرا route.fullPath ممکن است شامل موارد زیر باشد:

  • Slug اشتباه
  • Query Parameter
  • Tracking Parameter
  • حروف بزرگ
  • Trailing Slash نامطلوب
  • مسیر قدیمی
  • فیلترهای غیرCanonical
  • URLای که فقط به دلیل خطای Routing قابل دسترسی شده است

Canonical باید از داده معتبر Domain و سیاست مرکزی URL تولید شود.

برای نمونه:

const canonical = buildProductCanonicalUrl({
  id: product.id,
  name: product.name
})

در این مدل، Canonical از وضعیت رسمی موجودیت ساخته می‌شود، نه از آدرسی که کاربر اتفاقاً با آن وارد شده است.


اصل دوم: URL ورودی و URL Canonical دو مفهوم متفاوت‌اند

کاربر ممکن است با URL زیر وارد شود:

/products/125/wrong-name

اما URL رسمی موجودیت این باشد:

/products/125/industrial-pump

سامانه باید ابتدا URL ورودی را Parse کند، سپس Entity را با شناسه پایدار بیابد و بعد URL رسمی را بسازد.

Request URL:
/products/125/wrong-name

Resolved Entity:
Product ID = 125
Name = Industrial Pump

Canonical URL:
/products/125/industrial-pump

از اینجا یک تصمیم مهم ایجاد می‌شود:

  • آیا صفحه با پاسخ 200 نمایش داده شود و فقط Canonical صحیح داشته باشد؟
  • یا درخواست باید به URL صحیح Redirect شود؟

در اغلب صفحات موجودیت‌محور، زمانی که URL ورودی یک نسخه غیررسمی و بدون ارزش مستقل از همان محتواست، Redirect سمت سرور به URL رسمی معمولاً معماری تمیزتری ایجاد می‌کند.

اما این تصمیم همیشه خودکار نیست و باید با توجه به نوع اختلاف URL گرفته شود.


Canonical یا Redirect؛ کدام‌یک مناسب‌تر است؟

Canonical و Redirect دو ابزار متفاوت‌اند.

Redirect

در Redirect، کاربر و خزنده از URL قدیمی یا غیررسمی به URL جدید منتقل می‌شوند.

GET /products/125/wrong-slug

HTTP/1.1 301 Moved Permanently
Location: /products/125/industrial-pump

Redirect در عمل می‌گوید:

این منبع اکنون در URL دیگری قرار دارد.

Redirectها برای تغییر محل یک صفحه و هدایت کاربران و موتور جست‌وجو به آدرس جدید استفاده می‌شوند.

Canonical

در Canonical، صفحه همچنان از URL فعلی قابل دسترسی است، اما ترجیح مالک سایت برای نسخه اصلی اعلام می‌شود.

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

Canonical می‌گوید:

این صفحه از نظر محتوا متعلق به مجموعه‌ای از صفحات مشابه است و URL ترجیحی این مجموعه آدرس دیگری است.

قاعده عملی

اگر نسخه جایگزین دیگر برای کاربر و سامانه ارزش مستقلی ندارد و باید کنار گذاشته شود، Redirect معمولاً انتخاب مناسب‌تری است.

اگر چند نسخه به دلایل فنی یا محصولی باید همچنان در دسترس بمانند، اما یک URL باید نماینده اصلی باشد، Canonical می‌تواند مناسب‌تر باشد.


انتخاب میان 301 و 308

کدهای 301 و 308 هر دو Redirect دائمی را بیان می‌کنند.

در 308 Permanent Redirect، منبع به یک URI دائمی جدید منتقل شده و Client باید روش درخواست را در انتقال حفظ کند. RFC 9110 تعریف این کد را در کنار سایر وضعیت‌های Redirect قرار داده است.

برای درخواست‌های معمول GET، تفاوت عملی 301 و 308 اغلب محدود است؛ اما در درخواست‌های غیرGET، حفظ Method اهمیت بیشتری پیدا می‌کند.

برای صفحات عمومی SEO، استفاده از 301 بسیار رایج و قابل فهم است.

برای Routeهایی که باید Method و Body درخواست بدون تغییر حفظ شوند، 308 معنای دقیق‌تری دارد.

با این حال، تصمیم نباید فقط بر اساس SEO گرفته شود. رفتار Clientها، Reverse Proxy، Framework و API نیز باید بررسی شود.


302 و 307 برای تغییرات موقت

اگر انتقال واقعاً موقت است، استفاده از Redirect موقت مناسب‌تر است.

302 Found
307 Temporary Redirect

در 307، Method درخواست حفظ می‌شود.

یک اشتباه رایج این است که تمام Redirectها به‌صورت 302 تولید شوند، حتی زمانی که URL قدیمی برای همیشه کنار گذاشته شده است.

اشتباه دیگری نیز وجود دارد: استفاده از Redirect دائمی برای شرایط موقتی مانند Maintenance یا آزمایش کوتاه‌مدت.

نوع Redirect باید معنای واقعی تغییر را بیان کند.


آیا Slug اشتباه باید Redirect شود؟

فرض کنیم URL رسمی محصول این باشد:

/products/125/industrial-pump

اما درخواست زیر دریافت شود:

/products/125/electric-generator

شناسه هنوز به همان محصول اشاره می‌کند، اما Slug اشتباه است.

سه رویکرد ممکن وجود دارد.

رویکرد اول: نمایش صفحه با پاسخ 200 و Canonical صحیح

200 OK
Canonical: /products/125/industrial-pump

مزیت:

  • پیاده‌سازی ساده‌تر
  • کاربر به محتوای صحیح می‌رسد

محدودیت:

  • تعداد زیادی URL قابل دسترسی باقی می‌ماند
  • هر متن دلخواهی ممکن است به همان شناسه متصل شود
  • Log، Analytics و Crawl پراکنده می‌شوند
  • لینک‌های اشتباه ممکن است سال‌ها باقی بمانند

رویکرد دوم: Redirect به URL صحیح

301 → /products/125/industrial-pump

مزیت:

  • فقط یک URL عملی باقی می‌ماند
  • لینک‌ها و Crawl به نسخه رسمی منتقل می‌شوند
  • URL مرورگر اصلاح می‌شود
  • Analytics تمیزتر خواهد بود

این رویکرد در بسیاری از صفحات Entity-based مناسب‌تر است.

رویکرد سوم: پاسخ 404

اگر Slug بخشی از هویت منبع باشد یا عدم تطابق آن احتمال اشاره به موجودیت دیگری را نشان دهد، می‌توان URL را نامعتبر در نظر گرفت.

این روش در معماری‌هایی مناسب است که Route صرفاً با ID Resolve نمی‌شود.


تفاوت SSR Redirect با Client-side Redirect

در برنامه‌های SPA یا Frameworkهایی مانند Nuxt، ممکن است اصلاح URL فقط در Client انجام شود:

onMounted(() => {
  router.replace(correctUrl)
})

این روش برای Canonicalization اصلی مناسب نیست.

چرا؟

زیرا پیش از اجرای JavaScript:

  • سرور پاسخ 200 برای URL اشتباه فرستاده است؛
  • HTML اولیه ممکن است Canonical دیگری داشته باشد؛
  • خزنده باید JavaScript را اجرا کند تا اصلاح را ببیند؛
  • کاربر ممکن است تغییر URL را با تأخیر مشاهده کند؛
  • امکان Flash یا Hydration mismatch وجود دارد.

برای URLهای عمومی و قابل‌ایندکس، تصمیم Redirect بهتر است در SSR، Server Middleware، Backend یا Reverse Proxy اجرا شود.

نمونه مفهومی در Nuxt:

export default defineNuxtRouteMiddleware(async (to) => {
  const id = Number(to.params.id)

  if (!Number.isInteger(id) || id <= 0) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Page not found'
    })
  }

  const entity = await getProductRouteIdentity(id)

  if (!entity) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Product not found'
    })
  }

  const canonicalPath = buildProductPath(entity)

  if (to.path !== canonicalPath) {
    return navigateTo(canonicalPath, {
      redirectCode: 301
    })
  }
})

نکته مهم این است که منطق Route Identity نباید به درخواست کامل و پرهزینه صفحه وابسته باشد، مگر آنکه Cache مناسبی وجود داشته باشد.


حساسیت به حروف بزرگ و کوچک

در بخشی از زیرساخت‌ها، URLهای زیر ممکن است دو مسیر متفاوت تلقی شوند:

/Products/125
/products/125

در بخش دیگری از سیستم، هر دو ممکن است به یک Handler برسند.

این عدم قطعیت باید با سیاست روشن برطرف شود.

معمولاً بهتر است URLهای عمومی دارای یک فرم استاندارد باشند؛ برای مثال:

lowercase path

اگر درخواست با Case متفاوت وارد شود، سامانه می‌تواند آن را به نسخه رسمی Redirect کند.

اما باید مراقب بود:

  • آیا فایل‌های Static به Case حساس هستند؟
  • آیا CDN همان رفتار Origin را دارد؟
  • آیا Nginx و Application رفتار یکسانی دارند؟
  • آیا Redirect باعث Loop نمی‌شود؟
  • آیا URLهای قدیمی با حروف متفاوت در بیرون سایت وجود دارند؟

Trailing Slash

URLهای زیر از نظر نحوی متفاوت‌اند:

/products/125
/products/125/

وب‌سایت باید یکی از این دو حالت را به‌عنوان سیاست رسمی انتخاب کند.

سپس تمام لایه‌ها باید همان سیاست را اجرا کنند:

  • Route generation
  • Redirect
  • Canonical
  • Sitemap
  • Internal links
  • Open Graph
  • APIهای عمومی
  • Cache key
  • CDN rules

اشتباه رایج این است که Canonical بدون Slash باشد، اما Framework لینک‌های داخلی را با Slash تولید کند و Nginx نیز هر دو نسخه را با پاسخ 200 ارائه دهد.


HTTP و HTTPS

در سایت Production، Canonical معمولاً باید به نسخه HTTPS اشاره کند.

اما صرفاً تغییر Canonical کافی نیست.

نسخه HTTP نیز باید به‌صورت دائمی به HTTPS Redirect شود:

http://example.com/page
   ↓ 301
https://example.com/page

اگر نسخه HTTP پاسخ 200 بدهد و فقط Canonical آن HTTPS باشد، هنوز دو نسخه عملیاتی از محتوا در دسترس هستند.

همچنین باید مراقب Headerهای Proxy مانند موارد زیر بود:

X-Forwarded-Proto
X-Forwarded-Host
Forwarded

اگر Application پشت CDN یا Reverse Proxy قرار دارد و Protocol خارجی را به‌درستی تشخیص ندهد، ممکن است Canonicalهای HTTP تولید کند، در حالی که کاربر از HTTPS وارد شده است.


Host و نسخه‌های دامنه

ممکن است سایت از چند Host قابل دسترسی باشد:

https://example.com
https://www.example.com
https://origin.example.com
https://example.ir

معماری باید مشخص کند:

  • Host اصلی چیست؟
  • آیا دامنه جایگزین Redirect می‌شود؟
  • آیا Origin مستقیماً قابل دسترسی است؟
  • آیا Canonical همیشه Host اصلی را تولید می‌کند؟
  • آیا CDN Host را به Application منتقل می‌کند؟
  • آیا محیط Staging ممکن است Canonical Production تولید کند یا برعکس؟

بهتر است Canonical Base URL از Configuration معتبر محیط دریافت شود، نه از Host خام درخواست؛ به‌خصوص زمانی که سایت پشت چند Proxy قرار دارد.

const canonicalBaseUrl = runtimeConfig.public.canonicalBaseUrl

Query Parameterها و Canonical

Query Parameterها یکی از اصلی‌ترین منابع تولید URLهای مشابه هستند.

برای مثال:

/products/125/industrial-pump?utm_source=linkedin
/products/125/industrial-pump?ref=homepage
/products/125/industrial-pump?campaign=summer

اگر این پارامترها فقط برای Tracking باشند و محتوای اصلی صفحه را تغییر ندهند، Canonical معمولاً باید به URL بدون این پارامترها اشاره کند:

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

اما همه Query Parameterها قابل حذف نیستند.

برای مثال:

/category/pumps?page=2
/category/pumps?sort=price
/category/pumps?pressure=10bar
/category/pumps?brand=sample

هرکدام معنای متفاوتی دارند.

پارامترهای Tracking

معمولاً هویت محتوا را تغییر نمی‌دهند.

Pagination

نمایانگر بخش دیگری از فهرست است.

Sort

ترتیب نمایش را تغییر می‌دهد، اما ممکن است مجموعه آیتم‌ها همان باشد.

Filter

ممکن است مجموعه‌ای کاملاً متفاوت و دارای Search Intent مستقل ایجاد کند.

Search Query

ممکن است صفحه‌ای پویا و کم‌ارزش یا در برخی پروژه‌ها Landing Page‌ای برنامه‌ریزی‌شده باشد.

بنابراین نباید تمام Queryها را بدون تحلیل حذف یا به صفحه پایه Canonical کرد.


خطر Canonical کردن تمام فیلترها به دسته‌بندی اصلی

فرض کنید این URL وجود دارد:

/pumps?type=centrifugal

اگر این صفحه:

  • محتوای مستقل دارد؛
  • عنوان و Heading مشخص دارد؛
  • تقاضای جست‌وجویی مستقلی را پاسخ می‌دهد؛
  • محصولات مرتبط و کافی دارد؛
  • لینک داخلی دریافت می‌کند؛

ممکن است یک Landing Page معتبر باشد.

اگر Canonical آن به /pumps اشاره کند، سایت عملاً اعلام می‌کند که صفحه فیلترشده نسخه مستقلی نیست.

در مقابل، ترکیبی مانند زیر شاید ارزش مستقلی نداشته باشد:

/pumps?type=centrifugal&sort=price-desc&view=grid&session=123

Canonical Architecture باید Query Parameterها را طبقه‌بندی کند، نه اینکه فقط آن‌ها را حذف کند.

نمونه یک Policy:

type QueryParameterPolicy =
  | 'tracking'
  | 'presentation'
  | 'pagination'
  | 'indexable-filter'
  | 'non-indexable-filter'
  | 'invalid'

سپس تصمیم Canonical بر اساس این طبقه‌بندی انجام می‌شود.


Pagination و Canonical

یکی از خطاهای رایج این است که تمام صفحات Pagination به صفحه اول Canonical شوند:

/category/pumps?page=2
Canonical → /category/pumps

اگر صفحه دوم مجموعه متفاوتی از آیتم‌ها را نمایش دهد، صفحه دوم نسخه تکراری کامل صفحه اول نیست.

Canonical کردن تمام صفحات به صفحه اول ممکن است سیگنال نادرستی درباره محتوای صفحات ایجاد کند.

در بسیاری از معماری‌ها، هر صفحه Pagination باید Self-referencing Canonical داشته باشد:

/category/pumps?page=2
Canonical → /category/pumps?page=2

البته تصمیم نهایی به ساختار صفحه، مدل پیمایش و سیاست Indexability وابسته است.

اگر صفحات بعدی noindex باشند، باز هم باید بررسی شود که آیا دسترسی خزنده و لینک‌های آیتم‌ها حفظ می‌شود یا خیر.


Sort و View Mode

پارامترهایی مانند موارد زیر معمولاً هویت محتوایی مستقلی ایجاد نمی‌کنند:

?sort=price-asc
?sort=newest
?view=grid
?view=list

در این شرایط، Canonical معمولاً می‌تواند به نسخه استاندارد دسته‌بندی اشاره کند.

اما این تصمیم باید با Cache، Internal Links و Routing هماهنگ باشد.

بهتر است لینک‌های داخلی اصلی مستقیماً به URL Canonical اشاره کنند و پارامترهای نمایشی از طریق State یا کنترل‌های محدود مدیریت شوند.


Self-referencing Canonical

یک صفحه اصلی و Canonical نیز بهتر است Canonical خودارجاع داشته باشد:

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

مزایای آن:

  • سیاست URL صفحه به‌صراحت اعلام می‌شود؛
  • پارامترهای ناشناخته راحت‌تر از نسخه اصلی تفکیک می‌شوند؛
  • پیاده‌سازی تمام Page Typeها یکدست می‌شود؛
  • تست خودکار ساده‌تر خواهد بود؛
  • تغییرات ناخواسته سریع‌تر شناسایی می‌شوند.

Self-referencing Canonical نباید از URL خام Request ساخته شود. باید از Canonical Policy تولید شود.


Canonical باید URL مطلق باشد

بهتر است Canonical با URL کامل تولید شود:

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

نه فقط مسیر نسبی:

<link
  rel="canonical"
  href="/products/125/industrial-pump"
/>

استفاده از URL مطلق ابهام مربوط به Host، Protocol و Base URL را کاهش می‌دهد.

البته تولید URL مطلق نیز نیازمند Configuration صحیح محیط و Proxy است.


Canonical در فایل‌های غیرHTML

Canonical فقط محدود به تگ HTML نیست.

برای منابع غیرHTML مانند PDF یا اسناد، می‌توان از HTTP Header استفاده کرد:

Link: <https://example.com/documents/technical-guide.pdf>; rel="canonical"

مستندات Google نیز استفاده از Header با رابطه canonical را برای فایل‌های غیرHTML توضیح می‌دهند.

این قابلیت در سامانه‌هایی مهم است که یک محتوا را هم در قالب صفحه HTML و هم فایل PDF یا Word منتشر می‌کنند.

با این حال باید تصمیم گرفت که کدام نسخه واقعاً منبع اصلی است.


Canonical و Sitemap

Sitemap باید URLهای اصلی، قابل‌ایندکس و Canonical را معرفی کند.

اگر صفحه‌ای چنین Canonical داشته باشد:

Canonical:
/products/125/industrial-pump

اما Sitemap این URL را ثبت کند:

/products/125

سایت دو پیام متفاوت ارسال می‌کند.

Sitemap نباید فقط خروجی خام جدول پایگاه داده یا تمام Routeهای قابل تولید باشد.

پیش از ورود URL به Sitemap باید بررسی شود:

  • Entity فعال است؟
  • صفحه قابل‌ایندکس است؟
  • URL رسمی چیست؟
  • آیا Redirect می‌شود؟
  • آیا Canonical آن به خودش اشاره می‌کند؟
  • آیا محتوای کافی دارد؟
  • آیا دسترسی عمومی دارد؟
  • آیا Host و Protocol صحیح هستند؟

یک طراحی مناسب این است که Sitemap و Canonical از یک URL Policy مشترک استفاده کنند:

const canonicalPath = urlPolicy.buildProductPath(product)

همین تابع یا سرویس باید در موارد زیر استفاده شود:

  • Canonical
  • Sitemap
  • Internal links
  • Redirect validation
  • Open Graph URL
  • Schema url
  • Breadcrumb item URL

Canonical و لینک‌سازی داخلی

اگر Canonical صفحه به URL رسمی اشاره کند، اما سایت دائماً به نسخه غیررسمی لینک دهد، موتور جست‌وجو پیام‌های متناقض دریافت می‌کند.

مثال نامناسب:

<a href="/products/125">مشاهده محصول</a>

در حالی که Canonical این است:

/products/125/industrial-pump

در این حالت، هر کلیک داخلی باعث Redirect می‌شود.

پیامدها:

  • افزایش Redirect داخلی
  • پیچیده‌شدن Crawl
  • آلودگی Log
  • کاهش کیفیت Analytics
  • افزایش Latency
  • احتمال ایجاد Chain در آینده

اصل مناسب:

لینک‌های داخلی سایت باید تا حد ممکن مستقیماً به URL Canonical اشاره کنند.

Redirect باید برای سازگاری با لینک‌های قدیمی و ورودی‌های خارجی باشد، نه به‌عنوان بخشی عادی از Navigation داخلی.


Canonical و Breadcrumb

URLهای موجود در Breadcrumb نیز باید با URL Policy مرکزی تولید شوند.

فرض کنیم محصول در مسیر زیر نمایش داده می‌شود:

Home
→ Industrial Equipment
→ Pumps
→ Industrial Pump

اگر Breadcrumb به URL قدیمی دسته‌بندی لینک دهد، در حالی که Sitemap و Canonical نسخه جدید را معرفی می‌کنند، معماری دوباره دچار ناسازگاری می‌شود.

در ساختارهای چندوالدی، باید مشخص شود که Breadcrumb:

  • مسیر Navigation فعلی است؛
  • مسیر اصلی و ترجیحی موجودیت است؛
  • یا مسیری است که کاربر از آن وارد شده است.

برای SEO و Structured Data، معمولاً نیاز به یک مسیر پایدار و قابل پیش‌بینی داریم.


Canonical و Schema.org

فیلدهای URL در داده‌های ساخت‌یافته نیز باید با Canonical هماهنگ باشند.

برای مثال:

{
  "@type": "Product",
  "@id": "https://example.com/products/125/industrial-pump#product",
  "url": "https://example.com/products/125/industrial-pump",
  "name": "Industrial Pump"
}

اگر url یا @id به نسخه دیگری از URL اشاره کند، گراف موجودیت‌ها دچار چندگانگی می‌شود.

بهتر است URLهای زیر همگی از یک Source تولید شوند:

  • Canonical
  • WebPage.url
  • Product.url
  • @id
  • Breadcrumb items
  • Open Graph URL
  • Sitemap entry

Canonical و Open Graph

Open Graph معمولاً شامل فیلدی مانند زیر است:

<meta
  property="og:url"
  content="https://example.com/products/125/industrial-pump"
/>

این URL باید با Canonical هماهنگ باشد.

اگر کاربر صفحه‌ای با Tracking Parameter را Share کند، بهتر است Preview اجتماعی به URL رسمی متصل شود، مگر آنکه نیاز محصولی مشخصی خلاف آن وجود داشته باشد.


Canonical در معماری Microservice

در معماری Microservice، اطلاعات لازم برای تولید Canonical ممکن است در چند سرویس توزیع شده باشد.

برای مثال:

  • Route در Frontend تعریف شده است؛
  • شناسه و نام محصول در Product Service است؛
  • نوع موجودیت در Category Service مشخص می‌شود؛
  • وضعیت انتشار در Catalog Service قرار دارد؛
  • Tenant یا Domain در Gateway تعیین می‌شود.

اگر Frontend برای هر Canonical مجبور باشد چند سرویس را صدا بزند، هزینه SSR افزایش پیدا می‌کند.

راهکارهای ممکن:

SEO Route Identity Contract

یک API سبک برای هویت Route:

{
  "id": 125,
  "entityType": "product",
  "name": "Industrial Pump",
  "slug": "industrial-pump",
  "isActive": true,
  "canonicalPath": "/products/125/industrial-pump"
}

Cache کردن Route Identity

اطلاعات هویتی URL معمولاً نسبت به داده‌های عملیاتی کمتر تغییر می‌کنند و می‌توانند Cache شوند.

تولید Canonical در Backend

Backend می‌تواند canonicalPath را به DTO اضافه کند.

مزیت:

  • منطق URL در یک نقطه قرار می‌گیرد.

محدودیت:

  • Backend به ساختار Route رابط کاربری وابسته می‌شود.

Shared URL Policy

Frontend و Backend از یک Specification یا Contract مشترک استفاده کنند.

این راهکار زمانی مفید است که چند Client یا چند Frontend وجود دارد.


آیا Canonical را Backend تولید کند یا Frontend؟

پاسخ عمومی واحدی وجود ندارد.

تولید در Backend

مزایا:

  • دسترسی مستقیم به Entity
  • کنترل مرکزی
  • کاهش احتمال الگوریتم متفاوت

محدودیت‌ها:

  • وابستگی Domain Service به ساختار Public URL
  • دشواری پشتیبانی از چند Domain یا Client
  • احتمال سخت‌شدن مهاجرت Frontend

تولید در Frontend

مزایا:

  • آگاهی از Route
  • کنترل نزدیک به Rendering
  • انعطاف در چند Domain

محدودیت‌ها:

  • احتمال منطق تکراری
  • وابستگی به داده ناقص
  • ناهماهنگی میان Client و Server
  • خطر تولید از Route خام

راهکار متعادل

Backend هویت و داده پایدار Entity را فراهم کند و یک URL Policy مرکزی در لایه Web، URL نهایی را تولید کند.

Backend:
Entity Identity

Web Application:
Public URL Policy

SEO Engine:
Canonical Output

Canonical در SSR و Hydration

Canonical باید در HTML اولیه SSR وجود داشته باشد.

نمونه:

<head>
  <link
    rel="canonical"
    href="https://example.com/products/125/industrial-pump"
  />
</head>

اگر Canonical فقط پس از Hydration اضافه شود:

  • HTML اولیه آن را ندارد؛
  • ممکن است Client مقدار متفاوتی تولید کند؛
  • خطاهای Hydration ایجاد شود؛
  • ابزارهای ساده بررسی صفحه خروجی ناقص ببینند.

همچنین State مربوط به Canonical باید میان Navigationها پاک‌سازی شود.

یکی از خطاهای خطرناک در برنامه‌های SSR این است که Canonical صفحه قبلی برای لحظه‌ای یا در شرایط Race Condition روی صفحه بعدی باقی بماند.

یک طراحی مناسب:

const canonicalState = useState<string | null>(
  'canonical-url',
  () => null
)

در شروع Navigation:

canonicalState.value = null

و پس از Resolve شدن Entity:

canonicalState.value = buildCanonicalUrl(entity)

اما باید مراقب State مشترک سمت سرور بود. State هر Request باید کاملاً ایزوله باشد.


خطر تولید Canonical از route.fullPath

نمونه نامناسب:

useHead({
  link: [
    {
      rel: 'canonical',
      href: `${baseUrl}${route.fullPath}`
    }
  ]
})

اگر URL چنین باشد:

/products/125/industrial-pump?utm_source=email

Canonical نیز پارامتر Tracking را حفظ می‌کند.

یا اگر کاربر با Slug اشتباه وارد شده باشد:

/products/125/wrong-slug

Canonical همان URL اشتباه خواهد شد.

روش بهتر:

const canonicalUrl = computed(() =>
  product.value
    ? buildProductCanonicalUrl(product.value)
    : null
)

Canonical برای صفحات حذف‌شده

اگر موجودیت حذف یا غیرفعال شده باشد، Canonical به‌تنهایی راه‌حل مناسبی نیست.

باید وضعیت واقعی منبع مشخص شود.

منبع جایگزین واقعی دارد

اگر صفحه جدید دقیقاً جایگزین صفحه قدیمی است:

301 → Replacement URL

منبع برای همیشه حذف شده است

404 Not Found

یا در مواردی که حذف دائمی و آگاهانه باید صریح‌تر بیان شود:

410 Gone

محصول موقتاً ناموجود است

اگر خود موجودیت همچنان وجود دارد، صفحه می‌تواند 200 OK باقی بماند و وضعیت عدم موجودی را نمایش دهد.

Redirect کردن تمام محصولات ناموجود به دسته‌بندی والد، معمولاً رفتار درستی نیست؛ زیرا دسته‌بندی جایگزین دقیق همان محصول نیست.

Canonical کردن صفحه حذف‌شده به دسته‌بندی نیز فقط برای پنهان‌کردن وضعیت حذف، معماری مناسبی محسوب نمی‌شود.


Canonical به صفحه والد

یکی از خطاهای رایج این است که صفحات کم‌محتوا یا غیرفعال به دسته‌بندی والد Canonical شوند:

/products/125/industrial-pump
Canonical → /categories/pumps

اما صفحه محصول و صفحه دسته‌بندی دو هدف و محتوای متفاوت دارند.

Canonical برای صفحات تکراری یا بسیار مشابه است، نه ابزاری عمومی برای انتقال اهمیت هر صفحه به صفحه قوی‌تر.

اگر صفحه محصول نباید وجود داشته باشد، باید درباره Redirect، 404، 410 یا Noindex تصمیم گرفت.


Canonical Chain

Canonical Chain زمانی رخ می‌دهد که:

Page A → Canonical B
Page B → Canonical C

معماری بهتر این است که تمام نسخه‌ها مستقیماً به URL نهایی اشاره کنند:

Page A → Canonical C
Page B → Canonical C
Page C → Canonical C

Chainها معمولاً نتیجه مهاجرت ناقص یا استفاده از داده قدیمی هستند.

برای جلوگیری از آن‌ها، Canonical باید از وضعیت فعلی Entity ساخته شود، نه از Canonical ذخیره‌شده در رکورد قبلی.


Canonical Loop

Canonical Loop نمونه‌ای مانند این است:

Page A → Canonical B
Page B → Canonical A

این وضعیت نشان می‌دهد سیستم هویت اصلی محتوا را نمی‌داند.

Loop می‌تواند به دلایل زیر ایجاد شود:

  • داده‌های مهاجرت اشتباه؛
  • Ruleهای متناقض؛
  • Canonical مبتنی بر Referer؛
  • Tenant اشتباه؛
  • الگوریتم متفاوت میان Frontend و Backend؛
  • Cache قدیمی.

Canonical Loop باید در تست و Monitoring به‌صورت خودکار شناسایی شود.


Cross-domain Canonical

گاهی یک محتوا در چند Domain منتشر می‌شود و یک نسخه باید به‌عنوان منبع اصلی معرفی شود.

در این حالت Cross-domain Canonical می‌تواند استفاده شود:

<link
  rel="canonical"
  href="https://primary.example.com/article/technical-seo"
/>

اما پیش از آن باید بررسی شود:

  • آیا محتوا واقعاً تکراری یا بسیار مشابه است؟
  • آیا مالک هر دو Domain مشخص است؟
  • آیا مقصد قابل‌ایندکس است؟
  • آیا Canonical مقصد Self-referencing است؟
  • آیا سایت مقصد پاسخ موفق و پایدار دارد؟
  • آیا این تصمیم با قرارداد انتشار محتوا سازگار است؟

Cross-domain Canonical نباید صرفاً برای انتقال اعتبار از یک صفحه نامرتبط استفاده شود.


صفحات چندزبانه و Canonical

Canonical و hreflang دو مسئله متفاوت‌اند.

نسخه فارسی و انگلیسی یک مقاله، اگر ترجمه‌های واقعی و صفحات مستقل باشند، معمولاً باید Self-referencing Canonical داشته باشند:

/fa/technical-seo
Canonical → /fa/technical-seo

/en/technical-seo
Canonical → /en/technical-seo

سپس رابطه زبانی با hreflang اعلام می‌شود.

Canonical کردن همه زبان‌ها به نسخه انگلیسی می‌تواند باعث شود نسخه‌های زبانی به‌عنوان صفحات مستقل در نظر گرفته نشوند.


Canonical و محتوای شخصی‌سازی‌شده

فرض کنیم صفحه برای کاربران مختلف بخشی از اطلاعات را متفاوت نمایش می‌دهد:

  • کاربر مهمان
  • کاربر واردشده
  • عضو ویژه
  • خزنده

اگر URL یکسان است، باید مراقب Cloaking و ناپایداری HTML بود.

Canonical URL احتمالاً همان URL عمومی است، اما:

  • محتوای اصلی باید برای تمام کاربران معنای یکسانی داشته باشد؛
  • Schema نباید اطلاعاتی را اعلام کند که در صفحه عمومی وجود ندارد؛
  • Cache نباید نسخه شخصی‌سازی‌شده را به دیگران نمایش دهد؛
  • Canonical نباید بر اساس وضعیت Login تغییر کند.

Canonical هویت منبع است و معمولاً نباید به Session وابسته باشد.


Canonical و Cache

Cache می‌تواند خطاهای Canonical را به‌صورت گسترده منتشر کند.

برای مثال، اگر Cache Key فقط Path باشد اما Host یا Tenant را در نظر نگیرد:

tenant-a.example.com/products/125
tenant-b.example.com/products/125

ممکن است HTML Tenant اول برای Tenant دوم Serve شود و Canonical اشتباه داشته باشد.

یا اگر Query Parameterها در Cache Key نادیده گرفته شوند، نسخه متفاوتی از صفحه با Metadata نامناسب بازگردانده شود.

موارد مهم در طراحی Cache:

  • Host
  • Protocol
  • Path
  • Query Policy
  • Locale
  • Tenant
  • Authentication State
  • Preview Mode
  • Device Variation در صورت وجود
  • Bot Variation در صورت اجبار

بهتر است Canonical تا حد ممکن از داده پایدار و مستقل از Requestهای شخصی تولید شود.


Canonical در محیط Staging

یکی از خطاهای بسیار خطرناک این است که محیط Staging:

  • قابل Crawl باشد؛
  • Canonical به خودش تولید کند؛
  • یا برعکس، محتوای آزمایشی آن Canonical دامنه Production داشته باشد.

Staging بهتر است با چند لایه محافظت شود:

  • Authentication یا محدودیت شبکه؛
  • noindex;
  • جلوگیری از درج در Sitemap؛
  • عدم انتشار لینک عمومی؛
  • تنظیم Base URL محیط؛
  • تست Configuration در Pipeline.

Canonical Production روی Staging به‌تنهایی راه‌حل ایمن‌سازی Staging نیست.


موتور مرکزی Canonical

در سامانه بزرگ، بهتر است Canonical از یک Policy Engine مرکزی تولید شود.

نمونه مدل:

interface CanonicalContext {
  pageType: PageType
  entityId?: number
  entityName?: string
  routePath: string
  query: Record<string, string | string[]>
  pageNumber?: number
  locale?: string
  host?: string
  isIndexable: boolean
}

خروجی:

interface CanonicalDecision {
  canonicalUrl: string | null
  redirectUrl: string | null
  redirectCode?: 301 | 302 | 307 | 308
  robots: 'index,follow' | 'noindex,follow' | 'noindex,nofollow'
  reason: string
}

نمونه تصمیم:

function resolveProductCanonical(
  context: ProductCanonicalContext
): CanonicalDecision {
  const canonicalUrl = buildProductUrl(context.product)

  if (!context.product.isActive) {
    return {
      canonicalUrl: null,
      redirectUrl: null,
      robots: 'noindex,follow',
      reason: 'Product is inactive'
    }
  }

  if (context.requestedPath !== canonicalUrl.pathname) {
    return {
      canonicalUrl: canonicalUrl.toString(),
      redirectUrl: canonicalUrl.toString(),
      redirectCode: 301,
      robots: 'index,follow',
      reason: 'Requested slug does not match canonical slug'
    }
  }

  return {
    canonicalUrl: canonicalUrl.toString(),
    redirectUrl: null,
    robots: 'index,follow',
    reason: 'Canonical product URL'
  }
}

وجود reason برای Debugging و Log بسیار مفید است.


Canonical نباید فقط یک رشته باشد

در طراحی ساده، Canonical فقط یک URL است.

در طراحی بهتر، Canonical نتیجه یک تصمیم مستند است:

{
  "requestedUrl": "/products/125/wrong-slug?utm_source=email",
  "entityId": 125,
  "entityType": "product",
  "canonicalUrl": "/products/125/industrial-pump",
  "redirectRequired": true,
  "redirectCode": 301,
  "removedParameters": [
    "utm_source"
  ],
  "reason": "Slug mismatch and tracking parameter"
}

این مدل امکان موارد زیر را فراهم می‌کند:

  • Log
  • Debugging
  • تست خودکار
  • Monitoring
  • تحلیل URLهای اشتباه
  • شناسایی لینک‌های داخلی ناسازگار
  • گزارش Migration

روش پیشنهادی تصمیم‌گیری Canonical

یک جریان تصمیم عمومی می‌تواند به این صورت باشد:

1. آیا Route شناخته‌شده است؟
   ├── خیر → 404
   └── بله
        ↓
2. آیا Entity وجود دارد؟
   ├── خیر → 404 یا 410
   └── بله
        ↓
3. آیا Entity قابل انتشار است؟
   ├── خیر → Redirect / 404 / Noindex
   └── بله
        ↓
4. URL رسمی Entity چیست؟
        ↓
5. آیا Host و Protocol صحیح‌اند؟
   ├── خیر → Redirect
   └── بله
        ↓
6. آیا Path و Slug صحیح‌اند؟
   ├── خیر → Redirect
   └── بله
        ↓
7. Query Parameterها چه نوعی هستند؟
   ├── Tracking → حذف از Canonical
   ├── Presentation → حذف از Canonical
   ├── Pagination → تصمیم مستقل
   ├── Indexable Filter → Self Canonical
   ├── Invalid → Block / Redirect / 404
   └── Unknown → Policy
        ↓
8. Canonical Self-referencing تولید شود

تست Canonical

Canonical Architecture بدون تست قابل اعتماد نیست.

Unit Test

برای URL Builder:

describe('buildProductCanonicalUrl', () => {
  it('creates a normalized product URL', () => {
    const result = buildProductCanonicalUrl({
      id: 125,
      name: 'Industrial Pump'
    })

    expect(result).toBe(
      'https://example.com/products/125/industrial-pump'
    )
  })
})

تست Unicode

it.each([
  ['نرم افزار', 'نرم-افزار'],
  ['نرم‌افزار', 'نرم-افزار'],
  ['كالا', 'کالا'],
  ['يادگيري', 'یادگیری']
])('normalizes Persian slug', (input, expected) => {
  expect(toSlug(input)).toBe(expected)
})

Integration Test

Request:
/products/125/wrong-slug

Expected:
301

Location:
/products/125/industrial-pump

SSR Output Test

بررسی HTML اولیه:

<link
  rel="canonical"
  href="https://example.com/products/125/industrial-pump"
/>

Sitemap Consistency Test

تمام URLهای Sitemap باید:

  • پاسخ 200 بدهند؛
  • Redirect نشوند؛
  • noindex نباشند؛
  • Self-referencing Canonical داشته باشند.

Internal Link Test

لینک‌های داخلی نباید به URLهایی اشاره کنند که Redirect می‌شوند.


تست خاص Query Parameter

برای هر Page Type باید جدول تصمیم وجود داشته باشد:

URLCanonicalRobotsRedirect
/products/125/itemخودشindexخیر
/products/125/item?utm=xURL بدون Queryindexاختیاری
/products/125/wrongURL صحیحindex301
/category/pumps?page=2خودش یا Policy مشخصوابسته به سیاستخیر
/category/pumps?sort=priceدسته‌بندی اصلیوابسته به سیاستمعمولاً خیر
/category/pumps?invalid=xبر اساس سیاستnoindex یا خطاممکن

هدف جدول، تبدیل تصمیم‌های مبهم به Contract قابل تست است.


Monitoring Canonical در Production

تست پیش از Deploy کافی نیست.

داده، Route و Configuration ممکن است پس از انتشار تغییر کنند.

سیستم Monitoring می‌تواند به‌صورت دوره‌ای مجموعه‌ای از صفحات نمونه را بررسی کند:

  • Status Code
  • Redirect destination
  • Canonical presence
  • Canonical uniqueness
  • Canonical target status
  • Canonical target indexability
  • Canonical chain
  • Canonical loop
  • Host
  • Protocol
  • Trailing Slash
  • Sitemap consistency
  • Robots
  • Schema URL

نمونه هشدارها:

Canonical target returns 404
Canonical target redirects
Canonical points to another domain
Canonical missing
Multiple canonical tags found
Canonical differs between SSR and rendered DOM
Sitemap URL is noncanonical
Internal link points to redirected URL

تحلیل Log برای Canonical

Log سرور می‌تواند نشان دهد چه URLهای غیرCanonical هنوز Crawl یا استفاده می‌شوند.

برای مثال:

/products/125
/products/125/
/Products/125
/products/125/wrong-slug

اگر پس از اجرای Redirect هنوز حجم زیادی درخواست به نسخه قدیمی وجود دارد، منابع احتمالی عبارت‌اند از:

  • لینک داخلی قدیمی
  • Sitemap قدیمی
  • لینک خارجی
  • Cache
  • اپلیکیشن موبایل
  • Email template
  • API response
  • Schema قدیمی
  • JavaScript bundle قدیمی

Canonical Monitoring فقط بررسی تگ نیست؛ باید مسیر ورود URLهای اشتباه نیز پیدا شود.


Canonical در CI/CD

در Pipeline می‌توان Quality Gate تعریف کرد.

برای مثال، Deploy در شرایط زیر متوقف شود:

  • URL Builder Test شکست خورده است؛
  • بیش از یک Canonical در صفحه وجود دارد؛
  • Canonical نسبی تولید شده است؛
  • Host محیط اشتباه است؛
  • Sitemap شامل URL Redirectشونده است؛
  • Schema URL با Canonical تفاوت دارد؛
  • صفحه Indexable فاقد Canonical است؛
  • Slug Frontend و Backend یکسان نیستند.

به این ترتیب، Canonical از یک تنظیم دستی به بخشی از مهندسی نرم‌افزار تبدیل می‌شود.


خطاهای رایج Canonical

ساخت Canonical از URL فعلی

باعث حفظ Slug و Query اشتباه می‌شود.

Canonical کردن تمام صفحات به Home

این کار ارتباط معنایی صفحات را از بین می‌برد.

Canonical کردن محصولات حذف‌شده به دسته‌بندی

دسته‌بندی لزوماً جایگزین همان محصول نیست.

قرار دادن URLهای Redirectشونده در Sitemap

Sitemap باید URL نهایی را معرفی کند.

استفاده از Canonical به‌جای Redirect در مهاجرت کامل

وقتی URL قدیمی دیگر نباید استفاده شود، Redirect سیگنال عملی‌تری است.

تولید Canonical فقط در Client

HTML اولیه فاقد تصمیم Canonical می‌ماند.

چند Canonical در یک صفحه

ممکن است Theme، Plugin و Application هم‌زمان Canonical تولید کنند.

Canonical وابسته به User یا Session

Canonical باید هویت عمومی منبع را نشان دهد.

Canonical به URL خطادار

مقصد Canonical باید قابل دسترسی و مناسب Index باشد.

Canonical متفاوت با Sitemap و لینک داخلی

این وضعیت سیگنال‌های متناقض ایجاد می‌کند.

ذخیره Canonical کامل در پایگاه داده

اگر Domain یا ساختار Route تغییر کند، هزاران رکورد باید Migrated شوند.

اغلب بهتر است داده پایدار Entity ذخیره و URL بر اساس Policy ساخته شود.

استفاده از Canonical برای پنهان‌کردن معماری بد URL

Canonical جایگزین اصلاح Routing، Redirect و لینک‌سازی نیست.


آیا Canonical باید در پایگاه داده ذخیره شود؟

در بیشتر سامانه‌های Entity-based، ذخیره URL کامل Canonical در پایگاه داده ضروری نیست.

بهتر است موارد پایدار ذخیره شوند:

  • Entity ID
  • Name
  • Preferred Slug در صورت نیاز
  • Locale
  • Route type
  • Historical Slugs در صورت نیاز

سپس URL از Policy ساخته شود.

مزایا:

  • تغییر Domain ساده‌تر است؛
  • مهاجرت Route کنترل‌پذیرتر است؛
  • URLها از یک Rule مشترک پیروی می‌کنند؛
  • داده تکراری کاهش می‌یابد.

اما گاهی Canonical دستی لازم است:

  • محتوای Syndicated
  • Cross-domain Canonical
  • Legacy CMS
  • صفحه‌ای که منبع اصلی آن خارج از سامانه است
  • Override تحریریه

در این حالت بهتر است Override صریح و محدود وجود داشته باشد:

canonicalOverride?: string

و اعتبارسنجی شود که:

  • URL مطلق است؛
  • Protocol مجاز دارد؛
  • Domain مورد اعتماد است؛
  • Loop ایجاد نمی‌کند؛
  • مقصد قابل دسترسی است.

Versioning سیاست URL

URL Policy نیز بخشی از معماری نرم‌افزار است و ممکن است تغییر کند.

برای مثال:

Version 1:
/product/125

Version 2:
/products/125/industrial-pump

مهاجرت باید شامل موارد زیر باشد:

  • Mapping URL قدیم به جدید
  • Redirect دائمی
  • اصلاح لینک‌های داخلی
  • Sitemap جدید
  • Canonical جدید
  • Schema URL جدید
  • Breadcrumb جدید
  • حفظ Redirectهای قدیمی
  • Monitoring
  • تحلیل Log
  • جلوگیری از Chain

اشتباه رایج:

/product/125
   ↓
/products/125
   ↓
/products/125/industrial-pump

بهتر است URL قدیمی مستقیماً به مقصد نهایی Redirect شود:

/product/125
   ↓
/products/125/industrial-pump

چه مدت Redirectها حفظ شوند؟

Redirectهای مربوط به URLهای عمومی و دارای سابقه بهتر است بخشی از دارایی پایدار سایت در نظر گرفته شوند، نه تنظیماتی موقت که پس از چند هفته حذف شوند.

URL قدیمی ممکن است همچنان در این مکان‌ها وجود داشته باشد:

  • Backlink
  • Bookmark
  • ایمیل
  • فایل PDF
  • شبکه اجتماعی
  • نرم‌افزار موبایل
  • Cache مرورگر
  • مستندات قدیمی

حذف زودهنگام Redirect باعث تبدیل ورودی معتبر قبلی به 404 می‌شود.

سیاست نگهداری Redirect باید بر اساس ارزش URL، هزینه نگهداری، Log و برنامه مهاجرت تعیین شود.


طراحی پیشنهادی Canonical Architecture

یک معماری قابل نگهداری می‌تواند اجزای زیر را داشته باشد:

┌────────────────────────────┐
│ Incoming HTTP Request      │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ Route Parser               │
│ ID / Slug / Query / Locale │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ Entity Resolver            │
│ Stable Domain Identity     │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ URL Policy Engine          │
│ Host / Path / Slug / Query │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ Canonical Decision         │
│ Redirect / Canonical / 404 │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ SSR SEO Orchestrator       │
│ Head / Robots / Schema     │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ Shared URL Consumers       │
│ Sitemap / Links / Graph    │
└──────────────┬─────────────┘
               ↓
┌────────────────────────────┐
│ Tests & Monitoring         │
└────────────────────────────┘

مسئولیت هر لایه

Domain Layer

  • هویت موجودیت
  • وضعیت فعال یا حذف‌شده
  • روابط کسب‌وکار
  • نام رسمی
  • Locale
  • جایگزین احتمالی

URL Policy

  • Path
  • Slug
  • Host
  • Protocol
  • Query
  • Trailing Slash
  • Case
  • نسخه Route

Routing Layer

  • Parse درخواست
  • Resolve Entity
  • تشخیص URL ناسازگار
  • Redirect
  • 404 یا 410

SEO Orchestrator

  • Canonical tag
  • Robots
  • Open Graph URL
  • Schema URL
  • Metadata

Sitemap Generator

  • فقط URLهای Canonical
  • فقط صفحات Indexable
  • حذف Redirect و خطا

Internal Link Builder

  • لینک مستقیم به URL نهایی
  • عدم ایجاد Redirect داخلی

Monitoring

  • کشف خطا و Regression
  • تحلیل Targetها
  • هشدار

چک‌لیست طراحی Canonical

پیش از انتشار هر Page Type باید به این پرسش‌ها پاسخ داده شود:

هویت

  • Entity اصلی صفحه چیست؟
  • شناسه پایدار آن چیست؟
  • آیا چند Entity در صفحه وجود دارد؟
  • mainEntity کدام است؟

URL

  • URL رسمی چگونه تولید می‌شود؟
  • ID در URL وجود دارد؟
  • Slug چه نقشی دارد؟
  • تغییر نام چه اثری دارد؟
  • تغییر دسته‌بندی چه اثری دارد؟
  • Case Policy چیست؟
  • Trailing Slash Policy چیست؟

Request

  • Slug اشتباه چه رفتاری دارد؟
  • Query ناشناخته چه رفتاری دارد؟
  • Host اشتباه چه رفتاری دارد؟
  • HTTP چه رفتاری دارد؟
  • URL قدیمی چه رفتاری دارد؟

Canonical

  • Self-referencing است؟
  • URL مطلق است؟
  • مقصد پاسخ 200 دارد؟
  • مقصد Indexable است؟
  • Chain یا Loop ندارد؟
  • در SSR تولید می‌شود؟

سایر سیگنال‌ها

  • Sitemap همان URL را دارد؟
  • لینک‌های داخلی همان URL را دارند؟
  • Breadcrumb همان URL را دارد؟
  • Schema همان URL را دارد؟
  • Open Graph همان URL را دارد؟

عملیات

  • Unit Test وجود دارد؟
  • Integration Test وجود دارد؟
  • Monitoring وجود دارد؟
  • Redirectها Log می‌شوند؟
  • Migration Plan وجود دارد؟

یک نمونه سیاست کامل برای صفحه محصول

فرض کنیم URL رسمی محصول چنین است:

https://example.com/products/{id}/{slug}

قواعد:

  1. id هویت اصلی Entity است.
  2. slug از نام Normalize‌شده محصول ساخته می‌شود.
  3. Path همیشه Lowercase است.
  4. Trailing Slash استفاده نمی‌شود.
  5. HTTPS اجباری است.
  6. Host اصلی example.com است.
  7. Tracking Parameterها از Canonical حذف می‌شوند.
  8. Slug اشتباه با 301 اصلاح می‌شود.
  9. URL بدون Slug با 301 به URL کامل منتقل می‌شود.
  10. محصول حذف‌شده 404 یا 410 دریافت می‌کند.
  11. محصول دارای جایگزین واقعی با 301 منتقل می‌شود.
  12. Sitemap فقط URL کامل را ثبت می‌کند.
  13. لینک‌های داخلی مستقیماً به URL کامل اشاره می‌کنند.
  14. Canonical در SSR تولید می‌شود.
  15. Schema و Open Graph از همان URL استفاده می‌کنند.

نمونه‌ها:

HTTP:
http://example.com/products/125/industrial-pump
→ 301 HTTPS

Host:
https://www.example.com/products/125/industrial-pump
→ 301 Main Host

Missing Slug:
https://example.com/products/125
→ 301 Full URL

Wrong Slug:
https://example.com/products/125/wrong
→ 301 Correct URL

Tracking:
https://example.com/products/125/industrial-pump?utm_source=email
→ 200
Canonical بدون Query

Canonical:
https://example.com/products/125/industrial-pump
→ 200 Self-referencing

این همان تفاوت میان «داشتن Canonical» و «داشتن Canonical Architecture» است.


جمع‌بندی

Canonical فقط یک تگ در بخش <head> صفحه نیست.

این تگ، خروجی نهایی مجموعه‌ای از تصمیم‌های عمیق‌تر است:

Domain Identity
      ↓
URL Policy
      ↓
Route Resolution
      ↓
Slug Normalization
      ↓
Redirect Decision
      ↓
Canonical Generation
      ↓
Sitemap
      ↓
Internal Linking
      ↓
Schema and Metadata
      ↓
Testing and Monitoring

اگر هرکدام از این لایه‌ها URL متفاوتی را به‌عنوان نسخه اصلی معرفی کنند، سایت سیگنال‌های متناقض ارسال خواهد کرد.

یک معماری Canonical حرفه‌ای باید بتواند به‌صورت شفاف پاسخ دهد:

  • این صفحه نماینده کدام Entity است؟
  • URL پایدار و رسمی آن چیست؟
  • URLهای اشتباه یا قدیمی چگونه مدیریت می‌شوند؟
  • Query Parameterها چه معنایی دارند؟
  • چه زمانی Redirect و چه زمانی Canonical استفاده می‌شود؟
  • Sitemap و لینک‌های داخلی به کدام URL اشاره می‌کنند؟
  • چگونه این تصمیم‌ها در SSR، Cache و Microserviceها پایدار می‌مانند؟
  • و چگونه از بازگشت خطاها در Deployهای بعدی جلوگیری می‌شود؟

مهم‌ترین اصل این است:

سایت نباید فقط از طریق یک تگ، URL ترجیحی خود را اعلام کند؛ تمام معماری سایت باید همان URL را به‌عنوان نسخه اصلی بشناسد و استفاده کند.

در مقاله بعدی این مجموعه، به طراحی یک موتور متمرکز Metadata خواهیم پرداخت و بررسی خواهیم کرد که چگونه Title، Description، Canonical، Robots، Open Graph و Structured Data را می‌توان بر اساس Page Type، Entity، Route Context و سیاست‌های کسب‌وکار، به‌صورت یکپارچه و قابل تست تولید کرد.