Skip to content

Commit 575ee0b

Browse files
committed
Refactor redirects middleware to improve extensibility
1 parent 0e83893 commit 575ee0b

File tree

1 file changed

+84
-72
lines changed

1 file changed

+84
-72
lines changed

packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts

+84-72
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,86 @@ export class RedirectsMiddleware extends MiddlewareBase {
7171
};
7272
}
7373

74-
private handler = async (req: NextRequest, res?: NextResponse): Promise<NextResponse> => {
74+
/**
75+
* Method returns RedirectInfo when matches
76+
* @param {NextRequest} req request
77+
* @param {string} siteName site name
78+
* @returns Promise<RedirectInfo | undefined> The redirect info or undefined if no redirect is found
79+
* @protected
80+
*/
81+
protected async getExistsRedirect(
82+
req: NextRequest,
83+
siteName: string
84+
): Promise<RedirectResult | undefined> {
85+
const { pathname: targetURL, search: targetQS = '', locale } = this.normalizeUrl(
86+
req.nextUrl.clone()
87+
);
88+
const normalizedPath = targetURL.replace(/\/*$/gi, '');
89+
const redirects = await this.redirectsService.fetchRedirects(siteName);
90+
const language = this.getLanguage(req);
91+
const modifyRedirects = structuredClone(redirects);
92+
let matchedQueryString: string | undefined;
93+
94+
return modifyRedirects.length
95+
? modifyRedirects.find((redirect: RedirectResult) => {
96+
if (isRegexOrUrl(redirect.pattern) === 'url') {
97+
const parseUrlPattern = redirect.pattern.endsWith('/')
98+
? redirect.pattern.slice(0, -1).split('?')
99+
: redirect.pattern.split('?');
100+
101+
return (
102+
(parseUrlPattern[0] === normalizedPath ||
103+
parseUrlPattern[0] === `/${locale}${normalizedPath}`) &&
104+
areURLSearchParamsEqual(
105+
new URLSearchParams(parseUrlPattern[1] ?? ''),
106+
new URLSearchParams(targetQS)
107+
)
108+
);
109+
}
110+
111+
// Modify the redirect pattern to ignore the language prefix in the path
112+
// And escapes non-special "?" characters in a string or regex.
113+
redirect.pattern = escapeNonSpecialQuestionMarks(
114+
redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), '')
115+
);
116+
117+
// Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
118+
redirect.pattern = `/^\/${redirect.pattern
119+
.replace(/^\/|\/$/g, '') // Removes leading and trailing slashes
120+
.replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
121+
.replace(/^\^|\$$/g, '') // Further cleans up anchors
122+
.replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
123+
124+
matchedQueryString = [
125+
regexParser(redirect.pattern).test(`${normalizedPath}${targetQS}`),
126+
regexParser(redirect.pattern).test(`/${locale}${normalizedPath}${targetQS}`),
127+
].some(Boolean)
128+
? targetQS
129+
: undefined;
130+
131+
// Save the matched query string (if found) into the redirect object
132+
redirect.matchedQueryString = matchedQueryString || '';
133+
134+
return (
135+
!!(
136+
regexParser(redirect.pattern).test(targetURL) ||
137+
regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${targetURL}`) ||
138+
matchedQueryString
139+
) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true)
140+
);
141+
})
142+
: undefined;
143+
}
144+
145+
/**
146+
* @param {NextRequest} req request
147+
* @param {Response} res response
148+
* @returns {Promise<NextResponse>} The redirect response.
149+
*/
150+
protected async processRedirectRequest(
151+
req: NextRequest,
152+
res?: NextResponse
153+
): Promise<NextResponse> {
75154
const pathname = req.nextUrl.pathname;
76155
const language = this.getLanguage(req);
77156
const hostname = this.getHostHeader(req) || this.defaultHostname;
@@ -201,79 +280,12 @@ export class RedirectsMiddleware extends MiddlewareBase {
201280
});
202281

203282
return response;
204-
};
205-
206-
/**
207-
* Method returns RedirectInfo when matches
208-
* @param {NextRequest} req request
209-
* @param {string} siteName site name
210-
* @returns Promise<RedirectInfo | undefined>
211-
* @private
212-
*/
213-
private async getExistsRedirect(
214-
req: NextRequest,
215-
siteName: string
216-
): Promise<RedirectResult | undefined> {
217-
const { pathname: targetURL, search: targetQS = '', locale } = this.normalizeUrl(
218-
req.nextUrl.clone()
219-
);
220-
const normalizedPath = targetURL.replace(/\/*$/gi, '');
221-
const redirects = await this.redirectsService.fetchRedirects(siteName);
222-
const language = this.getLanguage(req);
223-
const modifyRedirects = structuredClone(redirects);
224-
let matchedQueryString: string | undefined;
225-
226-
return modifyRedirects.length
227-
? modifyRedirects.find((redirect: RedirectResult) => {
228-
if (isRegexOrUrl(redirect.pattern) === 'url') {
229-
const parseUrlPattern = redirect.pattern.endsWith('/')
230-
? redirect.pattern.slice(0, -1).split('?')
231-
: redirect.pattern.split('?');
232-
233-
return (
234-
(parseUrlPattern[0] === normalizedPath ||
235-
parseUrlPattern[0] === `/${locale}${normalizedPath}`) &&
236-
areURLSearchParamsEqual(
237-
new URLSearchParams(parseUrlPattern[1] ?? ''),
238-
new URLSearchParams(targetQS)
239-
)
240-
);
241-
}
242-
243-
// Modify the redirect pattern to ignore the language prefix in the path
244-
// And escapes non-special "?" characters in a string or regex.
245-
redirect.pattern = escapeNonSpecialQuestionMarks(
246-
redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), '')
247-
);
248-
249-
// Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
250-
redirect.pattern = `/^\/${redirect.pattern
251-
.replace(/^\/|\/$/g, '') // Removes leading and trailing slashes
252-
.replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
253-
.replace(/^\^|\$$/g, '') // Further cleans up anchors
254-
.replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
255-
256-
matchedQueryString = [
257-
regexParser(redirect.pattern).test(`${normalizedPath}${targetQS}`),
258-
regexParser(redirect.pattern).test(`/${locale}${normalizedPath}${targetQS}`),
259-
].some(Boolean)
260-
? targetQS
261-
: undefined;
262-
263-
// Save the matched query string (if found) into the redirect object
264-
redirect.matchedQueryString = matchedQueryString || '';
265-
266-
return (
267-
!!(
268-
regexParser(redirect.pattern).test(targetURL) ||
269-
regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${targetURL}`) ||
270-
matchedQueryString
271-
) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true)
272-
);
273-
})
274-
: undefined;
275283
}
276284

285+
private handler = async (req: NextRequest, res?: NextResponse): Promise<NextResponse> => {
286+
return this.processRedirectRequest(req, res);
287+
};
288+
277289
/**
278290
* When a user clicks on a link generated by the Link component from next/link,
279291
* Next.js adds special parameters in the route called path.

0 commit comments

Comments
 (0)