diff --git a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts index c6e572f31f..45619f7841 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts @@ -1383,6 +1383,7 @@ describe('RedirectsMiddleware', () => { origin: 'http://localhost:3000', clone: cloneUrl, }, + headerValues: { 'cdn-loop': 'netlify' }, }, }); setupRedirectStub(301); diff --git a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts index c0b715e5e3..9edabede07 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts @@ -1,4 +1,4 @@ -import { CacheClient, debug, MemoryCacheClient } from '@sitecore-jss/sitecore-jss'; +import { debug } from '@sitecore-jss/sitecore-jss'; import { GraphQLRedirectsService, GraphQLRedirectsServiceConfig, @@ -16,6 +16,7 @@ import { MiddlewareBase, MiddlewareBaseConfig } from './middleware'; const REGEXP_CONTEXT_SITE_LANG = new RegExp(/\$siteLang/, 'i'); const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i'); +const NAME_NETLIFY = 'netlify'; type RedirectResult = RedirectInfo & { matchedQueryString?: string }; @@ -37,7 +38,6 @@ export type RedirectsMiddlewareConfig = Omit; /** * @param {RedirectsMiddlewareConfig} [config] redirects middleware config @@ -49,10 +49,6 @@ export class RedirectsMiddleware extends MiddlewareBase { // (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78) this.redirectsService = new GraphQLRedirectsService({ ...config, fetch: fetch }); this.locales = config.locales; - this.cache = new MemoryCacheClient({ - cacheEnabled: config.cacheEnabled, - cacheTimeout: config.cacheTimeout, - }); } /** @@ -85,15 +81,24 @@ export class RedirectsMiddleware extends MiddlewareBase { }); const createResponse = async () => { + const response = res || NextResponse.next(); + if (this.config.disabled && this.config.disabled(req, res || NextResponse.next())) { debug.redirects('skipped (redirects middleware is disabled)'); - return res || NextResponse.next(); + return response; } if (this.isPreview(req) || this.excludeRoute(pathname)) { debug.redirects('skipped (%s)', this.isPreview(req) ? 'preview' : 'route excluded'); - return res || NextResponse.next(); + return response; + } + + // Skip prefetch requests + if (this.isPrefetch(req)) { + debug.redirects('skipped (prefetch)'); + response.headers.set('x-middleware-cache', 'no-cache'); + return response; } site = this.getSite(req, res); @@ -104,7 +109,7 @@ export class RedirectsMiddleware extends MiddlewareBase { if (!existsRedirect) { debug.redirects('skipped (redirect does not exist)'); - return res || NextResponse.next(); + return response; } // Find context site language and replace token @@ -160,16 +165,16 @@ export class RedirectsMiddleware extends MiddlewareBase { /** return Response redirect with http code of redirect type */ switch (existsRedirect.redirectType) { case REDIRECT_TYPE_301: { - return this.createRedirectResponse(url, res, 301, 'Moved Permanently'); + return this.createRedirectResponse(url, response, 301, 'Moved Permanently'); } case REDIRECT_TYPE_302: { - return this.createRedirectResponse(url, res, 302, 'Found'); + return this.createRedirectResponse(url, response, 302, 'Found'); } case REDIRECT_TYPE_SERVER_TRANSFER: { - return this.rewrite(url.href, req, res || NextResponse.next()); + return this.rewrite(url.href, req, response); } default: - return res || NextResponse.next(); + return response; } }; @@ -199,29 +204,14 @@ export class RedirectsMiddleware extends MiddlewareBase { const { pathname: targetURL, search: targetQS = '', locale } = this.normalizeUrl( req.nextUrl.clone() ); - const cacheKey = `${targetURL}-${targetQS}-${locale}`; - const cachedRedirect = this.cache.getCacheValue(cacheKey); - - if (cachedRedirect !== null) { - return typeof cachedRedirect === 'boolean' ? undefined : cachedRedirect; - } - + const normalizedPath = targetURL.replace(/\/*$/gi, ''); const redirects = await this.redirectsService.fetchRedirects(siteName); - const language = this.getLanguage(req); const modifyRedirects = structuredClone(redirects); + let matchedQueryString: string | undefined; - const result = modifyRedirects.length + return modifyRedirects.length ? modifyRedirects.find((redirect: RedirectResult) => { - // generate cache key for the current pattern - const chachedPatternResultKey = `${cacheKey}-${redirect.pattern}-${redirect.target}`; - // Check if the result is already cached - const chachedPatternResult = this.cache.getCacheValue(chachedPatternResultKey); - - if (chachedPatternResult !== null) { - return chachedPatternResult; - } - // Modify the redirect pattern to ignore the language prefix in the path // And escapes non-special "?" characters in a string or regex. redirect.pattern = this.escapeNonSpecialQuestionMarks( @@ -260,34 +250,35 @@ export class RedirectsMiddleware extends MiddlewareBase { * it returns `undefined`. The `matchedQueryString` is later used to indicate whether the query * string contributed to a successful redirect match. */ - const matchedQueryString = this.isPermutedQueryMatch({ - pathname: targetURL, - queryString: targetQS, - pattern: redirect.pattern, - locale, - }); + if (req.headers.get('cdn-loop') === NAME_NETLIFY) { + matchedQueryString = this.getPermutedQueryMatch({ + pathname: normalizedPath, + queryString: targetQS, + pattern: redirect.pattern, + locale, + }); + } else { + matchedQueryString = [ + regexParser(redirect.pattern).test(`${normalizedPath}${targetQS}`), + regexParser(redirect.pattern).test(`/${locale}${normalizedPath}${targetQS}`), + ].some(Boolean) + ? targetQS + : undefined; + } // Save the matched query string (if found) into the redirect object redirect.matchedQueryString = matchedQueryString || ''; - const matchedPatterResult = + // Return the redirect if the URL path or any query string permutation matches the pattern + return ( !!( regexParser(redirect.pattern).test(targetURL) || regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${targetURL}`) || matchedQueryString - ) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true); - - // Save cache the result for the current pattern - this.cache.setCacheValue(chachedPatternResultKey, matchedPatterResult); - - // Return the redirect if the URL path or any query string permutation matches the pattern - return matchedPatterResult; + ) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true) + ); }) : undefined; - - this.cache.setCacheValue(cacheKey, result ? result : undefined); - - return result; } /** @@ -372,7 +363,7 @@ export class RedirectsMiddleware extends MiddlewareBase { * @param {string} [params.locale] - The locale prefix to include in the URL if present. * @returns {string | undefined} - return query string if any of the query permutations match the provided pattern, undefined otherwise. */ - private isPermutedQueryMatch({ + private getPermutedQueryMatch({ pathname, queryString, pattern, @@ -389,11 +380,10 @@ export class RedirectsMiddleware extends MiddlewareBase { '?' + permutation.map(([key, value]) => `${key}=${value}`).join('&') ); - const normalizedPath = pathname.replace(/\/*$/gi, ''); return listOfPermuted.find((query: string) => [ - regexParser(pattern).test(`${normalizedPath}${query}`), - regexParser(pattern).test(`/${locale}${normalizedPath}${query}`), + regexParser(pattern).test(`${pathname}${query}`), + regexParser(pattern).test(`/${locale}${pathname}${query}`), ].some(Boolean) ); }