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',