feat: mejorar el sitemap añadiendo rutas legales y asegurando la correcta codificación XML

This commit is contained in:
2026-09-16 10:04:18 -03:00
parent ae63d0dea0
commit 8c1089df41
2 changed files with 52 additions and 21 deletions
+8 -3
View File
@@ -8,9 +8,14 @@ export default function robots(): MetadataRoute.Robots {
allow: "/",
disallow: [
"/admin/",
"/dashboard/",
"/my-appointments/",
"/offer-appointments/",
"/user/",
"/messenger/",
"/landing/dashboard/",
"/landing/my-appointments/",
"/landing/current-plan/",
"/landing/upgrade-plan/",
"/landing/pending-ratings/",
"/landing/subscription/",
"/landing/recover-account/",
],
},
+43 -17
View File
@@ -4,6 +4,17 @@ import { PublicServicesResults } from "@models/Service.model";
import { sliderFrames } from "@core/app/Services/SliderFrames";
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, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
export async function GET() {
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[] = [];
try {
@@ -99,19 +129,12 @@ export async function GET() {
if (servicesResult && servicesResult.data) {
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) => ({
url: `${baseUrl}/landing/service/${service.id}`,
changeFrequency: "weekly",
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];
@@ -126,7 +149,7 @@ export async function GET() {
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"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
@@ -135,18 +158,18 @@ export async function GET() {
.map((route) => {
return `
<url>
<loc>${route.url}</loc>
${route.lastModified ? `<lastmod>${route.lastModified}</lastmod>` : ""}
<changefreq>${route.changeFrequency}</changefreq>
<loc>${escapeXml(route.url)}</loc>
${route.lastModified ? `<lastmod>${escapeXml(route.lastModified)}</lastmod>` : ""}
<changefreq>${escapeXml(route.changeFrequency)}</changefreq>
<priority>${route.priority}</priority>
${route.images
? route.images
.map(
(img) => `
<image:image>
<image:loc>${img.url}</image:loc>
<image:title><![CDATA[${img.title}]]></image:title>
<image:caption><![CDATA[${img.caption}]]></image:caption>
<image:loc>${escapeXml(img.url)}</image:loc>
<image:title>${escapeXml(img.title)}</image:title>
<image:caption>${escapeXml(img.caption)}</image:caption>
</image:image>`
)
.join("")
@@ -159,7 +182,10 @@ export async function GET() {
return new NextResponse(sitemapXml, {
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",
},
});
}