feat: mejorar el sitemap añadiendo rutas legales y asegurando la correcta codificación XML
This commit is contained in:
@@ -8,9 +8,14 @@ export default function robots(): MetadataRoute.Robots {
|
|||||||
allow: "/",
|
allow: "/",
|
||||||
disallow: [
|
disallow: [
|
||||||
"/admin/",
|
"/admin/",
|
||||||
"/dashboard/",
|
"/user/",
|
||||||
"/my-appointments/",
|
"/messenger/",
|
||||||
"/offer-appointments/",
|
"/landing/dashboard/",
|
||||||
|
"/landing/my-appointments/",
|
||||||
|
"/landing/current-plan/",
|
||||||
|
"/landing/upgrade-plan/",
|
||||||
|
"/landing/pending-ratings/",
|
||||||
|
"/landing/subscription/",
|
||||||
"/landing/recover-account/",
|
"/landing/recover-account/",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ import { PublicServicesResults } from "@models/Service.model";
|
|||||||
import { sliderFrames } from "@core/app/Services/SliderFrames";
|
import { sliderFrames } from "@core/app/Services/SliderFrames";
|
||||||
import { getAllVideos } from "@core/app/landing/help/videoData";
|
import { getAllVideos } from "@core/app/landing/help/videoData";
|
||||||
|
|
||||||
|
// Escape text content so the sitemap is always well-formed XML
|
||||||
|
// (URLs with query strings, service names/descriptions, etc.).
|
||||||
|
function escapeXml(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar";
|
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar";
|
||||||
|
|
||||||
@@ -83,6 +94,25 @@ export async function GET() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Legal pages (rarely change, low priority)
|
||||||
|
const legalRoutes: SitemapRoute[] = [
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/landing/terms`,
|
||||||
|
changeFrequency: "yearly",
|
||||||
|
priority: 0.3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/landing/privacy`,
|
||||||
|
changeFrequency: "yearly",
|
||||||
|
priority: 0.3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/landing/cookies`,
|
||||||
|
changeFrequency: "yearly",
|
||||||
|
priority: 0.3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let serviceRoutes: SitemapRoute[] = [];
|
let serviceRoutes: SitemapRoute[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -99,19 +129,12 @@ export async function GET() {
|
|||||||
if (servicesResult && servicesResult.data) {
|
if (servicesResult && servicesResult.data) {
|
||||||
totalPages = servicesResult.pages || 1;
|
totalPages = servicesResult.pages || 1;
|
||||||
|
|
||||||
|
// Image sitemap entries are omitted: service images may be hosted
|
||||||
|
// on arbitrary external domains, which the image extension can't verify.
|
||||||
const newRoutes = servicesResult.data.map((service) => ({
|
const newRoutes = servicesResult.data.map((service) => ({
|
||||||
url: `${baseUrl}/landing/service/${service.id}`,
|
url: `${baseUrl}/landing/service/${service.id}`,
|
||||||
changeFrequency: "weekly",
|
changeFrequency: "weekly",
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: service.image.startsWith("http")
|
|
||||||
? service.image
|
|
||||||
: `${process.env.NEXT_PUBLIC_API_URL}${service.image}`,
|
|
||||||
title: service.name,
|
|
||||||
caption: service.description,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
serviceRoutes = [...serviceRoutes, ...newRoutes];
|
serviceRoutes = [...serviceRoutes, ...newRoutes];
|
||||||
@@ -126,7 +149,7 @@ export async function GET() {
|
|||||||
console.error("Error generating sitemap:", error);
|
console.error("Error generating sitemap:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRoutes = [...staticRoutes, ...videoRoutes, ...authRoutes, ...marketingRoutes, ...serviceRoutes];
|
const allRoutes = [...staticRoutes, ...videoRoutes, ...authRoutes, ...marketingRoutes, ...legalRoutes, ...serviceRoutes];
|
||||||
|
|
||||||
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
|
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
@@ -135,18 +158,18 @@ export async function GET() {
|
|||||||
.map((route) => {
|
.map((route) => {
|
||||||
return `
|
return `
|
||||||
<url>
|
<url>
|
||||||
<loc>${route.url}</loc>
|
<loc>${escapeXml(route.url)}</loc>
|
||||||
${route.lastModified ? `<lastmod>${route.lastModified}</lastmod>` : ""}
|
${route.lastModified ? `<lastmod>${escapeXml(route.lastModified)}</lastmod>` : ""}
|
||||||
<changefreq>${route.changeFrequency}</changefreq>
|
<changefreq>${escapeXml(route.changeFrequency)}</changefreq>
|
||||||
<priority>${route.priority}</priority>
|
<priority>${route.priority}</priority>
|
||||||
${route.images
|
${route.images
|
||||||
? route.images
|
? route.images
|
||||||
.map(
|
.map(
|
||||||
(img) => `
|
(img) => `
|
||||||
<image:image>
|
<image:image>
|
||||||
<image:loc>${img.url}</image:loc>
|
<image:loc>${escapeXml(img.url)}</image:loc>
|
||||||
<image:title><![CDATA[${img.title}]]></image:title>
|
<image:title>${escapeXml(img.title)}</image:title>
|
||||||
<image:caption><![CDATA[${img.caption}]]></image:caption>
|
<image:caption>${escapeXml(img.caption)}</image:caption>
|
||||||
</image:image>`
|
</image:image>`
|
||||||
)
|
)
|
||||||
.join("")
|
.join("")
|
||||||
@@ -159,7 +182,10 @@ export async function GET() {
|
|||||||
|
|
||||||
return new NextResponse(sitemapXml, {
|
return new NextResponse(sitemapXml, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/xml",
|
"Content-Type": "application/xml; charset=utf-8",
|
||||||
|
// Regenerated on every request (fetch no-store): cache at the edge/CDN
|
||||||
|
// so crawlers and browsers don't re-hit the services API each time.
|
||||||
|
"Cache-Control": "public, max-age=3600, s-maxage=86400, stale-while-revalidate=86400",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user