Skip to content

Commit

Permalink
[sitecore-jss-nextjs]: Redirects with multilingual support have been…
Browse files Browse the repository at this point in the history
… fixed
  • Loading branch information
Ruslan Matkovskyi committed Oct 2, 2024
1 parent fbd6023 commit 5d8717c
Showing 1 changed file with 61 additions and 45 deletions.
106 changes: 61 additions & 45 deletions packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { NextRequest, NextResponse } from 'next/server';
import regexParser from 'regex-parser';
import { MiddlewareBase, MiddlewareBaseConfig } from './middleware';
import { NextURL } from 'next/dist/server/web/next-url';

const REGEXP_CONTEXT_SITE_LANG = new RegExp(/\$siteLang/, 'i');
const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i');
Expand Down Expand Up @@ -140,21 +141,23 @@ export class RedirectsMiddleware extends MiddlewareBase {
}

const prepareNewURL = new URL(`${target[0]}${url.search}`, url.origin);

url.href = prepareNewURL.href;
url.pathname = prepareNewURL.pathname;
url.search = prepareNewURL.search;
url.locale = req.nextUrl.locale;
}

const redirectUrl = decodeURIComponent(url.href);

/** return Response redirect with http code of redirect type **/
switch (existsRedirect.redirectType) {
case REDIRECT_TYPE_301: {
return this.createRedirectResponse(redirectUrl, res, 301, 'Moved Permanently');
return this.createRedirectResponse(url, res, 301, 'Moved Permanently');
}
case REDIRECT_TYPE_302: {
return this.createRedirectResponse(redirectUrl, res, 302, 'Found');
return this.createRedirectResponse(url, res, 302, 'Found');
}
case REDIRECT_TYPE_SERVER_TRANSFER: {
return this.rewrite(redirectUrl, req, res || NextResponse.next());
return this.rewrite(url.href, req, res || NextResponse.next());
}
default:
return res || NextResponse.next();
Expand Down Expand Up @@ -185,49 +188,60 @@ export class RedirectsMiddleware extends MiddlewareBase {
siteName: string
): Promise<(RedirectInfo & { matchedQueryString?: string }) | undefined> {
const redirects = await this.redirectsService.fetchRedirects(siteName);
const normalizedUrl = this.normalizeUrl(req.nextUrl.clone());
const tragetURL = normalizedUrl.pathname;
const targetQS = normalizedUrl.search || '';
const { pathname: targetURL, search: targetQS = '', locale } = req.nextUrl.clone();
const language = this.getLanguage(req);
const modifyRedirects = structuredClone(redirects);

return modifyRedirects.length
? modifyRedirects.find((redirect: RedirectInfo & { matchedQueryString?: string }) => {
redirect.pattern = redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), '');
redirect.pattern = `/^\/${redirect.pattern
.replace(/^\/|\/$/g, '')
.replace(/^\^\/|\/\$$/g, '')
.replace(/^\^|\$$/g, '')
.replace(/(?<!\\)\?/g, '\\?')
.replace(/\$\/gi$/g, '')}[\/]?$/i`;
const matchedQueryString = this.isPermutedQueryMatch({
pathname: tragetURL,
queryString: targetQS,
pattern: redirect.pattern,
locale: req.nextUrl.locale,
});
redirect.matchedQueryString = matchedQueryString || '';

return (
(regexParser(redirect.pattern).test(tragetURL) ||
regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}`) ||
matchedQueryString) &&
(redirect.locale
? redirect.locale.toLowerCase() === req.nextUrl.locale.toLowerCase()
: true)
);
})
: undefined;
const clonedRedirects = structuredClone(redirects);

if (!clonedRedirects.length) return undefined;

return clonedRedirects.find((redirect: RedirectInfo & { matchedQueryString?: string }) => {
// Clean up and transform the redirect pattern
redirect.pattern = this.cleanPattern(redirect.pattern, language);

// Check if the query string matches the redirect pattern
const matchedQueryString = this.isPermutedQueryMatch({
pathname: targetURL,
queryString: targetQS,
pattern: redirect.pattern,
locale,
});
redirect.matchedQueryString = matchedQueryString || '';

// Test the pattern against the target URL and locale
const matchesURL = regexParser(redirect.pattern).test(targetURL);
const matchesLocaleURL = regexParser(redirect.pattern).test(`/${locale}${targetURL}`);

return (
(matchesURL || matchesLocaleURL || matchedQueryString) &&
(!redirect.locale || redirect.locale.toLowerCase() === locale.toLowerCase())
);
});
}

/**
* Helper function to clean the redirect pattern
* @param {string} pattern Pattern to clean
* @param {string} language Locale to remove from the pattern
* @returns {string} Cleaned pattern
*/
private cleanPattern(pattern: string, language: string): string {
return `/^\/${
pattern
.replace(RegExp(`^[^]?/${language}/`, 'gi'), '') // Remove language prefix
.replace(/^\/|\/$/g, '') // Trim leading and trailing slashes
.replace(/^\^\/|\/\$$/g, '') // Remove regex anchors from URL
.replace(/(?<!\\)\?/g, '\\?') // Escape unescaped question marks
}[\/]?$/i`; // Append optional trailing slash
}

/**
* When a user clicks on a link generated by the Link component from next/link,
* Next.js adds special parameters in the route called path.
* This method removes these special parameters.
* @param {URL} url
* @param {NextURL} url
* @returns {string} normalize url
*/
private normalizeUrl(url: URL): URL {
private normalizeUrl(url: NextURL): NextURL {
if (!url.search) {
return url;
}
Expand Down Expand Up @@ -257,23 +271,25 @@ export class RedirectsMiddleware extends MiddlewareBase {
})
.join('&');

if (newQueryString) {
return new URL(`${url.pathname}?${newQueryString}`, url.origin);
}
const newUrl = newQueryString ? new URL(`${url.pathname}?${newQueryString}`, url.origin) : url;

url.search = newUrl.search;
url.pathname = newUrl.pathname;
url.href = newUrl.href;

return new URL(`${url.pathname}`, url.origin);
return url;
}

/**
* Helper function to create a redirect response and remove the x-middleware-next header.
* @param {string} url The URL to redirect to.
* @param {NextURL} url The URL to redirect to.
* @param {Response} res The response object.
* @param {number} status The HTTP status code of the redirect.
* @param {string} statusText The status text of the redirect.
* @returns {NextResponse<unknown>} The redirect response.
*/
private createRedirectResponse(
url: string,
url: NextURL,
res: Response | undefined,
status: number,
statusText: string
Expand Down

0 comments on commit 5d8717c

Please sign in to comment.