Replies: 49 comments 20 replies
-
@armspkt Can you expand a bit more on why you need this behavior (other than "just because nuxt does it this way"). Is there a specific use-case you're trying to solve? What's the practical use of having different urls point to the same content? Is there a benefit to having that behavior? |
Beta Was this translation helpful? Give feedback.
-
@Janpot I need this feature because many users on my website access via both (with a slash and without a slash) and I think it good to have those options that we don't have to redirect for the better user experience and I saw many sites work that way too. There is no specific use-case for Nextjs yet but There is a case about i18n localePath for Nuxtjs. nuxt/nuxt#6331 I think it better to have more options to choose from. Is there a specific use-case you're trying to solve?
What's the practical use of having different urls point to the same content?
Can you explain why those websites did not redirect? Is there a benefit to having that behavior?
I don't want it because Nuxtjs have it but I want it because it is nice to have feature. |
Beta Was this translation helpful? Give feedback.
-
Yeah, i think we need that, idk if this solutions is the best, but, need a solution. |
Beta Was this translation helpful? Give feedback.
-
Check their website's next.config.js file. I think that you will find your answer there. |
Beta Was this translation helpful? Give feedback.
-
Now in "next": "^9.4.5-canary.32" have a solution by default :) |
Beta Was this translation helpful? Give feedback.
-
Something wrong going on when using
Using |
Beta Was this translation helpful? Give feedback.
-
@Unsfer Do you know, by any chance, which browser throws this error? |
Beta Was this translation helpful? Give feedback.
-
It was some old version of FF, which doesn't sopport Object.entries. I think it's supposed to be polyfilled |
Beta Was this translation helpful? Give feedback.
-
A richer system would be very good to be able to decide in which paths redirects should be avoided at all costs. For example, in our case, as I commented here: We want to avoid 308 before 404 pages. We want this: And the current feature forces to: |
Beta Was this translation helpful? Give feedback.
-
@aralroca feel free to reach out to enterprise@vercel.com and we'll see what we can do for your specific case under enterprise support. |
Beta Was this translation helpful? Give feedback.
-
Can I disable it? Or can it be used only as a designated route? diff --git a/node_modules/next/dist/client/normalize-trailing-slash.js b/node_modules/next/dist/client/normalize-trailing-slash.js
index 9ceda38..71b277b 100644
--- a/node_modules/next/dist/client/normalize-trailing-slash.js
+++ b/node_modules/next/dist/client/normalize-trailing-slash.js
@@ -1,6 +1,6 @@
"use strict";exports.__esModule=true;exports.removePathTrailingSlash=removePathTrailingSlash;exports.normalizePathTrailingSlash=void 0;/**
* Removes the trailing slash of a path if there is one. Preserves the root path `/`.
- */function removePathTrailingSlash(path){return path.endsWith('/')&&path!=='/'?path.slice(0,-1):path;}/**
+ */function removePathTrailingSlash(path){return path;}/**
* Normalizes the trailing slash of a path according to the `trailingSlash` option
* in `next.config.js`.
*/const normalizePathTrailingSlash=process.env.__NEXT_TRAILING_SLASH?path=>{if(/\.[^/]+\/?$/.test(path)){return removePathTrailingSlash(path);}else if(path.endsWith('/')){return path;}else{return path+'/';}}:removePathTrailingSlash;exports.normalizePathTrailingSlash=normalizePathTrailingSlash;
diff --git a/node_modules/next/dist/lib/load-custom-routes.js b/node_modules/next/dist/lib/load-custom-routes.js
index c56f7e4..f67024f 100644
--- a/node_modules/next/dist/lib/load-custom-routes.js
+++ b/node_modules/next/dist/lib/load-custom-routes.js
@@ -5,5 +5,5 @@ const errMatches=err.message.match(/at (\d{0,})/);if(errMatches){const position=
// for not being a string
const{tokens,error}=tryParsePath(route.source);if(error){invalidParts.push('`source` parse failed');}sourceTokens=tokens;}// make sure no unnamed patterns are attempted to be used in the
// destination as this can cause confusion and is not allowed
-if(typeof route.destination==='string'){if(route.destination.startsWith('/')&&Array.isArray(sourceTokens)){const unnamedInDest=new Set();for(const token of sourceTokens){if(typeof token==='object'&&typeof token.name==='number'){const unnamedIndex=new RegExp(`:${token.name}(?!\\d)`);if(route.destination.match(unnamedIndex)){unnamedInDest.add(`:${token.name}`);}}}if(unnamedInDest.size>0){invalidParts.push(`\`destination\` has unnamed params ${[...unnamedInDest].join(', ')}`);}else{const{tokens:destTokens,error:destinationParseFailed}=tryParsePath(route.destination,true);if(destinationParseFailed){invalidParts.push('`destination` parse failed');}else{const sourceSegments=new Set(sourceTokens.map(item=>typeof item==='object'&&item.name).filter(Boolean));const invalidDestSegments=new Set();for(const token of destTokens){if(typeof token==='object'&&!sourceSegments.has(token.name)){invalidDestSegments.add(token.name);}}if(invalidDestSegments.size){invalidParts.push(`\`destination\` has segments not in \`source\` (${[...invalidDestSegments].join(', ')})`);}}}}}const hasInvalidKeys=invalidKeys.length>0;const hasInvalidParts=invalidParts.length>0;if(hasInvalidKeys||hasInvalidParts){console.error(`${invalidParts.join(', ')}${invalidKeys.length?(hasInvalidParts?',':'')+` invalid field${invalidKeys.length===1?'':'s'}: `+invalidKeys.join(','):''} for route ${JSON.stringify(route)}`);numInvalidRoutes++;}}if(numInvalidRoutes>0){if(hadInvalidStatus){console.error(`\nValid redirect statusCode values are ${[...allowedStatusCodes].join(', ')}`);}console.error();throw new Error(`Invalid ${type}${numInvalidRoutes===1?'':'s'} found`);}}async function loadRedirects(config){if(typeof config.redirects!=='function'){return[];}const _redirects=await config.redirects();checkCustomRoutes(_redirects,'redirect');return _redirects;}async function loadRewrites(config){if(typeof config.rewrites!=='function'){return[];}const _rewrites=await config.rewrites();checkCustomRoutes(_rewrites,'rewrite');return _rewrites;}async function loadHeaders(config){if(typeof config.headers!=='function'){return[];}const _headers=await config.headers();checkCustomRoutes(_headers,'header');return _headers;}async function loadCustomRoutes(config){const[headers,rewrites,redirects]=await Promise.all([loadHeaders(config),loadRewrites(config),loadRedirects(config)]);if(config.trailingSlash){redirects.unshift({source:'/:file((?:[^/]+/)*[^/]+\\.\\w+)/',destination:'/:file',permanent:true},{source:'/:notfile((?:[^/]+/)*[^/\\.]+)',destination:'/:notfile/',permanent:true});if(config.basePath){redirects.unshift({source:config.basePath,destination:config.basePath+'/',permanent:true,basePath:false});}}else{redirects.unshift({source:'/:path+/',destination:'/:path+',permanent:true});if(config.basePath){redirects.unshift({source:config.basePath+'/',destination:config.basePath,permanent:true,basePath:false});}}return{headers,rewrites,redirects};}
+if(typeof route.destination==='string'){if(route.destination.startsWith('/')&&Array.isArray(sourceTokens)){const unnamedInDest=new Set();for(const token of sourceTokens){if(typeof token==='object'&&typeof token.name==='number'){const unnamedIndex=new RegExp(`:${token.name}(?!\\d)`);if(route.destination.match(unnamedIndex)){unnamedInDest.add(`:${token.name}`);}}}if(unnamedInDest.size>0){invalidParts.push(`\`destination\` has unnamed params ${[...unnamedInDest].join(', ')}`);}else{const{tokens:destTokens,error:destinationParseFailed}=tryParsePath(route.destination,true);if(destinationParseFailed){invalidParts.push('`destination` parse failed');}else{const sourceSegments=new Set(sourceTokens.map(item=>typeof item==='object'&&item.name).filter(Boolean));const invalidDestSegments=new Set();for(const token of destTokens){if(typeof token==='object'&&!sourceSegments.has(token.name)){invalidDestSegments.add(token.name);}}if(invalidDestSegments.size){invalidParts.push(`\`destination\` has segments not in \`source\` (${[...invalidDestSegments].join(', ')})`);}}}}}const hasInvalidKeys=invalidKeys.length>0;const hasInvalidParts=invalidParts.length>0;if(hasInvalidKeys||hasInvalidParts){console.error(`${invalidParts.join(', ')}${invalidKeys.length?(hasInvalidParts?',':'')+` invalid field${invalidKeys.length===1?'':'s'}: `+invalidKeys.join(','):''} for route ${JSON.stringify(route)}`);numInvalidRoutes++;}}if(numInvalidRoutes>0){if(hadInvalidStatus){console.error(`\nValid redirect statusCode values are ${[...allowedStatusCodes].join(', ')}`);}console.error();throw new Error(`Invalid ${type}${numInvalidRoutes===1?'':'s'} found`);}}async function loadRedirects(config){if(typeof config.redirects!=='function'){return[];}const _redirects=await config.redirects();checkCustomRoutes(_redirects,'redirect');return _redirects;}async function loadRewrites(config){if(typeof config.rewrites!=='function'){return[];}const _rewrites=await config.rewrites();checkCustomRoutes(_rewrites,'rewrite');return _rewrites;}async function loadHeaders(config){if(typeof config.headers!=='function'){return[];}const _headers=await config.headers();checkCustomRoutes(_headers,'header');return _headers;}async function loadCustomRoutes(config){const[headers,rewrites,redirects]=await Promise.all([loadHeaders(config),loadRewrites(config),loadRedirects(config)]);if(config.trailingSlash){redirects.unshift({source:'/:file((?:[^/]+/)*[^/]+\\.\\w+)/',destination:'/:file',permanent:true},{source:'/:notfile((?:[^/]+/)*[^/\\.]+)',destination:'/:notfile/',permanent:true});if(config.basePath){redirects.unshift({source:config.basePath,destination:config.basePath+'/',permanent:true,basePath:false});}}return{headers,rewrites,redirects};}
//# sourceMappingURL=load-custom-routes.js.map
|
Beta Was this translation helpful? Give feedback.
-
I think not being forced to opt-in into automatic trailingSlash url normalisation would be very helpful. My team wanted to upgrade to latest version of Next.js recently but this change keeps us from doing so. Our use-case is international website that does not use trailing slashes at the end of the url except for home pages for different countries:
We would benefit from |
Beta Was this translation helpful? Give feedback.
-
related issue: #18164 And I agree, this should be optional |
Beta Was this translation helpful? Give feedback.
-
Definitely, the option of not doing any redirect should be implemented. |
Beta Was this translation helpful? Give feedback.
-
This is a blocking issue for our team. We have to support a plethora of different application routes for applications we integrate with, some containing hashbang urls, some ending with trailing slashes, and some without trailing slashes. These redirects as a result are mutating our application routes which have strict route checking. e.g.
Is there also currently a way to optionally redirect for hashbang urls, as some of the apps we integrate with use these. At present if we go to |
Beta Was this translation helpful? Give feedback.
-
Also running into this: I want |
Beta Was this translation helpful? Give feedback.
-
I would love such option to be present, as I'm currently facing issues with redirect chains because of this. |
Beta Was this translation helpful? Give feedback.
-
This issue is more than two years old now, and still no fix available? Did someone find a workaround? |
Beta Was this translation helpful? Give feedback.
-
I would love the option to be present as well. It's 2022 folks are there any updates regarding this potential feature? |
Beta Was this translation helpful? Give feedback.
-
Any updates on this? It seems we're forced into using But this now breaks our blog which is linked to our main site through a rewrite. It would be nice to override this behavior at the rewrite level. |
Beta Was this translation helpful? Give feedback.
-
We also need this customisation feature for Seo purposes to be able to do smthn like "region/" , "/region/country/" , but "/region/country/city" |
Beta Was this translation helpful? Give feedback.
-
In terms of design, how vercel.json is perfect actually. But since now changing this to default "undefined" will break things, maybe we can have something like; trailingSlash: "preserve" Seems to me this would fix a lot problems, while keeping the best practices out of the box? |
Beta Was this translation helpful? Give feedback.
-
Running into this problem also. Our CMS provides canonical URLs for structural pages with trailing slashes, and for content detail pages with numeric IDs but no trailing slashes. Being able to apply the trailingSlash behaviour to specific URL patterns would be desirable. |
Beta Was this translation helpful? Give feedback.
-
An experimental /** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
skipTrailingSlashRedirect: true,
},
} |
Beta Was this translation helpful? Give feedback.
-
Has anyone found a way to opt out of the trailing slash for a single url? My use-case is that the AWS HTTP API Gateway JWT authorizer does not work with a trailing slash on the openid-configuration path. Ideally, for me, I only want to intercept this single url like this:
But then all my routes are stripped of their trailing slash. If I add a |
Beta Was this translation helpful? Give feedback.
-
After hours of researching and testing I come up with something like this. In middleware.ts I added redirects from locale/pathname/ -> locale. Down below is my code, maybe it would help someone: middleware.ts
P.S. On the client side on initial page /en it adds trailing slash after few seconds, when the page is generated, but it does not affect HTTP requests. I checked on that tool https://technicalseo.com/tools/hreflang/. All pages ending with .../ are being redirected to pages without slash with 301 code and pages without slash return 200 |
Beta Was this translation helpful? Give feedback.
-
Adding another comment here to give reasons why we need these options other than "just cause nuxt has it" @Janpot We are experiencing issues with firebase authentication using our own domain. Our trailing slash breaks their auth routes. We have these rewrites which handles the custom domain stuff. Unfortunately our trailing slash breaks it and sends auth/handler to auth/handler/handler with their setup and the trailing slash.
So hving an option to just If we had one config option to just disable it for specific routes it would be a 30 second fix, we have spent hours trying ti fix this though and anything we do conflicts with NextJS or vice versa.
|
Beta Was this translation helpful? Give feedback.
-
We're having similar problem. Trailing slash is used site wide, but since we're migrating large site to Next, the original urls don't have trailing slashes and while trying to redirect them to new ones, it causes redirect chains (because first redirect is always 308 - adding the slash) which can harm SEO which is really important to us since there is a lot of traffic. It would be really helpful to be able to disable trailing slashes for certain routes. We even tried with the |
Beta Was this translation helpful? Give feedback.
-
Can I disable Today NextJS does this: Take this route from my page: And send it to: This behavior is harmful to me, can I disable this behavior? I'm on NextJS version |
Beta Was this translation helpful? Give feedback.
-
For me |
Beta Was this translation helpful? Give feedback.
-
Feature request
Is your feature request related to a problem? Please describe.
Nuxtjs have several options to choose trailing slash. (undefined, true, false)
nuxt-modules/i18n#422
Describe the solution you'd like
Nextjs should have more options for trailing slash.
If use
undefined
Nextjs should not redirect route when use URL that ends with a slashtrailingSlashes: true
/abc/ -> /abc/ (keeps the same)
/abc -> /abc/ (changes)
trailingSlashes: false
/abc -> /abc (keeps the same)
/abc/ -> /abc (changes)
trailingSlashes: undefined (default)
/abc/ -> /abc/ (keeps the same)
/abc -> /abc (keeps the same)
Beta Was this translation helpful? Give feedback.
All reactions