From 8173fb170788b6fc2897bde86e7c9a0bf4f1bdbe Mon Sep 17 00:00:00 2001 From: Pedro Bernardina Date: Mon, 9 Mar 2026 16:30:59 -0300 Subject: [PATCH 1/2] feat(proxy): add prop to exclude entries from final sitemap --- vtex/handlers/sitemap.ts | 37 +++++++++++++++++++++++++++++++------ vtex/loaders/proxy.ts | 10 ++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/vtex/handlers/sitemap.ts b/vtex/handlers/sitemap.ts index b760995bf..a448402a0 100644 --- a/vtex/handlers/sitemap.ts +++ b/vtex/handlers/sitemap.ts @@ -27,14 +27,35 @@ const includeSiteMaps = ( : currentXML; }; +const excludeSitemapEntries = ( + xml: string, + exclude?: string[], +): string => { + if (!exclude?.length) return xml; + return xml.replace( + /\s*([^<]*)<\/loc>[\s\S]*?<\/sitemap>/gi, + (block, loc) => { + const shouldExclude = exclude.some( + (entry) => loc.includes(entry) || loc.endsWith(entry), + ); + return shouldExclude ? "" : block; + }, + ); +}; + export interface Props { include?: string[]; + /** + * @title Sitemap entries to remove from the sitemap index + * @description URLs or path suffixes to match; any whose contains or ends with one of these will be removed. + */ + excludeSitemapEntry?: string[]; } /** * @title Sitemap Proxy */ export default function Sitemap( - { include }: Props, + { include, excludeSitemapEntry }: Props, { publicUrl: url, usePortalSitemap, account }: AppContext, ) { return async ( @@ -62,12 +83,16 @@ export default function Sitemap( const reqUrl = new URL(req.url); const text = await response.text(); + const withIncludes = includeSiteMaps( + text.replaceAll(publicUrl, `${reqUrl.origin}/`), + reqUrl.origin, + include, + ); + + const filtered = excludeSitemapEntries(withIncludes, excludeSitemapEntry); + return new Response( - includeSiteMaps( - text.replaceAll(publicUrl, `${reqUrl.origin}/`), - reqUrl.origin, - include, - ), + filtered, { headers: response.headers, status: response.status, diff --git a/vtex/loaders/proxy.ts b/vtex/loaders/proxy.ts index 322498c0a..66c7483e9 100644 --- a/vtex/loaders/proxy.ts +++ b/vtex/loaders/proxy.ts @@ -31,6 +31,7 @@ const buildProxyRoutes = ( includePathToDecoSitemap, generateDecoSiteMap, excludePathsFromDecoSiteMap, + excludeSitemapEntry, includeScriptsToHead, includeScriptsToBody, }: { @@ -40,6 +41,7 @@ const buildProxyRoutes = ( includePathToDecoSitemap?: string[]; generateDecoSiteMap?: boolean; excludePathsFromDecoSiteMap: string[]; + excludeSitemapEntry?: string[]; includeScriptsToHead?: { includes?: Script[]; }; @@ -110,6 +112,7 @@ const buildProxyRoutes = ( handler: { value: { include, + excludeSitemapEntry, __resolveType: "vtex/handlers/sitemap.ts", }, }, @@ -149,6 +152,11 @@ export interface Props { * @title Exclude paths from /deco-sitemap.xml */ excludePathsFromDecoSiteMap?: string[]; + /** + * @title Sitemap entries to remove from the sitemap index + * @description Path or URL substrings; any <sitemap> in /sitemap.xml whose <loc> contains one of these will be removed. + */ + excludeSitemapEntry?: string[]; /** * @title Scripts to include on Html head */ @@ -174,6 +182,7 @@ function loader( includePathToDecoSitemap = [], generateDecoSiteMap = true, excludePathsFromDecoSiteMap = [], + excludeSitemapEntry = [], includeScriptsToHead = { includes: [] }, includeScriptsToBody = { includes: [] }, }: Props, @@ -183,6 +192,7 @@ function loader( return buildProxyRoutes({ generateDecoSiteMap, excludePathsFromDecoSiteMap, + excludeSitemapEntry, includeSiteMap, includePathToDecoSitemap, publicUrl: ctx.publicUrl, From 77540a72b3a84a37ee6cd0878d0d7e861cc3e1b6 Mon Sep 17 00:00:00 2001 From: Pedro Bernardina Date: Mon, 9 Mar 2026 18:31:26 -0300 Subject: [PATCH 2/2] feat(vtex-proxy): make possible to add custom handlers to proxy --- vtex/handlers/sitemap.ts | 6 +-- vtex/loaders/proxy.ts | 88 ++++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/vtex/handlers/sitemap.ts b/vtex/handlers/sitemap.ts index a448402a0..a9824f74d 100644 --- a/vtex/handlers/sitemap.ts +++ b/vtex/handlers/sitemap.ts @@ -49,13 +49,13 @@ export interface Props { * @title Sitemap entries to remove from the sitemap index * @description URLs or path suffixes to match; any whose contains or ends with one of these will be removed. */ - excludeSitemapEntry?: string[]; + excludeSiteMapEntry?: string[]; } /** * @title Sitemap Proxy */ export default function Sitemap( - { include, excludeSitemapEntry }: Props, + { include, excludeSiteMapEntry }: Props, { publicUrl: url, usePortalSitemap, account }: AppContext, ) { return async ( @@ -89,7 +89,7 @@ export default function Sitemap( include, ); - const filtered = excludeSitemapEntries(withIncludes, excludeSitemapEntry); + const filtered = excludeSitemapEntries(withIncludes, excludeSiteMapEntry); return new Response( filtered, diff --git a/vtex/loaders/proxy.ts b/vtex/loaders/proxy.ts index 66c7483e9..75884c9f1 100644 --- a/vtex/loaders/proxy.ts +++ b/vtex/loaders/proxy.ts @@ -23,25 +23,59 @@ export const VTEX_PATHS_THAT_REQUIRES_SAME_REFERER = ["/no-cache/AviseMe.aspx"]; const decoSiteMapUrl = "/sitemap/deco.xml"; +/** @title {{__title}} */ +export interface IncludeSiteMapEntry { + /** @title Title (CMS only) */ + __title?: string; + /** @title Path */ + path: string; + /** @title Handler */ + handler?: string; + /** @title Paths to exclude from the sitemap */ + excludePaths?: string[]; +} + +const normalizeIncludeSiteMap = ( + includeSiteMap?: IncludeSiteMapEntry[], +): IncludeSiteMapEntry[] => (includeSiteMap ?? []); + +const includeEntriesToPaths = (entries: IncludeSiteMapEntry[]): string[] => + entries.map((e) => e.path); + +const includeEntriesToRoutes = (entries: IncludeSiteMapEntry[]): Route[] => + entries + .map(({ path, handler, excludePaths, ...rest }) => ({ + pathTemplate: path, + handler: { + value: { + excludePaths, + __resolveType: handler ?? "website/handlers/sitemap.ts", + ...rest, + }, + }, + })); + const buildProxyRoutes = ( { publicUrl, extraPaths, includeSiteMap, + includeSiteMapWithHandler, includePathToDecoSitemap, generateDecoSiteMap, excludePathsFromDecoSiteMap, - excludeSitemapEntry, + excludeSiteMapEntry, includeScriptsToHead, includeScriptsToBody, }: { publicUrl?: string; extraPaths: string[]; includeSiteMap?: string[]; + includeSiteMapWithHandler?: IncludeSiteMapEntry[]; includePathToDecoSitemap?: string[]; generateDecoSiteMap?: boolean; excludePathsFromDecoSiteMap: string[]; - excludeSitemapEntry?: string[]; + excludeSiteMapEntry?: string[]; includeScriptsToHead?: { includes?: Script[]; }; @@ -55,6 +89,8 @@ const buildProxyRoutes = ( } try { + const entries = normalizeIncludeSiteMap(includeSiteMapWithHandler); + const customSitemapRoutes = includeEntriesToRoutes(entries); const hostname = (new URL( publicUrl?.startsWith("http") ? publicUrl : `https://${publicUrl}`, )).hostname; @@ -93,17 +129,30 @@ const buildProxyRoutes = ( ); const [include, routes] = generateDecoSiteMap - ? [[...(includeSiteMap ?? []), decoSiteMapUrl], [{ - pathTemplate: decoSiteMapUrl, - handler: { - value: { - excludePaths: excludePathsFromDecoSiteMap, - includePaths: includePathToDecoSitemap, - __resolveType: "website/handlers/sitemap.ts", + ? [ + [ + ...includeEntriesToPaths(entries), + ...(includeSiteMap ?? []), + decoSiteMapUrl, + ], + [ + ...customSitemapRoutes, + { + pathTemplate: decoSiteMapUrl, + handler: { + value: { + excludePaths: excludePathsFromDecoSiteMap, + includePaths: includePathToDecoSitemap, + __resolveType: "website/handlers/sitemap.ts", + }, + }, }, - }, - }]] - : [includeSiteMap, []]; + ], + ] + : [ + [...includeEntriesToPaths(entries), ...(includeSiteMap ?? [])], + customSitemapRoutes, + ]; return [ ...routes, @@ -112,7 +161,7 @@ const buildProxyRoutes = ( handler: { value: { include, - excludeSitemapEntry, + excludeSiteMapEntry, __resolveType: "vtex/handlers/sitemap.ts", }, }, @@ -140,6 +189,11 @@ export interface Props { * @title Other site maps to include */ includeSiteMap?: string[]; + /** + * @title Other site maps to include + * @description URL path (e.g. "/sitemap/blog.xml") or object with path + handler (__resolveType) to register a route and add to the index. Use the object form for dynamic sitemaps (e.g. from Sanity). + */ + includeSiteMapWithHandler?: IncludeSiteMapEntry[]; /** * @title Paths to include in the deco sitemap */ @@ -156,7 +210,7 @@ export interface Props { * @title Sitemap entries to remove from the sitemap index * @description Path or URL substrings; any <sitemap> in /sitemap.xml whose <loc> contains one of these will be removed. */ - excludeSitemapEntry?: string[]; + excludeSiteMapEntry?: string[]; /** * @title Scripts to include on Html head */ @@ -179,10 +233,11 @@ function loader( { extraPathsToProxy = [], includeSiteMap = [], + includeSiteMapWithHandler = [], includePathToDecoSitemap = [], generateDecoSiteMap = true, excludePathsFromDecoSiteMap = [], - excludeSitemapEntry = [], + excludeSiteMapEntry = [], includeScriptsToHead = { includes: [] }, includeScriptsToBody = { includes: [] }, }: Props, @@ -192,8 +247,9 @@ function loader( return buildProxyRoutes({ generateDecoSiteMap, excludePathsFromDecoSiteMap, - excludeSitemapEntry, + excludeSiteMapEntry, includeSiteMap, + includeSiteMapWithHandler, includePathToDecoSitemap, publicUrl: ctx.publicUrl, extraPaths: extraPathsToProxy,