@sitecore-jss/sitecore-jss-nextjs 22.9.0-canary.1 → 22.9.0-canary.3

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.
@@ -64,7 +64,7 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
64
64
  return __awaiter(this, void 0, void 0, function* () {
65
65
  const { pathname: incomingURL, search: incomingQS = '' } = this.normalizeUrl(req.nextUrl.clone());
66
66
  const locale = this.getLanguage(req);
67
- const normalizedPath = incomingURL.replace(/\/*$/gi, '');
67
+ const normalizedPath = incomingURL.replace(/\/*$/gi, '').toLowerCase();
68
68
  const redirects = yield this.getRedirects(siteName);
69
69
  const language = this.getLanguage(req);
70
70
  const modifyRedirects = structuredClone(redirects);
@@ -78,7 +78,7 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
78
78
  ? redirect.pattern.slice(0, -1).split('?')
79
79
  : redirect.pattern.split('?');
80
80
  const patternQS = urlArray[1];
81
- let patternPath = urlArray[0];
81
+ let patternPath = urlArray[0].toLowerCase();
82
82
  // nextjs routes are case-sensitive, but locales should be compared case-insensitively
83
83
  const patternParts = patternPath.split('/');
84
84
  const maybeLocale = patternParts[1].toLowerCase();
@@ -149,6 +149,7 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
149
149
  if (this.isPrefetch(req)) {
150
150
  sitecore_jss_1.debug.redirects('skipped (prefetch)');
151
151
  response.headers.set('x-middleware-cache', 'no-cache');
152
+ response.headers.set('Cache-Control', 'no-store, must-revalidate');
152
153
  return response;
153
154
  }
154
155
  site = this.getSite(req, res);
@@ -54,6 +54,9 @@ query ${usesPersonalize ? 'PersonalizeSitemapQuery' : 'DefaultSitemapQuery'}(
54
54
  }
55
55
  results {
56
56
  path: routePath
57
+ route {
58
+ displayName
59
+ }
57
60
  ${usesPersonalize
58
61
  ? `
59
62
  route {
@@ -80,8 +83,10 @@ class BaseGraphQLSitemapService {
80
83
  * @param {GraphQLSitemapServiceConfig} options instance
81
84
  */
82
85
  constructor(options) {
86
+ var _a;
83
87
  this.options = options;
84
88
  this._graphQLClient = this.getGraphQLClient();
89
+ this._enableDisplayNameRouting = (_a = options.enableDisplayNameRouting) !== null && _a !== void 0 ? _a : false;
85
90
  }
86
91
  /**
87
92
  * GraphQL client accessible by descendant classes when needed
@@ -145,19 +150,90 @@ class BaseGraphQLSitemapService {
145
150
  }
146
151
  transformLanguageSitePaths(sitePaths, formatStaticPath, language) {
147
152
  return __awaiter(this, void 0, void 0, function* () {
148
- const formatPath = (path) => formatStaticPath(path.replace(/^\/|\/$/g, '').split('/'), language);
153
+ var _a, _b, _c, _d;
149
154
  const aggregatedPaths = [];
150
- sitePaths.forEach((item) => {
151
- var _a, _b, _c;
152
- if (!item)
153
- return;
154
- aggregatedPaths.push(formatPath(item.path));
155
- const variantIds = (_c = (_b = (_a = item.route) === null || _a === void 0 ? void 0 : _a.personalization) === null || _b === void 0 ? void 0 : _b.variantIds) === null || _c === void 0 ? void 0 : _c.filter((variantId) => !variantId.includes('_') // exclude component A/B test variants
156
- );
155
+ /**
156
+ * Build a map of the last segment of each path to its encoded display name.
157
+ * This is used later to substitute the final segment with a display name if available.
158
+ */
159
+ const displayNameMap = new Map();
160
+ if (this._enableDisplayNameRouting) {
161
+ for (const item of sitePaths) {
162
+ if (!item || typeof item.path !== 'string')
163
+ continue;
164
+ const segments = item.path.replace(/^\/|\/$/g, '').split('/');
165
+ const lastSegment = segments[segments.length - 1];
166
+ const displayName = (_a = item.route) === null || _a === void 0 ? void 0 : _a.displayName;
167
+ if (displayName) {
168
+ displayNameMap.set(lastSegment, encodeURIComponent(displayName));
169
+ }
170
+ }
171
+ }
172
+ /**
173
+ * Recursively generate all path combinations using either:
174
+ * - The item name segment (default)
175
+ * - Or the display name (if available in the map)
176
+ *
177
+ * For example: if path is ['about', 'team'] and displayName for 'team' is 'Team-Page',
178
+ * it will generate:
179
+ * - ['about', 'team']
180
+ * - ['about', 'Team-Page']
181
+ * @param {string[]} segments
182
+ */
183
+ const generateCombinations = (segments) => {
184
+ if (!this._enableDisplayNameRouting) {
185
+ return [segments];
186
+ }
187
+ const results = [];
188
+ const helper = (index, current) => {
189
+ if (index === segments.length) {
190
+ results.push([...current]);
191
+ return;
192
+ }
193
+ const segment = segments[index];
194
+ const display = displayNameMap.get(segment);
195
+ // Item name segment
196
+ current.push(segment);
197
+ helper(index + 1, current);
198
+ current.pop();
199
+ // Display name segment (if available and different)
200
+ if (display && display !== segment) {
201
+ current.push(display);
202
+ helper(index + 1, current);
203
+ current.pop();
204
+ }
205
+ };
206
+ helper(0, []);
207
+ return results;
208
+ };
209
+ /**
210
+ * Process each route in the result set to:
211
+ * - Add itemName-based and displayName-based paths
212
+ * - Add personalized variants (if applicable) for each of those paths
213
+ */
214
+ for (const item of sitePaths) {
215
+ if (!item || typeof item.path !== 'string')
216
+ continue;
217
+ const itemPath = item.path.replace(/^\/|\/$/g, '');
218
+ const segments = itemPath ? itemPath.split('/') : [];
219
+ // Generate all display/item name path combinations
220
+ const allCombinations = generateCombinations(segments);
221
+ // Add plain paths to the aggregated paths list
222
+ for (const combo of allCombinations) {
223
+ aggregatedPaths.push(formatStaticPath(combo, language));
224
+ }
225
+ // Check for personalization variants
226
+ const variantIds = (_d = (_c = (_b = item.route) === null || _b === void 0 ? void 0 : _b.personalization) === null || _c === void 0 ? void 0 : _c.variantIds) === null || _d === void 0 ? void 0 : _d.filter((variantId) => !variantId.includes('_'));
157
227
  if (variantIds === null || variantIds === void 0 ? void 0 : variantIds.length) {
158
- aggregatedPaths.push(...variantIds.map((varId) => formatPath((0, personalize_1.getPersonalizedRewrite)(item.path, [varId]))));
228
+ for (const variantId of variantIds) {
229
+ for (const combo of allCombinations) {
230
+ const rewritePath = (0, personalize_1.getPersonalizedRewrite)('/' + combo.join('/'), [variantId]);
231
+ const variantSegments = rewritePath.replace(/^\/|\/$/g, '').split('/');
232
+ aggregatedPaths.push(formatStaticPath(variantSegments, language));
233
+ }
234
+ }
159
235
  }
160
- });
236
+ }
161
237
  return aggregatedPaths;
162
238
  });
163
239
  }
@@ -58,7 +58,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
58
58
  return __awaiter(this, void 0, void 0, function* () {
59
59
  const { pathname: incomingURL, search: incomingQS = '' } = this.normalizeUrl(req.nextUrl.clone());
60
60
  const locale = this.getLanguage(req);
61
- const normalizedPath = incomingURL.replace(/\/*$/gi, '');
61
+ const normalizedPath = incomingURL.replace(/\/*$/gi, '').toLowerCase();
62
62
  const redirects = yield this.getRedirects(siteName);
63
63
  const language = this.getLanguage(req);
64
64
  const modifyRedirects = structuredClone(redirects);
@@ -72,7 +72,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
72
72
  ? redirect.pattern.slice(0, -1).split('?')
73
73
  : redirect.pattern.split('?');
74
74
  const patternQS = urlArray[1];
75
- let patternPath = urlArray[0];
75
+ let patternPath = urlArray[0].toLowerCase();
76
76
  // nextjs routes are case-sensitive, but locales should be compared case-insensitively
77
77
  const patternParts = patternPath.split('/');
78
78
  const maybeLocale = patternParts[1].toLowerCase();
@@ -143,6 +143,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
143
143
  if (this.isPrefetch(req)) {
144
144
  debug.redirects('skipped (prefetch)');
145
145
  response.headers.set('x-middleware-cache', 'no-cache');
146
+ response.headers.set('Cache-Control', 'no-store, must-revalidate');
146
147
  return response;
147
148
  }
148
149
  site = this.getSite(req, res);
@@ -50,6 +50,9 @@ query ${usesPersonalize ? 'PersonalizeSitemapQuery' : 'DefaultSitemapQuery'}(
50
50
  }
51
51
  results {
52
52
  path: routePath
53
+ route {
54
+ displayName
55
+ }
53
56
  ${usesPersonalize
54
57
  ? `
55
58
  route {
@@ -76,8 +79,10 @@ export class BaseGraphQLSitemapService {
76
79
  * @param {GraphQLSitemapServiceConfig} options instance
77
80
  */
78
81
  constructor(options) {
82
+ var _a;
79
83
  this.options = options;
80
84
  this._graphQLClient = this.getGraphQLClient();
85
+ this._enableDisplayNameRouting = (_a = options.enableDisplayNameRouting) !== null && _a !== void 0 ? _a : false;
81
86
  }
82
87
  /**
83
88
  * GraphQL client accessible by descendant classes when needed
@@ -141,19 +146,90 @@ export class BaseGraphQLSitemapService {
141
146
  }
142
147
  transformLanguageSitePaths(sitePaths, formatStaticPath, language) {
143
148
  return __awaiter(this, void 0, void 0, function* () {
144
- const formatPath = (path) => formatStaticPath(path.replace(/^\/|\/$/g, '').split('/'), language);
149
+ var _a, _b, _c, _d;
145
150
  const aggregatedPaths = [];
146
- sitePaths.forEach((item) => {
147
- var _a, _b, _c;
148
- if (!item)
149
- return;
150
- aggregatedPaths.push(formatPath(item.path));
151
- const variantIds = (_c = (_b = (_a = item.route) === null || _a === void 0 ? void 0 : _a.personalization) === null || _b === void 0 ? void 0 : _b.variantIds) === null || _c === void 0 ? void 0 : _c.filter((variantId) => !variantId.includes('_') // exclude component A/B test variants
152
- );
151
+ /**
152
+ * Build a map of the last segment of each path to its encoded display name.
153
+ * This is used later to substitute the final segment with a display name if available.
154
+ */
155
+ const displayNameMap = new Map();
156
+ if (this._enableDisplayNameRouting) {
157
+ for (const item of sitePaths) {
158
+ if (!item || typeof item.path !== 'string')
159
+ continue;
160
+ const segments = item.path.replace(/^\/|\/$/g, '').split('/');
161
+ const lastSegment = segments[segments.length - 1];
162
+ const displayName = (_a = item.route) === null || _a === void 0 ? void 0 : _a.displayName;
163
+ if (displayName) {
164
+ displayNameMap.set(lastSegment, encodeURIComponent(displayName));
165
+ }
166
+ }
167
+ }
168
+ /**
169
+ * Recursively generate all path combinations using either:
170
+ * - The item name segment (default)
171
+ * - Or the display name (if available in the map)
172
+ *
173
+ * For example: if path is ['about', 'team'] and displayName for 'team' is 'Team-Page',
174
+ * it will generate:
175
+ * - ['about', 'team']
176
+ * - ['about', 'Team-Page']
177
+ * @param {string[]} segments
178
+ */
179
+ const generateCombinations = (segments) => {
180
+ if (!this._enableDisplayNameRouting) {
181
+ return [segments];
182
+ }
183
+ const results = [];
184
+ const helper = (index, current) => {
185
+ if (index === segments.length) {
186
+ results.push([...current]);
187
+ return;
188
+ }
189
+ const segment = segments[index];
190
+ const display = displayNameMap.get(segment);
191
+ // Item name segment
192
+ current.push(segment);
193
+ helper(index + 1, current);
194
+ current.pop();
195
+ // Display name segment (if available and different)
196
+ if (display && display !== segment) {
197
+ current.push(display);
198
+ helper(index + 1, current);
199
+ current.pop();
200
+ }
201
+ };
202
+ helper(0, []);
203
+ return results;
204
+ };
205
+ /**
206
+ * Process each route in the result set to:
207
+ * - Add itemName-based and displayName-based paths
208
+ * - Add personalized variants (if applicable) for each of those paths
209
+ */
210
+ for (const item of sitePaths) {
211
+ if (!item || typeof item.path !== 'string')
212
+ continue;
213
+ const itemPath = item.path.replace(/^\/|\/$/g, '');
214
+ const segments = itemPath ? itemPath.split('/') : [];
215
+ // Generate all display/item name path combinations
216
+ const allCombinations = generateCombinations(segments);
217
+ // Add plain paths to the aggregated paths list
218
+ for (const combo of allCombinations) {
219
+ aggregatedPaths.push(formatStaticPath(combo, language));
220
+ }
221
+ // Check for personalization variants
222
+ const variantIds = (_d = (_c = (_b = item.route) === null || _b === void 0 ? void 0 : _b.personalization) === null || _c === void 0 ? void 0 : _c.variantIds) === null || _d === void 0 ? void 0 : _d.filter((variantId) => !variantId.includes('_'));
153
223
  if (variantIds === null || variantIds === void 0 ? void 0 : variantIds.length) {
154
- aggregatedPaths.push(...variantIds.map((varId) => formatPath(getPersonalizedRewrite(item.path, [varId]))));
224
+ for (const variantId of variantIds) {
225
+ for (const combo of allCombinations) {
226
+ const rewritePath = getPersonalizedRewrite('/' + combo.join('/'), [variantId]);
227
+ const variantSegments = rewritePath.replace(/^\/|\/$/g, '').split('/');
228
+ aggregatedPaths.push(formatStaticPath(variantSegments, language));
229
+ }
230
+ }
155
231
  }
156
- });
232
+ }
157
233
  return aggregatedPaths;
158
234
  });
159
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-jss/sitecore-jss-nextjs",
3
- "version": "22.9.0-canary.1",
3
+ "version": "22.9.0-canary.3",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -72,16 +72,16 @@
72
72
  "react-dom": "^19.1.0"
73
73
  },
74
74
  "dependencies": {
75
- "@sitecore-jss/sitecore-jss": "22.9.0-canary.1",
76
- "@sitecore-jss/sitecore-jss-dev-tools": "22.9.0-canary.1",
77
- "@sitecore-jss/sitecore-jss-react": "22.9.0-canary.1",
75
+ "@sitecore-jss/sitecore-jss": "22.9.0-canary.3",
76
+ "@sitecore-jss/sitecore-jss-dev-tools": "22.9.0-canary.3",
77
+ "@sitecore-jss/sitecore-jss-react": "22.9.0-canary.3",
78
78
  "@vercel/kv": "^0.2.1",
79
79
  "regex-parser": "^2.2.11",
80
80
  "sync-disk-cache": "^2.1.0"
81
81
  },
82
82
  "description": "",
83
83
  "types": "types/index.d.ts",
84
- "gitHead": "ae9418c7df8c0e7c91b902eaffc58c7ad0beacd7",
84
+ "gitHead": "70636941cdd23fdc50c2ca6da426d878a1120831",
85
85
  "files": [
86
86
  "dist",
87
87
  "types",
@@ -58,6 +58,7 @@ export interface SiteRouteQueryResult<T> {
58
58
  export type RouteListQueryResult = {
59
59
  path: string;
60
60
  route?: {
61
+ displayName: string;
61
62
  personalization?: {
62
63
  variantIds: string[];
63
64
  };
@@ -73,6 +74,10 @@ export interface BaseGraphQLSitemapServiceConfig extends Omit<SiteRouteQueryVari
73
74
  * Turned off by default.
74
75
  */
75
76
  includePersonalizedRoutes?: boolean;
77
+ /**
78
+ * Gets a flag indicating whether display name routing is enabled.
79
+ */
80
+ enableDisplayNameRouting?: boolean;
76
81
  /**
77
82
  * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient.
78
83
  * This factory function is used to create and configure GraphQL clients for making GraphQL API requests.
@@ -97,6 +102,7 @@ export type StaticPath = {
97
102
  export declare abstract class BaseGraphQLSitemapService {
98
103
  options: BaseGraphQLSitemapServiceConfig;
99
104
  private _graphQLClient;
105
+ private _enableDisplayNameRouting;
100
106
  /**
101
107
  * Creates an instance of graphQL sitemap service with the provided options
102
108
  * @param {GraphQLSitemapServiceConfig} options instance