@sitecore-jss/sitecore-jss-nextjs 21.10.0 → 21.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MiddlewareBase = void 0;
3
+ exports.MiddlewareBase = exports.REWRITE_HEADER_NAME = void 0;
4
4
  const server_1 = require("next/server");
5
+ exports.REWRITE_HEADER_NAME = 'x-sc-rewrite';
5
6
  class MiddlewareBase {
6
7
  constructor(config) {
7
8
  this.config = config;
@@ -36,6 +37,27 @@ class MiddlewareBase {
36
37
  incomingHeaders.forEach((value, key) => (headers[key] = value));
37
38
  return headers;
38
39
  }
40
+ /**
41
+ * Determines if the request is a Next.js (next/link) prefetch request
42
+ * @param {NextRequest} req request
43
+ * @returns {boolean} is prefetch
44
+ */
45
+ isPrefetch(req) {
46
+ const isMobile = req.headers.get('sec-ch-ua-mobile') === '?1';
47
+ const userAgent = req.headers.get('user-agent') || '';
48
+ const isKnownPlatform = /iPhone|Mac|Linux|Windows|Android/i.test(userAgent);
49
+ const isKnownDevice = isMobile || isKnownPlatform;
50
+ const purpose = req.headers.get('purpose');
51
+ const nextRouterPrefetch = req.headers.get('Next-Router-Prefetch');
52
+ const middlewarePrefetch = req.headers.get('x-middleware-prefetch');
53
+ // Some real navigations on different devices may incorrectly include 'prefetch' headers.
54
+ // To avoid skipping personalization in such cases, we treat 'x-middleware-prefetch' as a more reliable signal of true prefetch behavior.
55
+ if (isKnownDevice && middlewarePrefetch === '1') {
56
+ return false;
57
+ }
58
+ // Otherwise, standard prefetch detection
59
+ return purpose === 'prefetch' || nextRouterPrefetch === '1' || middlewarePrefetch === '1';
60
+ }
39
61
  /**
40
62
  * Provides used language
41
63
  * @param {NextRequest} req request
@@ -72,14 +94,17 @@ class MiddlewareBase {
72
94
  * @param {string} rewritePath the destionation path
73
95
  * @param {NextRequest} req the current request
74
96
  * @param {NextResponse} res the current response
97
+ * @param {boolean} [skipHeader] don't write 'x-sc-rewrite' header
75
98
  */
76
- rewrite(rewritePath, req, res) {
99
+ rewrite(rewritePath, req, res, skipHeader) {
77
100
  // Note an absolute URL is required: https://nextjs.org/docs/messages/middleware-relative-urls
78
101
  const rewriteUrl = req.nextUrl.clone();
79
102
  rewriteUrl.pathname = rewritePath;
80
103
  const response = server_1.NextResponse.rewrite(rewriteUrl, res);
81
104
  // Share rewrite path with following executed middlewares
82
- response.headers.set(this.REWRITE_HEADER_NAME, rewritePath);
105
+ if (!skipHeader) {
106
+ response.headers.set(exports.REWRITE_HEADER_NAME, rewritePath);
107
+ }
83
108
  return response;
84
109
  }
85
110
  }
@@ -17,6 +17,7 @@ const regex_parser_1 = __importDefault(require("regex-parser"));
17
17
  const server_1 = require("next/server");
18
18
  const site_1 = require("@sitecore-jss/sitecore-jss/site");
19
19
  const sitecore_jss_1 = require("@sitecore-jss/sitecore-jss");
20
+ const utils_1 = require("@sitecore-jss/sitecore-jss/utils");
20
21
  const middleware_1 = require("./middleware");
21
22
  const REGEXP_CONTEXT_SITE_LANG = new RegExp(/\$siteLang/, 'i');
22
23
  const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i');
@@ -25,13 +26,100 @@ const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i');
25
26
  * compares with current url and redirects to target url
26
27
  */
27
28
  class RedirectsMiddleware extends middleware_1.MiddlewareBase {
28
- /**
29
- * @param {RedirectsMiddlewareConfig} [config] redirects middleware config
30
- */
31
29
  constructor(config) {
32
30
  super(config);
33
31
  this.config = config;
34
- this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
32
+ // NOTE: we provide native fetch for compatibility on Next.js Edge Runtime
33
+ // (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78)
34
+ this.redirectsService = new site_1.GraphQLRedirectsService(Object.assign(Object.assign({}, config), { fetch: fetch }));
35
+ this.locales = config.locales;
36
+ }
37
+ /**
38
+ * Gets the Next.js middleware handler with error handling
39
+ * @returns route handler
40
+ */
41
+ getHandler() {
42
+ return (req, res) => __awaiter(this, void 0, void 0, function* () {
43
+ try {
44
+ return this.processRedirectRequest(req, res);
45
+ }
46
+ catch (error) {
47
+ console.log('Redirect middleware failed:');
48
+ console.log(error);
49
+ return res || server_1.NextResponse.next();
50
+ }
51
+ });
52
+ }
53
+ /**
54
+ * Method returns RedirectInfo when matches
55
+ * @param {NextRequest} req request
56
+ * @param {string} siteName site name
57
+ * @returns Promise<RedirectInfo | undefined> The redirect info or undefined if no redirect is found
58
+ * @protected
59
+ */
60
+ getExistsRedirect(req, siteName) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const { pathname: incomingURL, search: incomingQS = '' } = this.normalizeUrl(req.nextUrl.clone());
63
+ const locale = this.getLanguage(req);
64
+ const normalizedPath = incomingURL.replace(/\/*$/gi, '').toLowerCase();
65
+ const redirects = yield this.getRedirects(siteName);
66
+ const language = this.getLanguage(req);
67
+ const modifyRedirects = structuredClone(redirects);
68
+ let matchedQueryString;
69
+ const localePath = `/${locale.toLowerCase()}${normalizedPath}`;
70
+ return modifyRedirects.length
71
+ ? modifyRedirects.find((redirect) => {
72
+ // process static URL (non-regex) rules
73
+ if ((0, utils_1.isRegexOrUrl)(redirect.pattern) === 'url') {
74
+ const urlArray = redirect.pattern.endsWith('/')
75
+ ? redirect.pattern.slice(0, -1).split('?')
76
+ : redirect.pattern.split('?');
77
+ const patternQS = urlArray[1];
78
+ let patternPath = urlArray[0].toLowerCase();
79
+ // nextjs routes are case-sensitive, but locales should be compared case-insensitively
80
+ const patternParts = patternPath.split('/');
81
+ const maybeLocale = patternParts[1].toLowerCase();
82
+ // case insensitive lookup of locales
83
+ if (new RegExp(this.locales.join('|'), 'i').test(maybeLocale)) {
84
+ patternPath = patternPath.replace(`/${patternParts[1]}`, `/${maybeLocale}`);
85
+ }
86
+ return ((patternPath === localePath || patternPath === normalizedPath) &&
87
+ (!patternQS ||
88
+ (0, utils_1.areURLSearchParamsEqual)(new URLSearchParams(patternQS), new URLSearchParams(incomingQS))));
89
+ }
90
+ // process regex rules
91
+ // Modify the redirect pattern to ignore the language prefix in the path
92
+ // And escapes non-special "?" characters in a string or regex.
93
+ redirect.pattern = (0, utils_1.escapeNonSpecialQuestionMarks)(redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), ''));
94
+ // Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
95
+ redirect.pattern = `/^\/${redirect.pattern
96
+ .replace(/^\/|\/$/g, '') // Removes leading and trailing slashes
97
+ .replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
98
+ .replace(/^\^|\$$/g, '') // Further cleans up anchors
99
+ .replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
100
+ // Redirect pattern matches the full incoming URL with query string present
101
+ matchedQueryString = [
102
+ (0, regex_parser_1.default)(redirect.pattern).test(`/${localePath}${incomingQS}`),
103
+ (0, regex_parser_1.default)(redirect.pattern).test(`${normalizedPath}${incomingQS}`),
104
+ ].some(Boolean)
105
+ ? incomingQS
106
+ : undefined;
107
+ // Save the matched query string (if found) into the redirect object
108
+ redirect.matchedQueryString = matchedQueryString || '';
109
+ return (!!((0, regex_parser_1.default)(redirect.pattern).test(`/${req.nextUrl.locale}${incomingURL}`) ||
110
+ (0, regex_parser_1.default)(redirect.pattern).test(incomingURL) ||
111
+ matchedQueryString) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
112
+ })
113
+ : undefined;
114
+ });
115
+ }
116
+ /**
117
+ * @param {NextRequest} req request
118
+ * @param {Response} res response
119
+ * @returns {Promise<NextResponse>} The redirect response.
120
+ */
121
+ processRedirectRequest(req, res) {
122
+ return __awaiter(this, void 0, void 0, function* () {
35
123
  const pathname = req.nextUrl.pathname;
36
124
  const language = this.getLanguage(req);
37
125
  const hostname = this.getHostHeader(req) || this.defaultHostname;
@@ -43,70 +131,69 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
43
131
  hostname,
44
132
  });
45
133
  const createResponse = () => __awaiter(this, void 0, void 0, function* () {
46
- if (this.config.disabled && this.config.disabled(req, res || server_1.NextResponse.next())) {
134
+ var _a;
135
+ const response = res || server_1.NextResponse.next();
136
+ if (this.config.disabled && this.config.disabled(req, response)) {
47
137
  sitecore_jss_1.debug.redirects('skipped (redirects middleware is disabled)');
48
- return res || server_1.NextResponse.next();
138
+ return response;
49
139
  }
50
140
  if (this.isPreview(req) || this.excludeRoute(pathname)) {
51
141
  sitecore_jss_1.debug.redirects('skipped (%s)', this.isPreview(req) ? 'preview' : 'route excluded');
52
- return res || server_1.NextResponse.next();
142
+ return response;
53
143
  }
54
- site = this.getSite(req, res);
144
+ // Skip prefetch requests from Next.js, which are not original client requests
145
+ // as they load unnecessary requests that burden the redirects middleware with meaningless traffic
146
+ if (this.isPrefetch(req)) {
147
+ sitecore_jss_1.debug.redirects('skipped (prefetch)');
148
+ response.headers.set('x-middleware-cache', 'no-cache');
149
+ response.headers.set('Cache-Control', 'no-store, must-revalidate');
150
+ return response;
151
+ }
152
+ site = this.getSite(req, response);
55
153
  // Find the redirect from result of RedirectService
56
154
  const existsRedirect = yield this.getExistsRedirect(req, site.name);
57
155
  if (!existsRedirect) {
58
156
  sitecore_jss_1.debug.redirects('skipped (redirect does not exist)');
59
- return res || server_1.NextResponse.next();
157
+ return response;
60
158
  }
159
+ sitecore_jss_1.debug.redirects('Matched redirect rule: %o', { existsRedirect });
61
160
  // Find context site language and replace token
62
161
  if (REGEXP_CONTEXT_SITE_LANG.test(existsRedirect.target) &&
63
162
  !(REGEXP_ABSOLUTE_URL.test(existsRedirect.target) &&
64
163
  existsRedirect.target.includes(hostname))) {
65
164
  existsRedirect.target = existsRedirect.target.replace(REGEXP_CONTEXT_SITE_LANG, site.language);
165
+ req.nextUrl.locale = site.language;
66
166
  }
67
- const url = req.nextUrl.clone();
167
+ const url = this.normalizeUrl(req.nextUrl.clone());
68
168
  if (REGEXP_ABSOLUTE_URL.test(existsRedirect.target)) {
69
- url.href = existsRedirect.target;
169
+ return this.dispatchRedirect(existsRedirect.target, existsRedirect.redirectType, req, response, true);
70
170
  }
71
171
  else {
72
- const source = `${url.pathname}${url.search}`;
73
- url.search = existsRedirect.isQueryStringPreserved ? url.search : '';
74
- const urlFirstPart = existsRedirect.target.split('/')[1];
172
+ const isUrl = (0, utils_1.isRegexOrUrl)(existsRedirect.pattern) === 'url';
173
+ const targetParts = existsRedirect.target.split('/');
174
+ const urlFirstPart = targetParts[1];
75
175
  if (this.locales.includes(urlFirstPart)) {
76
- url.locale = urlFirstPart;
176
+ req.nextUrl.locale = urlFirstPart;
77
177
  existsRedirect.target = existsRedirect.target.replace(`/${urlFirstPart}`, '');
78
178
  }
79
- const target = source
80
- .replace((0, regex_parser_1.default)(existsRedirect.pattern), existsRedirect.target)
81
- .replace(/^\/\//, '/')
82
- .split('?');
83
- url.pathname = target[0];
84
- if (target[1]) {
85
- const newParams = new URLSearchParams(target[1]);
86
- for (const [key, val] of newParams.entries()) {
87
- url.searchParams.append(key, val);
88
- }
89
- }
90
- }
91
- const redirectUrl = decodeURIComponent(url.href);
92
- /** return Response redirect with http code of redirect type **/
93
- switch (existsRedirect.redirectType) {
94
- case site_1.REDIRECT_TYPE_301:
95
- return server_1.NextResponse.redirect(redirectUrl, {
96
- status: 301,
97
- statusText: 'Moved Permanently',
98
- headers: res === null || res === void 0 ? void 0 : res.headers,
99
- });
100
- case site_1.REDIRECT_TYPE_302:
101
- return server_1.NextResponse.redirect(redirectUrl, {
102
- status: 302,
103
- statusText: 'Found',
104
- headers: res === null || res === void 0 ? void 0 : res.headers,
105
- });
106
- case site_1.REDIRECT_TYPE_SERVER_TRANSFER:
107
- return server_1.NextResponse.rewrite(redirectUrl, res);
108
- default:
109
- return res || server_1.NextResponse.next();
179
+ const targetSegments = isUrl
180
+ ? existsRedirect.target.split('?')
181
+ : url.pathname.replace(/\/*$/gi, '') + existsRedirect.matchedQueryString;
182
+ const [targetPath, targetQueryString] = isUrl
183
+ ? targetSegments
184
+ : targetSegments
185
+ .replace((0, regex_parser_1.default)(existsRedirect.pattern), existsRedirect.target)
186
+ .replace(/^\/\//, '/')
187
+ .split('?');
188
+ const mergedQueryString = existsRedirect.isQueryStringPreserved
189
+ ? (0, utils_1.mergeURLSearchParams)(new URLSearchParams((_a = url.search) !== null && _a !== void 0 ? _a : ''), new URLSearchParams(targetQueryString || ''))
190
+ : targetQueryString || '';
191
+ const prepareNewURL = new URL(`${targetPath}${mergedQueryString ? `?${mergedQueryString}` : ''}`, url.origin);
192
+ url.href = prepareNewURL.href;
193
+ url.pathname = prepareNewURL.pathname;
194
+ url.search = prepareNewURL.search;
195
+ url.locale = req.nextUrl.locale;
196
+ return this.dispatchRedirect(url, existsRedirect.redirectType, req, response, false);
110
197
  }
111
198
  });
112
199
  const response = yield createResponse();
@@ -118,60 +205,95 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
118
205
  });
119
206
  return response;
120
207
  });
121
- // NOTE: we provide native fetch for compatibility on Next.js Edge Runtime
122
- // (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78)
123
- this.redirectsService = new site_1.GraphQLRedirectsService(Object.assign(Object.assign({}, config), { fetch: fetch }));
124
- this.locales = config.locales;
125
208
  }
126
209
  /**
127
- * Gets the Next.js middleware handler with error handling
128
- * @returns route handler
210
+ * Fetches all redirects for a given site from the Sitecore instance
211
+ * @param {string} siteName - The name of the site to fetch redirects for
212
+ * @returns {Promise<RedirectInfo[]>} A promise that resolves to an array of redirect information
213
+ * @protected
129
214
  */
130
- getHandler() {
131
- return (req, res) => __awaiter(this, void 0, void 0, function* () {
132
- try {
133
- return yield this.handler(req, res);
134
- }
135
- catch (error) {
136
- console.log('Redirect middleware failed:');
137
- console.log(error);
138
- return res || server_1.NextResponse.next();
139
- }
215
+ getRedirects(siteName) {
216
+ return __awaiter(this, void 0, void 0, function* () {
217
+ return this.redirectsService.fetchRedirects(siteName);
140
218
  });
141
219
  }
142
220
  /**
143
- * Method returns RedirectInfo when matches
144
- * @param {NextRequest} req request
145
- * @param {string} siteName site name
146
- * @returns Promise<RedirectInfo | undefined>
147
- * @private
221
+ * When a user clicks on a link generated by the Link component from next/link,
222
+ * Next.js adds special parameters in the route called path.
223
+ * This method removes these special parameters.
224
+ * @param {NextURL} url
225
+ * @returns {string} normalize url
148
226
  */
149
- getExistsRedirect(req, siteName) {
150
- return __awaiter(this, void 0, void 0, function* () {
151
- const redirects = yield this.redirectsService.fetchRedirects(siteName);
152
- const tragetURL = req.nextUrl.pathname;
153
- const targetQS = req.nextUrl.search || '';
154
- const language = this.getLanguage(req);
155
- const modifyRedirects = structuredClone(redirects);
156
- return modifyRedirects.length
157
- ? modifyRedirects.find((redirect) => {
158
- redirect.pattern = redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), '');
159
- redirect.pattern = `/^\/${redirect.pattern
160
- .replace(/^\/|\/$/g, '')
161
- .replace(/^\^\/|\/\$$/g, '')
162
- .replace(/^\^|\$$/g, '')
163
- .replace(/(?<!\\)\?/g, '\\?')
164
- .replace(/\$\/gi$/g, '')}[\/]?$/gi`;
165
- return (((0, regex_parser_1.default)(redirect.pattern).test(tragetURL) ||
166
- (0, regex_parser_1.default)(redirect.pattern).test(`${tragetURL}${targetQS}`) ||
167
- (0, regex_parser_1.default)(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}`) ||
168
- (0, regex_parser_1.default)(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}${targetQS}`)) &&
169
- (redirect.locale
170
- ? redirect.locale.toLowerCase() === req.nextUrl.locale.toLowerCase()
171
- : true));
172
- })
173
- : undefined;
227
+ normalizeUrl(url) {
228
+ if (!url.search)
229
+ return url;
230
+ /**
231
+ * Prepare special parameters for exclusion.
232
+ */
233
+ const splittedPathname = url.pathname
234
+ .split('/')
235
+ .filter((route) => route)
236
+ .map((route) => `path=${route}`);
237
+ /**
238
+ * Remove special parameters(Next.JS)
239
+ * Example: /about/contact/us
240
+ * When a user clicks on this link, Next.js should generate a link for the middleware, formatted like this:
241
+ * http://host/about/contact/us?path=about&path=contact&path=us
242
+ */
243
+ const newQueryString = url.search
244
+ .replace(/^\?/, '')
245
+ .split('&')
246
+ .filter((param) => !splittedPathname.includes(param))
247
+ .join('&');
248
+ const newUrl = new URL(`${url.pathname.toLowerCase()}?${newQueryString}`, url.origin);
249
+ url.search = newUrl.search;
250
+ url.pathname = newUrl.pathname.toLowerCase();
251
+ url.href = newUrl.href;
252
+ return url;
253
+ }
254
+ /**
255
+ * Dispatch a redirect or rewrite based on type.
256
+ * @param {NextURL | string} target Final target to redirect/rewrite to (NextURL or string for externals).
257
+ * @param {string} type One of `REDIRECT_TYPE_301`, `REDIRECT_TYPE_302`, or `REDIRECT_TYPE_SERVER_TRANSFER`.
258
+ * @param {NextRequest} req Incoming request.
259
+ * @param {NextResponse} res Current response (used for header cleanup/carry-over).
260
+ * @param {boolean} isExternal Set to `true` when target is an external absolute URL.
261
+ * @returns A NextResponse.
262
+ */
263
+ dispatchRedirect(target, type, req, res, isExternal = false) {
264
+ switch (type) {
265
+ case site_1.REDIRECT_TYPE_301:
266
+ return this.createRedirectResponse(target, res, 301, 'Moved Permanently');
267
+ case site_1.REDIRECT_TYPE_302:
268
+ return this.createRedirectResponse(target, res, 302, 'Found');
269
+ case site_1.REDIRECT_TYPE_SERVER_TRANSFER:
270
+ // rewrite expects a string; unwrap NextURL if needed
271
+ return this.rewrite(typeof target === 'string' ? target : target.href, req, res, isExternal);
272
+ default:
273
+ // Unknown type: return the input response unchanged
274
+ return res;
275
+ }
276
+ }
277
+ /**
278
+ * Helper function to create a redirect response and remove the x-middleware-next header.
279
+ * @param {NextURL} url The URL to redirect to.
280
+ * @param {Response} res The response object.
281
+ * @param {number} status The HTTP status code of the redirect.
282
+ * @param {string} statusText The status text of the redirect.
283
+ * @returns {NextResponse<unknown>} The redirect response.
284
+ */
285
+ createRedirectResponse(url, res, status, statusText) {
286
+ const redirect = server_1.NextResponse.redirect(url, {
287
+ status,
288
+ statusText,
289
+ headers: res === null || res === void 0 ? void 0 : res.headers,
174
290
  });
291
+ if (res === null || res === void 0 ? void 0 : res.headers) {
292
+ redirect.headers.delete('x-middleware-next');
293
+ redirect.headers.delete('x-middleware-rewrite');
294
+ redirect.headers.delete(middleware_1.REWRITE_HEADER_NAME);
295
+ }
296
+ return redirect;
175
297
  }
176
298
  }
177
299
  exports.RedirectsMiddleware = RedirectsMiddleware;
@@ -1,4 +1,5 @@
1
1
  import { NextResponse } from 'next/server';
2
+ export const REWRITE_HEADER_NAME = 'x-sc-rewrite';
2
3
  export class MiddlewareBase {
3
4
  constructor(config) {
4
5
  this.config = config;
@@ -33,6 +34,27 @@ export class MiddlewareBase {
33
34
  incomingHeaders.forEach((value, key) => (headers[key] = value));
34
35
  return headers;
35
36
  }
37
+ /**
38
+ * Determines if the request is a Next.js (next/link) prefetch request
39
+ * @param {NextRequest} req request
40
+ * @returns {boolean} is prefetch
41
+ */
42
+ isPrefetch(req) {
43
+ const isMobile = req.headers.get('sec-ch-ua-mobile') === '?1';
44
+ const userAgent = req.headers.get('user-agent') || '';
45
+ const isKnownPlatform = /iPhone|Mac|Linux|Windows|Android/i.test(userAgent);
46
+ const isKnownDevice = isMobile || isKnownPlatform;
47
+ const purpose = req.headers.get('purpose');
48
+ const nextRouterPrefetch = req.headers.get('Next-Router-Prefetch');
49
+ const middlewarePrefetch = req.headers.get('x-middleware-prefetch');
50
+ // Some real navigations on different devices may incorrectly include 'prefetch' headers.
51
+ // To avoid skipping personalization in such cases, we treat 'x-middleware-prefetch' as a more reliable signal of true prefetch behavior.
52
+ if (isKnownDevice && middlewarePrefetch === '1') {
53
+ return false;
54
+ }
55
+ // Otherwise, standard prefetch detection
56
+ return purpose === 'prefetch' || nextRouterPrefetch === '1' || middlewarePrefetch === '1';
57
+ }
36
58
  /**
37
59
  * Provides used language
38
60
  * @param {NextRequest} req request
@@ -69,14 +91,17 @@ export class MiddlewareBase {
69
91
  * @param {string} rewritePath the destionation path
70
92
  * @param {NextRequest} req the current request
71
93
  * @param {NextResponse} res the current response
94
+ * @param {boolean} [skipHeader] don't write 'x-sc-rewrite' header
72
95
  */
73
- rewrite(rewritePath, req, res) {
96
+ rewrite(rewritePath, req, res, skipHeader) {
74
97
  // Note an absolute URL is required: https://nextjs.org/docs/messages/middleware-relative-urls
75
98
  const rewriteUrl = req.nextUrl.clone();
76
99
  rewriteUrl.pathname = rewritePath;
77
100
  const response = NextResponse.rewrite(rewriteUrl, res);
78
101
  // Share rewrite path with following executed middlewares
79
- response.headers.set(this.REWRITE_HEADER_NAME, rewritePath);
102
+ if (!skipHeader) {
103
+ response.headers.set(REWRITE_HEADER_NAME, rewritePath);
104
+ }
80
105
  return response;
81
106
  }
82
107
  }
@@ -11,7 +11,8 @@ import regexParser from 'regex-parser';
11
11
  import { NextResponse } from 'next/server';
12
12
  import { GraphQLRedirectsService, REDIRECT_TYPE_301, REDIRECT_TYPE_302, REDIRECT_TYPE_SERVER_TRANSFER, } from '@sitecore-jss/sitecore-jss/site';
13
13
  import { debug } from '@sitecore-jss/sitecore-jss';
14
- import { MiddlewareBase } from './middleware';
14
+ import { areURLSearchParamsEqual, escapeNonSpecialQuestionMarks, isRegexOrUrl, mergeURLSearchParams, } from '@sitecore-jss/sitecore-jss/utils';
15
+ import { MiddlewareBase, REWRITE_HEADER_NAME } from './middleware';
15
16
  const REGEXP_CONTEXT_SITE_LANG = new RegExp(/\$siteLang/, 'i');
16
17
  const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i');
17
18
  /**
@@ -19,13 +20,100 @@ const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i');
19
20
  * compares with current url and redirects to target url
20
21
  */
21
22
  export class RedirectsMiddleware extends MiddlewareBase {
22
- /**
23
- * @param {RedirectsMiddlewareConfig} [config] redirects middleware config
24
- */
25
23
  constructor(config) {
26
24
  super(config);
27
25
  this.config = config;
28
- this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
26
+ // NOTE: we provide native fetch for compatibility on Next.js Edge Runtime
27
+ // (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78)
28
+ this.redirectsService = new GraphQLRedirectsService(Object.assign(Object.assign({}, config), { fetch: fetch }));
29
+ this.locales = config.locales;
30
+ }
31
+ /**
32
+ * Gets the Next.js middleware handler with error handling
33
+ * @returns route handler
34
+ */
35
+ getHandler() {
36
+ return (req, res) => __awaiter(this, void 0, void 0, function* () {
37
+ try {
38
+ return this.processRedirectRequest(req, res);
39
+ }
40
+ catch (error) {
41
+ console.log('Redirect middleware failed:');
42
+ console.log(error);
43
+ return res || NextResponse.next();
44
+ }
45
+ });
46
+ }
47
+ /**
48
+ * Method returns RedirectInfo when matches
49
+ * @param {NextRequest} req request
50
+ * @param {string} siteName site name
51
+ * @returns Promise<RedirectInfo | undefined> The redirect info or undefined if no redirect is found
52
+ * @protected
53
+ */
54
+ getExistsRedirect(req, siteName) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ const { pathname: incomingURL, search: incomingQS = '' } = this.normalizeUrl(req.nextUrl.clone());
57
+ const locale = this.getLanguage(req);
58
+ const normalizedPath = incomingURL.replace(/\/*$/gi, '').toLowerCase();
59
+ const redirects = yield this.getRedirects(siteName);
60
+ const language = this.getLanguage(req);
61
+ const modifyRedirects = structuredClone(redirects);
62
+ let matchedQueryString;
63
+ const localePath = `/${locale.toLowerCase()}${normalizedPath}`;
64
+ return modifyRedirects.length
65
+ ? modifyRedirects.find((redirect) => {
66
+ // process static URL (non-regex) rules
67
+ if (isRegexOrUrl(redirect.pattern) === 'url') {
68
+ const urlArray = redirect.pattern.endsWith('/')
69
+ ? redirect.pattern.slice(0, -1).split('?')
70
+ : redirect.pattern.split('?');
71
+ const patternQS = urlArray[1];
72
+ let patternPath = urlArray[0].toLowerCase();
73
+ // nextjs routes are case-sensitive, but locales should be compared case-insensitively
74
+ const patternParts = patternPath.split('/');
75
+ const maybeLocale = patternParts[1].toLowerCase();
76
+ // case insensitive lookup of locales
77
+ if (new RegExp(this.locales.join('|'), 'i').test(maybeLocale)) {
78
+ patternPath = patternPath.replace(`/${patternParts[1]}`, `/${maybeLocale}`);
79
+ }
80
+ return ((patternPath === localePath || patternPath === normalizedPath) &&
81
+ (!patternQS ||
82
+ areURLSearchParamsEqual(new URLSearchParams(patternQS), new URLSearchParams(incomingQS))));
83
+ }
84
+ // process regex rules
85
+ // Modify the redirect pattern to ignore the language prefix in the path
86
+ // And escapes non-special "?" characters in a string or regex.
87
+ redirect.pattern = escapeNonSpecialQuestionMarks(redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), ''));
88
+ // Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
89
+ redirect.pattern = `/^\/${redirect.pattern
90
+ .replace(/^\/|\/$/g, '') // Removes leading and trailing slashes
91
+ .replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
92
+ .replace(/^\^|\$$/g, '') // Further cleans up anchors
93
+ .replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
94
+ // Redirect pattern matches the full incoming URL with query string present
95
+ matchedQueryString = [
96
+ regexParser(redirect.pattern).test(`/${localePath}${incomingQS}`),
97
+ regexParser(redirect.pattern).test(`${normalizedPath}${incomingQS}`),
98
+ ].some(Boolean)
99
+ ? incomingQS
100
+ : undefined;
101
+ // Save the matched query string (if found) into the redirect object
102
+ redirect.matchedQueryString = matchedQueryString || '';
103
+ return (!!(regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${incomingURL}`) ||
104
+ regexParser(redirect.pattern).test(incomingURL) ||
105
+ matchedQueryString) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
106
+ })
107
+ : undefined;
108
+ });
109
+ }
110
+ /**
111
+ * @param {NextRequest} req request
112
+ * @param {Response} res response
113
+ * @returns {Promise<NextResponse>} The redirect response.
114
+ */
115
+ processRedirectRequest(req, res) {
116
+ return __awaiter(this, void 0, void 0, function* () {
29
117
  const pathname = req.nextUrl.pathname;
30
118
  const language = this.getLanguage(req);
31
119
  const hostname = this.getHostHeader(req) || this.defaultHostname;
@@ -37,70 +125,69 @@ export class RedirectsMiddleware extends MiddlewareBase {
37
125
  hostname,
38
126
  });
39
127
  const createResponse = () => __awaiter(this, void 0, void 0, function* () {
40
- if (this.config.disabled && this.config.disabled(req, res || NextResponse.next())) {
128
+ var _a;
129
+ const response = res || NextResponse.next();
130
+ if (this.config.disabled && this.config.disabled(req, response)) {
41
131
  debug.redirects('skipped (redirects middleware is disabled)');
42
- return res || NextResponse.next();
132
+ return response;
43
133
  }
44
134
  if (this.isPreview(req) || this.excludeRoute(pathname)) {
45
135
  debug.redirects('skipped (%s)', this.isPreview(req) ? 'preview' : 'route excluded');
46
- return res || NextResponse.next();
136
+ return response;
47
137
  }
48
- site = this.getSite(req, res);
138
+ // Skip prefetch requests from Next.js, which are not original client requests
139
+ // as they load unnecessary requests that burden the redirects middleware with meaningless traffic
140
+ if (this.isPrefetch(req)) {
141
+ debug.redirects('skipped (prefetch)');
142
+ response.headers.set('x-middleware-cache', 'no-cache');
143
+ response.headers.set('Cache-Control', 'no-store, must-revalidate');
144
+ return response;
145
+ }
146
+ site = this.getSite(req, response);
49
147
  // Find the redirect from result of RedirectService
50
148
  const existsRedirect = yield this.getExistsRedirect(req, site.name);
51
149
  if (!existsRedirect) {
52
150
  debug.redirects('skipped (redirect does not exist)');
53
- return res || NextResponse.next();
151
+ return response;
54
152
  }
153
+ debug.redirects('Matched redirect rule: %o', { existsRedirect });
55
154
  // Find context site language and replace token
56
155
  if (REGEXP_CONTEXT_SITE_LANG.test(existsRedirect.target) &&
57
156
  !(REGEXP_ABSOLUTE_URL.test(existsRedirect.target) &&
58
157
  existsRedirect.target.includes(hostname))) {
59
158
  existsRedirect.target = existsRedirect.target.replace(REGEXP_CONTEXT_SITE_LANG, site.language);
159
+ req.nextUrl.locale = site.language;
60
160
  }
61
- const url = req.nextUrl.clone();
161
+ const url = this.normalizeUrl(req.nextUrl.clone());
62
162
  if (REGEXP_ABSOLUTE_URL.test(existsRedirect.target)) {
63
- url.href = existsRedirect.target;
163
+ return this.dispatchRedirect(existsRedirect.target, existsRedirect.redirectType, req, response, true);
64
164
  }
65
165
  else {
66
- const source = `${url.pathname}${url.search}`;
67
- url.search = existsRedirect.isQueryStringPreserved ? url.search : '';
68
- const urlFirstPart = existsRedirect.target.split('/')[1];
166
+ const isUrl = isRegexOrUrl(existsRedirect.pattern) === 'url';
167
+ const targetParts = existsRedirect.target.split('/');
168
+ const urlFirstPart = targetParts[1];
69
169
  if (this.locales.includes(urlFirstPart)) {
70
- url.locale = urlFirstPart;
170
+ req.nextUrl.locale = urlFirstPart;
71
171
  existsRedirect.target = existsRedirect.target.replace(`/${urlFirstPart}`, '');
72
172
  }
73
- const target = source
74
- .replace(regexParser(existsRedirect.pattern), existsRedirect.target)
75
- .replace(/^\/\//, '/')
76
- .split('?');
77
- url.pathname = target[0];
78
- if (target[1]) {
79
- const newParams = new URLSearchParams(target[1]);
80
- for (const [key, val] of newParams.entries()) {
81
- url.searchParams.append(key, val);
82
- }
83
- }
84
- }
85
- const redirectUrl = decodeURIComponent(url.href);
86
- /** return Response redirect with http code of redirect type **/
87
- switch (existsRedirect.redirectType) {
88
- case REDIRECT_TYPE_301:
89
- return NextResponse.redirect(redirectUrl, {
90
- status: 301,
91
- statusText: 'Moved Permanently',
92
- headers: res === null || res === void 0 ? void 0 : res.headers,
93
- });
94
- case REDIRECT_TYPE_302:
95
- return NextResponse.redirect(redirectUrl, {
96
- status: 302,
97
- statusText: 'Found',
98
- headers: res === null || res === void 0 ? void 0 : res.headers,
99
- });
100
- case REDIRECT_TYPE_SERVER_TRANSFER:
101
- return NextResponse.rewrite(redirectUrl, res);
102
- default:
103
- return res || NextResponse.next();
173
+ const targetSegments = isUrl
174
+ ? existsRedirect.target.split('?')
175
+ : url.pathname.replace(/\/*$/gi, '') + existsRedirect.matchedQueryString;
176
+ const [targetPath, targetQueryString] = isUrl
177
+ ? targetSegments
178
+ : targetSegments
179
+ .replace(regexParser(existsRedirect.pattern), existsRedirect.target)
180
+ .replace(/^\/\//, '/')
181
+ .split('?');
182
+ const mergedQueryString = existsRedirect.isQueryStringPreserved
183
+ ? mergeURLSearchParams(new URLSearchParams((_a = url.search) !== null && _a !== void 0 ? _a : ''), new URLSearchParams(targetQueryString || ''))
184
+ : targetQueryString || '';
185
+ const prepareNewURL = new URL(`${targetPath}${mergedQueryString ? `?${mergedQueryString}` : ''}`, url.origin);
186
+ url.href = prepareNewURL.href;
187
+ url.pathname = prepareNewURL.pathname;
188
+ url.search = prepareNewURL.search;
189
+ url.locale = req.nextUrl.locale;
190
+ return this.dispatchRedirect(url, existsRedirect.redirectType, req, response, false);
104
191
  }
105
192
  });
106
193
  const response = yield createResponse();
@@ -112,59 +199,94 @@ export class RedirectsMiddleware extends MiddlewareBase {
112
199
  });
113
200
  return response;
114
201
  });
115
- // NOTE: we provide native fetch for compatibility on Next.js Edge Runtime
116
- // (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78)
117
- this.redirectsService = new GraphQLRedirectsService(Object.assign(Object.assign({}, config), { fetch: fetch }));
118
- this.locales = config.locales;
119
202
  }
120
203
  /**
121
- * Gets the Next.js middleware handler with error handling
122
- * @returns route handler
204
+ * Fetches all redirects for a given site from the Sitecore instance
205
+ * @param {string} siteName - The name of the site to fetch redirects for
206
+ * @returns {Promise<RedirectInfo[]>} A promise that resolves to an array of redirect information
207
+ * @protected
123
208
  */
124
- getHandler() {
125
- return (req, res) => __awaiter(this, void 0, void 0, function* () {
126
- try {
127
- return yield this.handler(req, res);
128
- }
129
- catch (error) {
130
- console.log('Redirect middleware failed:');
131
- console.log(error);
132
- return res || NextResponse.next();
133
- }
209
+ getRedirects(siteName) {
210
+ return __awaiter(this, void 0, void 0, function* () {
211
+ return this.redirectsService.fetchRedirects(siteName);
134
212
  });
135
213
  }
136
214
  /**
137
- * Method returns RedirectInfo when matches
138
- * @param {NextRequest} req request
139
- * @param {string} siteName site name
140
- * @returns Promise<RedirectInfo | undefined>
141
- * @private
215
+ * When a user clicks on a link generated by the Link component from next/link,
216
+ * Next.js adds special parameters in the route called path.
217
+ * This method removes these special parameters.
218
+ * @param {NextURL} url
219
+ * @returns {string} normalize url
142
220
  */
143
- getExistsRedirect(req, siteName) {
144
- return __awaiter(this, void 0, void 0, function* () {
145
- const redirects = yield this.redirectsService.fetchRedirects(siteName);
146
- const tragetURL = req.nextUrl.pathname;
147
- const targetQS = req.nextUrl.search || '';
148
- const language = this.getLanguage(req);
149
- const modifyRedirects = structuredClone(redirects);
150
- return modifyRedirects.length
151
- ? modifyRedirects.find((redirect) => {
152
- redirect.pattern = redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), '');
153
- redirect.pattern = `/^\/${redirect.pattern
154
- .replace(/^\/|\/$/g, '')
155
- .replace(/^\^\/|\/\$$/g, '')
156
- .replace(/^\^|\$$/g, '')
157
- .replace(/(?<!\\)\?/g, '\\?')
158
- .replace(/\$\/gi$/g, '')}[\/]?$/gi`;
159
- return ((regexParser(redirect.pattern).test(tragetURL) ||
160
- regexParser(redirect.pattern).test(`${tragetURL}${targetQS}`) ||
161
- regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}`) ||
162
- regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}${targetQS}`)) &&
163
- (redirect.locale
164
- ? redirect.locale.toLowerCase() === req.nextUrl.locale.toLowerCase()
165
- : true));
166
- })
167
- : undefined;
221
+ normalizeUrl(url) {
222
+ if (!url.search)
223
+ return url;
224
+ /**
225
+ * Prepare special parameters for exclusion.
226
+ */
227
+ const splittedPathname = url.pathname
228
+ .split('/')
229
+ .filter((route) => route)
230
+ .map((route) => `path=${route}`);
231
+ /**
232
+ * Remove special parameters(Next.JS)
233
+ * Example: /about/contact/us
234
+ * When a user clicks on this link, Next.js should generate a link for the middleware, formatted like this:
235
+ * http://host/about/contact/us?path=about&path=contact&path=us
236
+ */
237
+ const newQueryString = url.search
238
+ .replace(/^\?/, '')
239
+ .split('&')
240
+ .filter((param) => !splittedPathname.includes(param))
241
+ .join('&');
242
+ const newUrl = new URL(`${url.pathname.toLowerCase()}?${newQueryString}`, url.origin);
243
+ url.search = newUrl.search;
244
+ url.pathname = newUrl.pathname.toLowerCase();
245
+ url.href = newUrl.href;
246
+ return url;
247
+ }
248
+ /**
249
+ * Dispatch a redirect or rewrite based on type.
250
+ * @param {NextURL | string} target Final target to redirect/rewrite to (NextURL or string for externals).
251
+ * @param {string} type One of `REDIRECT_TYPE_301`, `REDIRECT_TYPE_302`, or `REDIRECT_TYPE_SERVER_TRANSFER`.
252
+ * @param {NextRequest} req Incoming request.
253
+ * @param {NextResponse} res Current response (used for header cleanup/carry-over).
254
+ * @param {boolean} isExternal Set to `true` when target is an external absolute URL.
255
+ * @returns A NextResponse.
256
+ */
257
+ dispatchRedirect(target, type, req, res, isExternal = false) {
258
+ switch (type) {
259
+ case REDIRECT_TYPE_301:
260
+ return this.createRedirectResponse(target, res, 301, 'Moved Permanently');
261
+ case REDIRECT_TYPE_302:
262
+ return this.createRedirectResponse(target, res, 302, 'Found');
263
+ case REDIRECT_TYPE_SERVER_TRANSFER:
264
+ // rewrite expects a string; unwrap NextURL if needed
265
+ return this.rewrite(typeof target === 'string' ? target : target.href, req, res, isExternal);
266
+ default:
267
+ // Unknown type: return the input response unchanged
268
+ return res;
269
+ }
270
+ }
271
+ /**
272
+ * Helper function to create a redirect response and remove the x-middleware-next header.
273
+ * @param {NextURL} url The URL to redirect to.
274
+ * @param {Response} res The response object.
275
+ * @param {number} status The HTTP status code of the redirect.
276
+ * @param {string} statusText The status text of the redirect.
277
+ * @returns {NextResponse<unknown>} The redirect response.
278
+ */
279
+ createRedirectResponse(url, res, status, statusText) {
280
+ const redirect = NextResponse.redirect(url, {
281
+ status,
282
+ statusText,
283
+ headers: res === null || res === void 0 ? void 0 : res.headers,
168
284
  });
285
+ if (res === null || res === void 0 ? void 0 : res.headers) {
286
+ redirect.headers.delete('x-middleware-next');
287
+ redirect.headers.delete('x-middleware-rewrite');
288
+ redirect.headers.delete(REWRITE_HEADER_NAME);
289
+ }
290
+ return redirect;
169
291
  }
170
292
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-jss/sitecore-jss-nextjs",
3
- "version": "21.10.0",
3
+ "version": "21.10.1",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -69,9 +69,9 @@
69
69
  "react-dom": "^19.1.0"
70
70
  },
71
71
  "dependencies": {
72
- "@sitecore-jss/sitecore-jss": "21.10.0",
73
- "@sitecore-jss/sitecore-jss-dev-tools": "21.10.0",
74
- "@sitecore-jss/sitecore-jss-react": "21.10.0",
72
+ "@sitecore-jss/sitecore-jss": "21.10.1",
73
+ "@sitecore-jss/sitecore-jss-dev-tools": "21.10.1",
74
+ "@sitecore-jss/sitecore-jss-react": "21.10.1",
75
75
  "@vercel/kv": "^0.2.1",
76
76
  "node-html-parser": "^6.1.4",
77
77
  "regex-parser": "^2.2.11",
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "description": "",
81
81
  "types": "types/index.d.ts",
82
- "gitHead": "599ccf7d2789491a0847540adca697fa580d735f",
82
+ "gitHead": "aaeb09bfb4b3e9a40f2799342feebfaa4cf5f619",
83
83
  "files": [
84
84
  "dist",
85
85
  "types",
@@ -1,5 +1,6 @@
1
1
  import { SiteInfo, SiteResolver } from '@sitecore-jss/sitecore-jss/site';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
+ export declare const REWRITE_HEADER_NAME = "x-sc-rewrite";
3
4
  export type MiddlewareBaseConfig = {
4
5
  /**
5
6
  * function, determines if middleware should be turned off, based on cookie, header, or other considerations
@@ -47,6 +48,12 @@ export declare abstract class MiddlewareBase {
47
48
  protected extractDebugHeaders(incomingHeaders: Headers): {
48
49
  [key: string]: string;
49
50
  };
51
+ /**
52
+ * Determines if the request is a Next.js (next/link) prefetch request
53
+ * @param {NextRequest} req request
54
+ * @returns {boolean} is prefetch
55
+ */
56
+ protected isPrefetch(req: NextRequest): boolean;
50
57
  /**
51
58
  * Provides used language
52
59
  * @param {NextRequest} req request
@@ -71,6 +78,7 @@ export declare abstract class MiddlewareBase {
71
78
  * @param {string} rewritePath the destionation path
72
79
  * @param {NextRequest} req the current request
73
80
  * @param {NextResponse} res the current response
81
+ * @param {boolean} [skipHeader] don't write 'x-sc-rewrite' header
74
82
  */
75
- protected rewrite(rewritePath: string, req: NextRequest, res: NextResponse): NextResponse;
83
+ protected rewrite(rewritePath: string, req: NextRequest, res: NextResponse, skipHeader?: boolean): NextResponse;
76
84
  }
@@ -1,6 +1,9 @@
1
1
  import { NextResponse, NextRequest } from 'next/server';
2
- import { GraphQLRedirectsServiceConfig } from '@sitecore-jss/sitecore-jss/site';
2
+ import { RedirectInfo, GraphQLRedirectsServiceConfig } from '@sitecore-jss/sitecore-jss/site';
3
3
  import { MiddlewareBase, MiddlewareBaseConfig } from './middleware';
4
+ type RedirectResult = RedirectInfo & {
5
+ matchedQueryString?: string;
6
+ };
4
7
  /**
5
8
  * extended RedirectsMiddlewareConfig config type for RedirectsMiddleware
6
9
  */
@@ -19,22 +22,59 @@ export declare class RedirectsMiddleware extends MiddlewareBase {
19
22
  protected config: RedirectsMiddlewareConfig;
20
23
  private redirectsService;
21
24
  private locales;
22
- /**
23
- * @param {RedirectsMiddlewareConfig} [config] redirects middleware config
24
- */
25
25
  constructor(config: RedirectsMiddlewareConfig);
26
26
  /**
27
27
  * Gets the Next.js middleware handler with error handling
28
28
  * @returns route handler
29
29
  */
30
30
  getHandler(): (req: NextRequest, res?: NextResponse) => Promise<NextResponse>;
31
- private handler;
32
31
  /**
33
32
  * Method returns RedirectInfo when matches
34
33
  * @param {NextRequest} req request
35
34
  * @param {string} siteName site name
36
- * @returns Promise<RedirectInfo | undefined>
37
- * @private
35
+ * @returns Promise<RedirectInfo | undefined> The redirect info or undefined if no redirect is found
36
+ * @protected
37
+ */
38
+ protected getExistsRedirect(req: NextRequest, siteName: string): Promise<RedirectResult | undefined>;
39
+ /**
40
+ * @param {NextRequest} req request
41
+ * @param {Response} res response
42
+ * @returns {Promise<NextResponse>} The redirect response.
43
+ */
44
+ protected processRedirectRequest(req: NextRequest, res?: NextResponse): Promise<NextResponse>;
45
+ /**
46
+ * Fetches all redirects for a given site from the Sitecore instance
47
+ * @param {string} siteName - The name of the site to fetch redirects for
48
+ * @returns {Promise<RedirectInfo[]>} A promise that resolves to an array of redirect information
49
+ * @protected
50
+ */
51
+ protected getRedirects(siteName: string): Promise<RedirectInfo[]>;
52
+ /**
53
+ * When a user clicks on a link generated by the Link component from next/link,
54
+ * Next.js adds special parameters in the route called path.
55
+ * This method removes these special parameters.
56
+ * @param {NextURL} url
57
+ * @returns {string} normalize url
58
+ */
59
+ private normalizeUrl;
60
+ /**
61
+ * Dispatch a redirect or rewrite based on type.
62
+ * @param {NextURL | string} target Final target to redirect/rewrite to (NextURL or string for externals).
63
+ * @param {string} type One of `REDIRECT_TYPE_301`, `REDIRECT_TYPE_302`, or `REDIRECT_TYPE_SERVER_TRANSFER`.
64
+ * @param {NextRequest} req Incoming request.
65
+ * @param {NextResponse} res Current response (used for header cleanup/carry-over).
66
+ * @param {boolean} isExternal Set to `true` when target is an external absolute URL.
67
+ * @returns A NextResponse.
68
+ */
69
+ private dispatchRedirect;
70
+ /**
71
+ * Helper function to create a redirect response and remove the x-middleware-next header.
72
+ * @param {NextURL} url The URL to redirect to.
73
+ * @param {Response} res The response object.
74
+ * @param {number} status The HTTP status code of the redirect.
75
+ * @param {string} statusText The status text of the redirect.
76
+ * @returns {NextResponse<unknown>} The redirect response.
38
77
  */
39
- private getExistsRedirect;
78
+ private createRedirectResponse;
40
79
  }
80
+ export {};