Skip to content

Commit 791d407

Browse files
committed
Fix unwanted basePath removal if base path is a substring
1 parent 1b4f83e commit 791d407

File tree

6 files changed

+75
-13
lines changed

6 files changed

+75
-13
lines changed

packages/pwa-kit-runtime/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## v3.16.0-dev (Dec 17, 2025)
22
- Move envBasePath into ssrParameters [#3590](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3590)
33
- Support adding base paths to shopper facing URLs [#3615](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3615)
4+
- Update storefront preview to support base paths [#3666](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3666)
45

56
## v3.15.0 (Dec 17, 2025)
67
- Fix multiple set-cookie headers [#3508](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3508)

packages/pwa-kit-runtime/src/ssr/server/build-remote-server.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,10 @@ export const RemoteServerFactory = {
602602
return next()
603603
}
604604

605-
// For other routes, only proceed if path actually starts with base path
606-
if (!req.path.startsWith(basePath)) {
605+
// For other routes, only proceed if path equals basePath or path starts with basePath + '/'
606+
const pathMatchesBasePath =
607+
req.path === basePath || req.path.startsWith(basePath + '/')
608+
if (!pathMatchesBasePath) {
607609
return next()
608610
}
609611

packages/template-retail-react-app/app/utils/site-utils.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ export const getSiteByReference = (siteRef) => {
9595
)
9696
}
9797

98+
/**
99+
* Remove the base path from a path string only when path equals basePath or path starts with basePath + '/'.
100+
* @param {string} path - the path to strip
101+
* @param {string} basePath - the base path to remove
102+
* @returns {string} the path with base path removed, or the original path
103+
*/
104+
export const removeBasePathFromPath = (path, basePath) => {
105+
if (!basePath) return path
106+
if (path.startsWith(basePath + '/') || path === basePath) {
107+
return path.substring(basePath.length) || '/'
108+
}
109+
return path
110+
}
111+
98112
/**
99113
* This function return the identifiers (site and locale) from the given url
100114
* The site will always go before locale if both of them are presented in the pathname
@@ -107,9 +121,7 @@ export const getParamsFromPath = (path) => {
107121
// Remove the base path from the pathname if present since
108122
// it shifts the location of the site and locale in the pathname
109123
const basePath = getRouterBasePath()
110-
if (basePath && pathname.startsWith(basePath)) {
111-
pathname = pathname.substring(basePath.length)
112-
}
124+
pathname = removeBasePathFromPath(pathname, basePath)
113125

114126
const config = getConfig()
115127
const {pathMatcher, searchMatcherForSite, searchMatcherForLocale} = getConfigMatcher(config)

packages/template-retail-react-app/app/utils/site-utils.test.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/uti
1616
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
1717
import {
1818
getParamsFromPath,
19-
resolveLocaleFromUrl
19+
resolveLocaleFromUrl,
20+
removeBasePathFromPath
2021
} from '@salesforce/retail-react-app/app/utils/site-utils'
2122
jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => {
2223
const origin = jest.requireActual('@salesforce/pwa-kit-react-sdk/ssr/universal/utils')
@@ -341,6 +342,43 @@ describe('getParamsFromPath', function () {
341342
const result = getParamsFromPath(path)
342343
expect(result).toEqual({siteRef: 'us', localeRef: 'en-US'})
343344
})
345+
346+
test('should not strip when path has basePath only as substring (e.g. /shop vs /shopping/cart)', () => {
347+
const basePath = '/shop'
348+
getRouterBasePath.mockReturnValue(basePath)
349+
getConfig.mockImplementation(() => ({
350+
...mockConfig,
351+
app: {
352+
...mockConfig.app,
353+
url: {
354+
...mockConfig.app.url,
355+
showBasePath: true
356+
}
357+
}
358+
}))
359+
360+
const result = getParamsFromPath('/shopping/cart')
361+
expect(result).toBeDefined()
362+
})
363+
})
364+
})
365+
366+
describe('removeBasePathFromPath', () => {
367+
test('removes when path starts with basePath + "/"', () => {
368+
expect(removeBasePathFromPath('/shop/cart', '/shop')).toBe('/cart')
369+
expect(removeBasePathFromPath('/test-base/uk/en-GB/foo', '/test-base')).toBe(
370+
'/uk/en-GB/foo'
371+
)
372+
})
373+
test('removes to "/" when path exactly equals basePath', () => {
374+
expect(removeBasePathFromPath('/shop', '/shop')).toBe('/')
375+
})
376+
test('does not remove when basePath is only a substring (e.g. /shop vs /shopping/cart)', () => {
377+
expect(removeBasePathFromPath('/shopping/cart', '/shop')).toBe('/shopping/cart')
378+
expect(removeBasePathFromPath('/shopping', '/shop')).toBe('/shopping')
379+
})
380+
test('returns path unchanged when basePath is empty', () => {
381+
expect(removeBasePathFromPath('/any/path', '')).toBe('/any/path')
344382
})
345383
})
346384

packages/template-retail-react-app/app/utils/url.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
getLocaleByReference,
1010
getParamsFromPath,
1111
getDefaultSite,
12-
getSiteByReference
12+
getSiteByReference,
13+
removeBasePathFromPath
1314
} from '@salesforce/retail-react-app/app/utils/site-utils'
1415
import {HOME_HREF, urlPartPositions} from '@salesforce/retail-react-app/app/constants'
1516
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
@@ -121,9 +122,7 @@ export const getPathWithLocale = (shortCode, buildUrl, opts = {}) => {
121122

122123
// sanitize the base path from current url if existing
123124
const basePath = getRouterBasePath()
124-
if (basePath && pathname.startsWith(basePath)) {
125-
pathname = pathname.substring(basePath.length)
126-
}
125+
pathname = removeBasePathFromPath(pathname, basePath)
127126

128127
// sanitize the site from current url if existing
129128
if (siteRef) {
@@ -269,9 +268,7 @@ export const removeSiteLocaleFromPath = (pathName = '') => {
269268
let {siteRef, localeRef} = getParamsFromPath(pathName)
270269

271270
const basePath = getRouterBasePath()
272-
if (basePath && pathName.startsWith(basePath)) {
273-
pathName = pathName.substring(basePath.length)
274-
}
271+
pathName = removeBasePathFromPath(pathName, basePath)
275272

276273
// remove the site alias from the current pathName
277274
if (siteRef) {

packages/template-retail-react-app/app/utils/url.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ describe('getPathWithLocale', () => {
201201
// Caller uses basePath + path for window.location or full href
202202
expect(`${basePath}${path}`).toBe(`${basePath}/uk/fr/category/newarrivals-womens`)
203203
})
204+
205+
test('getPathWithLocale does not strip when path has basePath only as substring (e.g. /shop vs /shopping/cart)', () => {
206+
const basePath = '/shop'
207+
getRouterBasePath.mockReturnValue(basePath)
208+
209+
const location = new URL('http://localhost:3000/shopping/cart')
210+
const buildUrl = createUrlTemplate(mockConfig.app, 'uk', 'en-GB')
211+
212+
const path = getPathWithLocale('en-GB', buildUrl, {location})
213+
expect(path).toContain('/shopping')
214+
expect(path).not.toBe('/cart')
215+
})
204216
})
205217

206218
describe('createUrlTemplate tests', () => {

0 commit comments

Comments
 (0)