feat: add SEO configuration with dynamic robots.txt and sitemap.xml generation
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
|
// Get current domain from headers
|
||||||
|
function getCurrentDomain() {
|
||||||
|
const headersList = headers();
|
||||||
|
const host = headersList.get('host');
|
||||||
|
return host || 'nobat724.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get base URL based on current domain
|
||||||
|
function getBaseUrl(domain) {
|
||||||
|
// Remove port number if present (for development)
|
||||||
|
const cleanDomain = domain.replace(/:\d+$/, '');
|
||||||
|
|
||||||
|
// For localhost domains, use http, otherwise https
|
||||||
|
const protocol = cleanDomain.includes('localhost') ? 'http' : 'https';
|
||||||
|
|
||||||
|
return `${protocol}://${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function robots() {
|
||||||
|
const domain = getCurrentDomain();
|
||||||
|
const baseUrl = getBaseUrl(domain);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rules: {
|
||||||
|
userAgent: '*',
|
||||||
|
allow: '/',
|
||||||
|
disallow: '/admin',
|
||||||
|
},
|
||||||
|
sitemap: `${baseUrl}/sitemap.xml`,
|
||||||
|
};
|
||||||
|
}
|
||||||
+252
@@ -0,0 +1,252 @@
|
|||||||
|
import { headers } from 'next/headers';
|
||||||
|
import cityData from '../data/city.json';
|
||||||
|
import specialtyData from '../data/specialties.json';
|
||||||
|
import stateData from '../data/state.json';
|
||||||
|
import {
|
||||||
|
DOMAIN_CONFIG,
|
||||||
|
isMainDomain,
|
||||||
|
createSitemapUrl,
|
||||||
|
createSitemapEntry,
|
||||||
|
limitArray,
|
||||||
|
getBaseUrl
|
||||||
|
} from '../utils/sitemap';
|
||||||
|
|
||||||
|
// Get current domain from headers
|
||||||
|
function getCurrentDomain() {
|
||||||
|
const headersList = headers();
|
||||||
|
const host = headersList.get('host');
|
||||||
|
return host || DOMAIN_CONFIG.MAIN_DOMAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get city data based on domain
|
||||||
|
function getCityByDomain(domain) {
|
||||||
|
return cityData.find(city => city.domain === domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get state name by province_id
|
||||||
|
function getStateById(provinceId) {
|
||||||
|
return stateData.find(state => state.id === provinceId)?.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate static pages URLs
|
||||||
|
function getStaticPages(baseUrl) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: baseUrl,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'daily',
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/about-us`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly',
|
||||||
|
priority: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/contact-us`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly',
|
||||||
|
priority: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/blogs`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly',
|
||||||
|
priority: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/doctors`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'daily',
|
||||||
|
priority: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/clinics`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'daily',
|
||||||
|
priority: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/specialties`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly',
|
||||||
|
priority: 0.9,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate doctor search URLs based on domain
|
||||||
|
function getDoctorSearchUrls(domain, baseUrl) {
|
||||||
|
const urls = [];
|
||||||
|
|
||||||
|
if (isMainDomain(domain)) {
|
||||||
|
// For main domain, include all cities and limited specialties
|
||||||
|
cityData.forEach(city => {
|
||||||
|
const stateName = getStateById(city.province_id);
|
||||||
|
if (stateName) {
|
||||||
|
// City-only search
|
||||||
|
const cityUrl = createSitemapUrl(baseUrl, '/doctors', {
|
||||||
|
city: city.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(cityUrl, 0.8));
|
||||||
|
|
||||||
|
// City + specialty combinations (limited to prevent too many URLs)
|
||||||
|
const limitedSpecialties = limitArray(specialtyData, DOMAIN_CONFIG.MAX_SPECIALTY_COMBINATIONS);
|
||||||
|
limitedSpecialties.forEach(specialty => {
|
||||||
|
const specialtyUrl = createSitemapUrl(baseUrl, '/doctors', {
|
||||||
|
specialty: specialty.name,
|
||||||
|
city: city.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(specialtyUrl, 0.7));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Specialty-only searches
|
||||||
|
specialtyData.forEach(specialty => {
|
||||||
|
const specialtyUrl = createSitemapUrl(baseUrl, '/doctors', {
|
||||||
|
specialty: specialty.name
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(specialtyUrl, 0.8));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For city-specific domains, only include that city's data
|
||||||
|
const currentCity = getCityByDomain(domain);
|
||||||
|
if (currentCity) {
|
||||||
|
const stateName = getStateById(currentCity.province_id);
|
||||||
|
if (stateName) {
|
||||||
|
// City-only search
|
||||||
|
const cityUrl = createSitemapUrl(baseUrl, '/doctors', {
|
||||||
|
city: currentCity.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(cityUrl, 0.8));
|
||||||
|
|
||||||
|
// City + specialty combinations
|
||||||
|
specialtyData.forEach(specialty => {
|
||||||
|
const specialtyUrl = createSitemapUrl(baseUrl, '/doctors', {
|
||||||
|
specialty: specialty.name,
|
||||||
|
city: currentCity.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(specialtyUrl, 0.7));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate clinic search URLs
|
||||||
|
function getClinicSearchUrls(domain, baseUrl) {
|
||||||
|
const urls = [];
|
||||||
|
|
||||||
|
if (isMainDomain(domain)) {
|
||||||
|
// For main domain, include all cities
|
||||||
|
cityData.forEach(city => {
|
||||||
|
const stateName = getStateById(city.province_id);
|
||||||
|
if (stateName) {
|
||||||
|
const clinicUrl = createSitemapUrl(baseUrl, '/clinics', {
|
||||||
|
city: city.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(clinicUrl, 0.8));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For city-specific domains, only include that city
|
||||||
|
const currentCity = getCityByDomain(domain);
|
||||||
|
if (currentCity) {
|
||||||
|
const stateName = getStateById(currentCity.province_id);
|
||||||
|
if (stateName) {
|
||||||
|
const clinicUrl = createSitemapUrl(baseUrl, '/clinics', {
|
||||||
|
city: currentCity.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(clinicUrl, 0.8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate specialty pages URLs
|
||||||
|
function getSpecialtyUrls(domain, baseUrl) {
|
||||||
|
const urls = [];
|
||||||
|
|
||||||
|
if (isMainDomain(domain)) {
|
||||||
|
// For main domain, include all specialties with main cities only (to prevent too many URLs)
|
||||||
|
const mainCities = cityData.filter(city => DOMAIN_CONFIG.MAIN_CITIES.includes(city.id));
|
||||||
|
|
||||||
|
specialtyData.forEach(specialty => {
|
||||||
|
// Base specialty page
|
||||||
|
const specialtyUrl = createSitemapUrl(baseUrl, `/specialties/${encodeURIComponent(specialty.name)}`);
|
||||||
|
urls.push(createSitemapEntry(specialtyUrl, 0.8));
|
||||||
|
|
||||||
|
// Specialty + city combinations (only for main cities)
|
||||||
|
mainCities.forEach(city => {
|
||||||
|
const stateName = getStateById(city.province_id);
|
||||||
|
if (stateName) {
|
||||||
|
const citySpecialtyUrl = createSitemapUrl(baseUrl, `/specialties/${encodeURIComponent(specialty.name)}`, {
|
||||||
|
city: city.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(citySpecialtyUrl, 0.7));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For city-specific domains, include specialties with that city only
|
||||||
|
const currentCity = getCityByDomain(domain);
|
||||||
|
if (currentCity) {
|
||||||
|
const stateName = getStateById(currentCity.province_id);
|
||||||
|
if (stateName) {
|
||||||
|
specialtyData.forEach(specialty => {
|
||||||
|
const specialtyUrl = createSitemapUrl(baseUrl, `/specialties/${encodeURIComponent(specialty.name)}`, {
|
||||||
|
city: currentCity.name,
|
||||||
|
state: stateName
|
||||||
|
});
|
||||||
|
urls.push(createSitemapEntry(specialtyUrl, 0.7));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function sitemap() {
|
||||||
|
try {
|
||||||
|
const domain = getCurrentDomain();
|
||||||
|
const baseUrl = getBaseUrl(domain);
|
||||||
|
|
||||||
|
// Get all URLs
|
||||||
|
const staticPages = getStaticPages(baseUrl);
|
||||||
|
const doctorSearchUrls = getDoctorSearchUrls(domain, baseUrl);
|
||||||
|
const clinicSearchUrls = getClinicSearchUrls(domain, baseUrl);
|
||||||
|
const specialtyUrls = getSpecialtyUrls(domain, baseUrl);
|
||||||
|
|
||||||
|
// Combine all URLs
|
||||||
|
const allUrls = [
|
||||||
|
...staticPages,
|
||||||
|
...doctorSearchUrls,
|
||||||
|
...clinicSearchUrls,
|
||||||
|
...specialtyUrls,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Remove duplicates based on URL
|
||||||
|
const uniqueUrls = allUrls.filter((item, index, arr) =>
|
||||||
|
arr.findIndex(i => i.url === item.url) === index
|
||||||
|
);
|
||||||
|
|
||||||
|
return uniqueUrls;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating sitemap:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// Utility functions for sitemap generation
|
||||||
|
export const DOMAIN_CONFIG = {
|
||||||
|
MAIN_DOMAIN: 'nobat724.com',
|
||||||
|
MAX_SPECIALTY_COMBINATIONS: 20, // Limit specialty combinations to prevent too many URLs
|
||||||
|
MAIN_CITIES: ['108', '104', '111', '117', '113'], // Tehran, Isfahan, Mashhad, Shiraz, Ahvaz
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isMainDomain(domain) {
|
||||||
|
return domain === DOMAIN_CONFIG.MAIN_DOMAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeUrlParam(param) {
|
||||||
|
return encodeURIComponent(param || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSitemapUrl(baseUrl, path, params = {}) {
|
||||||
|
let url = `${baseUrl}${path}`;
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value) {
|
||||||
|
searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryString = searchParams.toString();
|
||||||
|
return queryString ? `${url}?${queryString}` : url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSitemapEntry(url, priority = 0.8, changeFrequency = 'weekly') {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency,
|
||||||
|
priority,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function limitArray(array, limit) {
|
||||||
|
return limit > 0 ? array.slice(0, limit) : array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBaseUrl(domain) {
|
||||||
|
return `https://${domain}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user