diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index 4d19cd2ba268c..f87bb7bbb77b1 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -161,6 +161,9 @@ export function getDefineEnv({
'process.env.__NEXT_ALLOW_MIDDLEWARE_RESPONSE_BODY': JSON.stringify(
config.experimental.allowMiddlewareResponseBody
),
+ 'process.env.__NEXT_MANUAL_TRAILING_SLASH': JSON.stringify(
+ config.experimental?.skipTrailingSlashRedirect
+ ),
'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(config.crossOrigin),
'process.browser': JSON.stringify(isClient),
'process.env.__NEXT_TEST_MODE': JSON.stringify(
diff --git a/packages/next/client/normalize-trailing-slash.ts b/packages/next/client/normalize-trailing-slash.ts
index d2405675a1de4..11547936af6b2 100644
--- a/packages/next/client/normalize-trailing-slash.ts
+++ b/packages/next/client/normalize-trailing-slash.ts
@@ -6,7 +6,7 @@ import { parsePath } from '../shared/lib/router/utils/parse-path'
* in `next.config.js`.
*/
export const normalizePathTrailingSlash = (path: string) => {
- if (!path.startsWith('/')) {
+ if (!path.startsWith('/') || process.env.__NEXT_MANUAL_TRAILING_SLASH) {
return path
}
diff --git a/test/e2e/skip-trailing-slash-redirect/app/pages/index.js b/test/e2e/skip-trailing-slash-redirect/app/pages/index.js
index 04fbd07fee5b1..8cf5943c3cd91 100644
--- a/test/e2e/skip-trailing-slash-redirect/app/pages/index.js
+++ b/test/e2e/skip-trailing-slash-redirect/app/pages/index.js
@@ -8,6 +8,10 @@ export default function Page(props) {
to another
+
+ to another
+
+
to /blog/first
diff --git a/test/e2e/skip-trailing-slash-redirect/index.test.ts b/test/e2e/skip-trailing-slash-redirect/index.test.ts
index e31d7a6909fc6..59013025d091c 100644
--- a/test/e2e/skip-trailing-slash-redirect/index.test.ts
+++ b/test/e2e/skip-trailing-slash-redirect/index.test.ts
@@ -158,6 +158,54 @@ describe('skip-trailing-slash-redirect', () => {
expect(await res.text()).toContain('another page')
})
+ it('should not apply trailing slash to links on client', async () => {
+ const browser = await webdriver(next.url, '/')
+ await browser.eval('window.beforeNav = 1')
+
+ expect(
+ new URL(
+ await browser.elementByCss('#to-another').getAttribute('href'),
+ 'http://n'
+ ).pathname
+ ).toBe('/another')
+
+ await browser.elementByCss('#to-another').click()
+ await browser.waitForElementByCss('#another')
+
+ expect(await browser.eval('window.location.pathname')).toBe('/another')
+
+ await browser.back().waitForElementByCss('#to-another')
+
+ expect(
+ new URL(
+ await browser
+ .elementByCss('#to-another-with-slash')
+ .getAttribute('href'),
+ 'http://n'
+ ).pathname
+ ).toBe('/another/')
+
+ await browser.elementByCss('#to-another-with-slash').click()
+ await browser.waitForElementByCss('#another')
+
+ expect(await browser.eval('window.location.pathname')).toBe('/another/')
+
+ await browser.back().waitForElementByCss('#to-another')
+ expect(await browser.eval('window.beforeNav')).toBe(1)
+ })
+
+ it('should not apply trailing slash on load on client', async () => {
+ let browser = await webdriver(next.url, '/another')
+ await check(() => browser.eval('next.router.isReady ? "yes": "no"'), 'yes')
+
+ expect(await browser.eval('location.pathname')).toBe('/another')
+
+ browser = await webdriver(next.url, '/another/')
+ await check(() => browser.eval('next.router.isReady ? "yes": "no"'), 'yes')
+
+ expect(await browser.eval('location.pathname')).toBe('/another/')
+ })
+
it('should not apply trailing slash redirect (with slash)', async () => {
const res = await fetchViaHTTP(next.url, '/another/', undefined, {
redirect: 'manual',