implement canonical URL handling with server and client-side support, add middleware for pathname access, and create hooks for dynamic canonical URL injection
This commit is contained in:
+14
-1
@@ -8,11 +8,15 @@ import "react-toastify/dist/ReactToastify.css";
|
||||
import CustomToastify from "./CustomToastify";
|
||||
import { ProvinceProvider } from "@/context/ProvinceProvider";
|
||||
import { getStateInfo } from "@/lib/getStateInfo";
|
||||
import { getCanonicalUrl } from "@/lib/getCanonicalUrl";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const { matchedCity } = getStateInfo();
|
||||
console.log("Matched City in generateMetadata:", matchedCity);
|
||||
|
||||
return {
|
||||
const canonicalUrl = getCanonicalUrl();
|
||||
|
||||
const baseMetadata = {
|
||||
title: matchedCity ? matchedCity.title : "نوبت724",
|
||||
description: matchedCity
|
||||
? matchedCity.description
|
||||
@@ -24,6 +28,15 @@ export async function generateMetadata() {
|
||||
images: ["https://www.nobat724.com/assets/images/logo.png"],
|
||||
},
|
||||
};
|
||||
|
||||
// Add canonical URL only if we're not on the main domain and matchedCity domain is not nobat724.com
|
||||
if (canonicalUrl && (!matchedCity || matchedCity.domain !== 'nobat724.com')) {
|
||||
baseMetadata.alternates = {
|
||||
canonical: canonicalUrl,
|
||||
};
|
||||
}
|
||||
|
||||
return baseMetadata;
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { getCanonicalUrlClient } from '@/lib/getCanonicalUrl';
|
||||
|
||||
/**
|
||||
* Client-side canonical URL handler
|
||||
* Use this component in pages that need dynamic canonical URLs
|
||||
* that can't be determined at build time or in generateMetadata
|
||||
*/
|
||||
export default function CanonicalHandler() {
|
||||
useEffect(() => {
|
||||
// Only run on client side
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const canonicalUrl = getCanonicalUrlClient();
|
||||
|
||||
if (canonicalUrl) {
|
||||
// Check if canonical link already exists
|
||||
let canonicalLink = document.querySelector('link[rel="canonical"]');
|
||||
|
||||
if (canonicalLink) {
|
||||
// Update existing canonical link
|
||||
canonicalLink.href = canonicalUrl;
|
||||
} else {
|
||||
// Create new canonical link
|
||||
canonicalLink = document.createElement('link');
|
||||
canonicalLink.rel = 'canonical';
|
||||
canonicalLink.href = canonicalUrl;
|
||||
document.head.appendChild(canonicalLink);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return null; // This component doesn't render anything
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getCanonicalUrlClient } from '@/lib/getCanonicalUrl';
|
||||
|
||||
/**
|
||||
* Custom hook to get canonical URL on client side
|
||||
* Useful for client components that need to know the canonical URL
|
||||
*/
|
||||
export function useCanonicalUrl() {
|
||||
const [canonicalUrl, setCanonicalUrl] = useState(null);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const canonical = getCanonicalUrlClient();
|
||||
setCanonicalUrl(canonical);
|
||||
setIsLoaded(true);
|
||||
}, []);
|
||||
|
||||
return { canonicalUrl, isLoaded };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to automatically inject canonical URL into document head
|
||||
* Use this in client components where you need automatic canonical URL injection
|
||||
*/
|
||||
export function useCanonicalUrlInjection() {
|
||||
const { canonicalUrl, isLoaded } = useCanonicalUrl();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoaded || !canonicalUrl) return;
|
||||
|
||||
// Check if canonical link already exists
|
||||
let canonicalLink = document.querySelector('link[rel="canonical"]');
|
||||
|
||||
if (canonicalLink) {
|
||||
// Update existing canonical link
|
||||
canonicalLink.href = canonicalUrl;
|
||||
} else {
|
||||
// Create new canonical link
|
||||
canonicalLink = document.createElement('link');
|
||||
canonicalLink.rel = 'canonical';
|
||||
canonicalLink.href = canonicalUrl;
|
||||
document.head.appendChild(canonicalLink);
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
const link = document.querySelector(`link[rel="canonical"][href="${canonicalUrl}"]`);
|
||||
if (link) {
|
||||
link.remove();
|
||||
}
|
||||
};
|
||||
}, [canonicalUrl, isLoaded]);
|
||||
|
||||
return { canonicalUrl, isLoaded };
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { headers } from "next/headers";
|
||||
import citiesData from "@/data/city.json";
|
||||
|
||||
const MAIN_DOMAIN = "https://nobat724.com";
|
||||
|
||||
/**
|
||||
* Determines if the current domain should have a canonical tag
|
||||
* and returns the canonical URL if needed
|
||||
*/
|
||||
export function getCanonicalUrl() {
|
||||
try {
|
||||
const headersList = headers();
|
||||
const host = headersList.get("host") || "";
|
||||
const pathname = headersList.get("x-pathname") || "/";
|
||||
|
||||
// Normalize host (remove www and convert to lowercase)
|
||||
const normalizedHost = host.toLowerCase().replace(/^www\./, "");
|
||||
|
||||
// Check if current domain is the main domain
|
||||
if (normalizedHost === "nobat724.com") {
|
||||
return null; // No canonical needed for main domain
|
||||
}
|
||||
|
||||
// Check if this is a subdomain/different domain from our city data
|
||||
const subdomain = normalizedHost.split(".")[0];
|
||||
const matchedCity = citiesData.find((city) => {
|
||||
const cityDomain = city.domain.toLowerCase();
|
||||
return cityDomain.includes(subdomain) || cityDomain === normalizedHost;
|
||||
});
|
||||
|
||||
// If we found a matching city or any non-main domain, canonicalize to main domain
|
||||
if (matchedCity || normalizedHost !== "nobat724.com") {
|
||||
// Ensure pathname starts with /
|
||||
const cleanPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
||||
return `${MAIN_DOMAIN}${cleanPathname}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error generating canonical URL:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side version for dynamic routes or client components
|
||||
*/
|
||||
export function getCanonicalUrlClient() {
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
try {
|
||||
const host = window.location.host;
|
||||
const pathname = window.location.pathname;
|
||||
|
||||
// Normalize host (remove www and convert to lowercase)
|
||||
const normalizedHost = host.toLowerCase().replace(/^www\./, "");
|
||||
|
||||
// Check if current domain is the main domain
|
||||
if (normalizedHost === "nobat724.com") {
|
||||
return null; // No canonical needed for main domain
|
||||
}
|
||||
|
||||
// Check if this is a subdomain/different domain from our city data
|
||||
const subdomain = normalizedHost.split(".")[0];
|
||||
const matchedCity = citiesData.find((city) => {
|
||||
const cityDomain = city.domain.toLowerCase();
|
||||
return cityDomain.includes(subdomain) || cityDomain === normalizedHost;
|
||||
});
|
||||
|
||||
// If we found a matching city or any non-main domain, canonicalize to main domain
|
||||
if (matchedCity || normalizedHost !== "nobat724.com") {
|
||||
return `${MAIN_DOMAIN}${pathname}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error generating client canonical URL:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export function middleware(request) {
|
||||
const response = NextResponse.next();
|
||||
|
||||
// Add pathname to headers so we can access it in getCanonicalUrl
|
||||
response.headers.set('x-pathname', request.nextUrl.pathname);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
*/
|
||||
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user