diff --git a/lib/getCanonicalUrl.js b/lib/getCanonicalUrl.js index 1973458..a5f0ba9 100644 --- a/lib/getCanonicalUrl.js +++ b/lib/getCanonicalUrl.js @@ -1,9 +1,12 @@ import { headers } from "next/headers"; -// استراتژی چند-دامنه‌ای: هر دامنه‌ی شهری یک property مستقل و first-class است. -// هر صفحه self-canonical روی هاست جاری می‌گیرد؛ هرگز به دامنه‌ی اصلی canonical نمی‌دهیم. +// استراتژی canonical: محتوای همه‌ی دامنه‌های شهری از nobat724.com تغذیه می‌شود — +// پس canonical هر صفحه (روی هر دامنه‌ای) به همان مسیر روی دامنه‌ی اصلی اشاره می‌کند. +// دامنه‌ی اصلی هم self-canonical می‌گیرد. +// توجه: buildCanonicalUrl per-host است و برای JSON-LD/sitemap/robots استفاده می‌شود — به آن دست نزن. const DEFAULT_HOST = "nobat724.com"; +const MAIN_ORIGIN = "https://nobat724.com"; export function normalizeHost(rawHost) { return (rawHost || DEFAULT_HOST).toLowerCase().replace(/^www\./, ""); @@ -34,15 +37,18 @@ export async function getRequestOrigin() { } } +export function buildMainCanonicalUrl(pathname) { + return `${MAIN_ORIGIN}${buildCanonicalPath(pathname)}`; +} + export async function getCanonicalUrl() { try { const headersList = await headers(); - const host = headersList.get("host"); const pathname = headersList.get("x-pathname"); // مسیرهای خارج از matcher میدل‌ور (panel/dashboard/login) این هدر را ندارند — // آن‌ها noindex هستند و نباید canonical جعلی به ریشه بگیرند. if (!pathname) return null; - return buildCanonicalUrl(host, pathname); + return buildMainCanonicalUrl(pathname); } catch (error) { console.error("Error generating canonical URL:", error); return null; diff --git a/lib/multiDomainClient.test.js b/lib/multiDomainClient.test.js index 5c7b8bf..e1bd565 100644 --- a/lib/multiDomainClient.test.js +++ b/lib/multiDomainClient.test.js @@ -1,6 +1,11 @@ import { describe, it, expect } from 'vitest'; import { getStateInfoClient } from '@/lib/getStateInfoClient'; -import { buildCanonicalUrl, buildCanonicalPath, normalizeHost } from '@/lib/getCanonicalUrl'; +import { + buildCanonicalUrl, + buildCanonicalPath, + buildMainCanonicalUrl, + normalizeHost, +} from '@/lib/getCanonicalUrl'; function setLocation(loc) { Object.defineProperty(window, 'location', { value: loc, writable: true, configurable: true }); @@ -56,3 +61,18 @@ describe('buildCanonicalUrl (self-canonical per-host)', () => { expect(buildCanonicalUrl(null, '/x')).toBe('https://nobat724.com/x'); }); }); + +describe('buildMainCanonicalUrl (canonical متادیتا → دامنه‌ی اصلی)', () => { + it('هر مسیری → همان مسیر روی nobat724.com', () => { + expect(buildMainCanonicalUrl('/doctors')).toBe('https://nobat724.com/doctors'); + expect(buildMainCanonicalUrl('/doctor/abc-123')).toBe('https://nobat724.com/doctor/abc-123'); + }); + + it('ریشه → با trailing slash', () => { + expect(buildMainCanonicalUrl('/')).toBe('https://nobat724.com/'); + }); + + it('query و trailing slash حذف می‌شوند', () => { + expect(buildMainCanonicalUrl('/doctors/?specialty=قلب')).toBe('https://nobat724.com/doctors'); + }); +}); diff --git a/next.config.js b/next.config.js index 5ddcacf..06bda8f 100644 --- a/next.config.js +++ b/next.config.js @@ -2,34 +2,17 @@ const nextConfig = { // Add other configurations below reactStrictMode: true, + // متادیتا (canonical/OG/keywords) باید داخل رندر شود نه استریم در . + // از Next 15.2 استریم متادیتا پیش‌فرض است؛ این regex همه‌ی UAها را blocking می‌کند. + htmlLimitedBots: /.*/, images: { remotePatterns: [ { protocol: 'https', - hostname: 'api.clinic-pro.ir', - }, - { - protocol: 'https', - hostname: 'clinicpro.liara.run', - }, - { - protocol: 'http', hostname: 'clinic-pro.ir', }, { protocol: 'https', - hostname: 'back-dev.clinic-pro.ir', - }, - { - protocol: 'https', - hostname: 'clinic-pro-back.ddev.site', - }, - { - protocol: 'https', - hostname: 'clinic-pro.ddev.site', - }, - { - protocol: 'http', hostname: 'clinic-pro.ddev.site', }, ],