Closed iamstarkov closed 4 years ago
most relevant and notable issues are #1189 and #3876
Looking forward to this being finally resolved! @timneutkens What's the status of trailing slash issues for Next 7?
@NathanielHill I could reproduce it on next@7
I'm using nextjs 7 and trailing slash is producing a 404 for me on both dev and prod:
And affects:
Simply removing the trailing slash fixes the issue.
Trailing slashes are often added by browsers, servers and/or other services where links might be pasted so while I can control internal links, it's hard to control what at what links external users might be arriving on
I'm also seeing this issue in version 7. Not sure if this is relevant but, I'm aliasing one Next.js project to a subfolder of another Now deployment. So our base url is primer.style
and we are aliasing our primer-components.now.sh
Next.js app to primer.style/components
. On production, primer.style/components
's index page works fine, but primer.style/components/
produces a 404.
I had to search around a bit to find this issue. I use static deployments on Netlify so it's not an issue on prod, but on development (Next 7) the compilation just freezes if there was a trailing slash, and it was hard to figure out why. I don't think this (not handling trailing slash on dev environment) is a good DX.
Im also having this issue and it is really annoying, i hope it is fixed soon.
If you want trailing slash, you can just do this. <Link href='/about' as='/about/'><a>about</a></Link>
but if you're using fridays/next-routes this is not possible. So I have a fork where you can add trailingSlash
as prop. Hope this helps
If you want trailing slash, you can just do this.
<Link href='/about' as='/about/'><a>about</a></Link>
but if you're using fridays/next-routes this is not possible. So I have a fork where you can addtrailingSlash
as prop. Hope this helps
@aluminick I'm sorry, I just tried this and it doesnt work for me. I still get to traling-slashed page (latest release), which is not found after refresh (current behavior).
also neither #6664 nor #6752 help with these, because experimental.exportTrailingSlash
doesnt help because it is for next export
only, I believe
there was a promising pull request #6421 by @Janpot which didn't reach any consensus, unfortunately
@iamstarkov What's the status of this issue? Any solutions beside the server.js
hook?
@dryleaf status: it is still opened
A similar issue... redirect when multiple forward slashes are added. Example: https://github.com/zeit/next.js////////////issues/5214
GitHub urls are irrelevant
@iamstarkov Not sure what you mean. But after rereading my original post, it looks like I could have been more clear.
The GitHub url is meant to be a simple demonstration of how urls should (preferably) work when an app is built with Next.js. In other words, if a user adds an extra slash, the url should still work.
Any update for nextjs 9 ?
I'm new to Next but what is the workaround you folks are using for this issue?
@iamstarkov What's the status of this issue?
I'm shocked that this issue not solved in any way for about year! Do Next.js team need any other reasons to start fixing this?
URL's should work regardless of trailing slash. Check any site on the web.
If this is out of scope of Next.js, give us ability to configure this in Now. I'm really confused that Zeit team ignores such critical issues for years.
@exentrich This is easily configurable in Zeit Now by simply 301 redirecting all trailing slashes to the same route without slashes:
now.json
:
"routes": [
{
"src": "/(.*)/",
"status": 301,
"headers": { "Location": "/$1" }
},
...
]
However, I also don't understand why this is not handled by Next.js itself and why the team has ignored this issue.
This, along with a public/
(in the works) are the main issues I see CRA converts running in to.
@rauchg
@NathanielHill thanks!
I tried this solution, but query parameters are stripped out. For example /some/?query=1
will redirect to /some
without query. Do you know how to fix it?
Yeah, that sounds like a problem @exentrich
I wouldn't have guessed that behavior as I've been told there's an implicit ^
and $
wrapped around the regex (meaning your example wouldn't match). Maybe there's a way to access the query string on it's own to add it back :man_shrugging: Good luck
Trying to get it working using a custom express server and avinoamr/connect-slashes but seem to be running into the same issue
This is certainly a massive issue especially because /
routes throw Error pages and that hurts SEO (which is one of the primary draws of Next).
The 301 redirects and the custom express servers all seem to be hacks rather than fixes. In my case, I have a full working application built on Next with no custom Express server - everything else works perfectly, but now I'm having to create a new Express server only because of the trailing slash issue. The effort required seems to be disproportionate considering this is a hack. I would love if this could be bumped up in priority! Due to this reason, I'm hearing grumblings in my team about having used Next as opposed to something like vanilla React/Angular and it certainly weakens the case for Next.
PS: I absolutely love working with Next ❤️
This is certainly a massive issue especially because
/
routes throw Error pages and that hurts SEO
It doesn't hurt your SEO. google treats trailing slash as a different page. Having it 404 doesn't impact SEO any more than any other non existing page in your site. Besides, as long as you never link to it with a trailing slash, google won't try to crawl it in the first place. This issue, while still being a valid issue, is way less critical than you all make it to be.
@nik-john @NathanielHill @dkrish @exentrich
You shouldn't have to use an Express server to do a 301 Redirect. Depends on your requirements, but I've been able to meet mine with a custom server.js
.
A 301 redirect is also the best way to go for SEO, as you won't get duplicate content penalties for the slash and non-slash route.
I love ❤️ Next.js, but I vote for this to be handled without this work around.
// server.js
const { createServer } = require('http');
const { parse } = require("url");
const next = require("next");
const dev = process.env.NODE_ENV !== 'production'
const port = parseInt(process.env.PORT, 10) || 3000;
const app = next({ dev, quiet: false });
const handle = app.getRequestHandler();
(async () => {
await app.prepare();
const server = createServer();
server.on('request', async (req, res) => {
const parsedUrl = parse(req.url, true);
const { pathname, query } = parsedUrl;
if (pathname.length > 1 && pathname.slice(-1) === "/") {
console.log('server.js - redirect on "/"...', pathname, query);
const queryString = await Object.keys(query).map(key => key + '=' + query[key]).join('&');
res.writeHead(301, { Location: pathname.slice(0, -1) + (queryString ? '?'+ queryString : '') });
res.end();
}
handle(req, res, parsedUrl);
});
await server.listen(port);
console.log(`🚀 Ready on http://localhost:${port}`);
})();
@Janpot
It doesn't hurt your SEO. google treats trailing slash as a different page. Having it 404 doesn't impact SEO any more than any other non existing page in your site.
I take your point that it doesn't particularly hurt SEO innately. But it does put additional pressure on developers to get the URL definitions right every time, which is subject to human errors. A developer that is new to Next wouldn't necessarily know that the following (perfectly normal looking) URL will lead to a 404 page. <Link href='/people/'>
A mature framework shouldn't be subject to such human errors ideally imo.
Besides, as long as you never link to it with a trailing slash, google won't try to crawl it in the first place.
Again - there exists the issue of people accidentally linking to the www.mysite.com/people/ instead of www.mysite.com/people (both of which seem to be exactly the same for users - even most developers).
Both of these scenarios can affect SEO.
Now, not considering the SEO impact, there's also the semantic meaning of the URL - what does www.mysite.com/people/ point to? Ideally because it is pointing to a directory, Next should return whatever is in pages > people > index.js
(as opposed to pages > people.js
for www.mysite.com/people) but instead it returns nothing, which is a very high level flaw in how the routing works.
Major routing libraries already have some provision for this - like isExact
in the case of React Router
While I understand where you're coming from, I still think this is a glaring issue that needs to be bumped up
This is also completely unavoidable in the case of next export
there exists the issue of people accidentally linking...
There exists the issue of people accidentally linking to any non-existing url, why would/some/path/
be less non-existing than /some/path/dhgfiuwo
?
there's also the semantic meaning of the URL
This is highly subjective, as far as I know there is no spec out there that dictates what is the semantic difference. According to the URL spec, with and without trailing slash are considered different urls. I can think of at least 7 different valid behaviours:
Pair this with the possibility of having either /pages/some-page.js
and /pages/some-page/index.js
(or both).
Should next.js support all those use cases? Should it pick a default behaviour?
I'm not against this, but after trying to implement this before, I just think there's more nuance to it than it initially seems.
There exists the issue of people accidentally linking to any non-existing url, why would/some/path/ be less non-existing than /some/path/dhgfiuwo?
For case /some/path/dhgfiuwo
- people expect tha dhgfiuwo
route can to be missing. (For example, user dhgfiuwo
cannot be found in system and way users/dhgfiuwo
is wrong. The absence of a user in the system is an expected occurrence.)
For case /some/path/
- people expect this path to be same as /some/path
, because this is default behavior on other sites.
Therefore, a failure in would/some/path/
is less non-existing than /some/path/dhgfiuwo
.
I see others have posted their solutions, so I wanted to share my approach : https://github.com/DevSpeak/next-trailingslash
Some improvements and support for dynamic routed pages when it comes to ?= should be done IMO, but this is only for showing the idea.
For a quick solution, you can replace the default _error
page (as in @DevSpeak's example).
@DevSpeak, I'd recommend a few changes for your repo:
if (typeof window === 'undefined') { ... }
to tree-shake it from the client bundleHere's what I'm using in a Typescript project (based on the built-in error page):
/pages/_error.tsx
(or remove the TypeScript types and name it /pages/_error.jsx
):
import React from 'react';
import Head from 'next/head';
import { NextPageContext } from 'next';
const statusCodes: { [code: number]: string } = {
400: 'Bad Request',
404: 'This page could not be found',
405: 'Method Not Allowed',
500: 'Internal Server Error'
};
export type ErrorProps = {
statusCode: number;
title?: string;
};
/**
* `Error` component used for handling errors.
*/
export default class Error<P = {}> extends React.Component<P & ErrorProps> {
static displayName = 'ErrorPage';
static getInitialProps({
req,
res,
err
}: NextPageContext): Promise<ErrorProps> | ErrorProps {
const statusCode =
res && res.statusCode ? res.statusCode : err ? err.statusCode! : 404;
if (typeof window === 'undefined') {
/**
* Workaround for: https://github.com/zeit/next.js/issues/8913#issuecomment-537632531
* Test vectors:
* `/test/test/` -> `/test/test`
* `/test/////test////` -> `/test/test`
* `/test//test//?a=1&b=2` -> `/test?a=1&b=2`
* `/test///#test` -> `/test#test`
*/
const correctPath = (invalidPath: string) =>
invalidPath
.replace(/\/+$/, '')
.replace(/\/+#/, '#')
.replace(/\/+\?/, '?')
.replace(/\/+/g, '/');
if (req && res && req.url && correctPath(req.url) !== req.url) {
res.writeHead(302, {
Location: correctPath(req.url)
});
res.end();
}
const reqInfo = req
? `; Url: ${req.url}; IP: ${req.headers['x-forwarded-for'] ||
(req.connection && req.connection.remoteAddress)};`
: '';
console.log(`Error rendered: ${statusCode}${reqInfo}`);
}
return { statusCode };
}
render() {
const { statusCode } = this.props;
const title =
this.props.title ||
statusCodes[statusCode] ||
'An unexpected error has occurred';
return (
<div style={styles.error}>
<Head>
<title>
{statusCode}: {title}
</title>
</Head>
<div>
<style dangerouslySetInnerHTML={{ __html: 'body { margin: 0 }' }} />
{statusCode ? <h1 style={styles.h1}>{statusCode}</h1> : null}
<div style={styles.desc}>
<h2 style={styles.h2}>{title}.</h2>
</div>
</div>
</div>
);
}
}
const styles: { [k: string]: React.CSSProperties } = {
error: {
color: '#000',
background: '#fff',
fontFamily:
'-apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif',
height: '100vh',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
desc: {
display: 'inline-block',
textAlign: 'left',
lineHeight: '49px',
height: '49px',
verticalAlign: 'middle'
},
h1: {
display: 'inline-block',
borderRight: '1px solid rgba(0, 0, 0,.3)',
margin: 0,
marginRight: '20px',
padding: '10px 23px 10px 0',
fontSize: '24px',
fontWeight: 500,
verticalAlign: 'top'
},
h2: {
fontSize: '14px',
fontWeight: 'normal',
lineHeight: 'inherit',
margin: 0,
padding: 0
}
};
Note, this also logs an error when the page is hit, so you can check your logs to fix any links/other issues.
@DevSpeak @bitjson Thanks for your suggestions. That's certainly one way to go about this and certainly solves the issue very well. But considering that the _error.jsx
is originally meant to handle errors and not house routing logic, in my opinion having all of this code there is hacky and quite declarative. Expecting every user to do this in every code base shouldn't be a requirement - this should come out of the box. = I am of the opinion that this condition needs to be built in with the routing logic, with an option to opt out like React Router.
@NathanielHill
This is also completely unavoidable in the case of next export
Wait - I understood from reading the documentation that there is specific code to handle the trailing slash condition:
The pages will be exported as html files, i.e. /about will become /about.html.
It is possible to configure Next.js to export pages as index.html files and require trailing slashes, i.e. /about becomes /about/index.html and is routable via /about/. This was the default behavior prior to Next.js 9. You can use the following next.config.js to switch back to this behavior:
// next.config.js module.exports = { exportTrailingSlash: true, }
Even if this isn't really an option for static HTML export via next export
, I don't agree with the logic that just because Next supports this (amazing) feature, other modes need to suffer (I don't know the usage stats but I would assume more people use the regular with-server mode as opposed to serverless) especially when this has been known to be such a common use case
FYI: There's an RFC that might interest you https://github.com/zeit/next.js/issues/9081
// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/:path*/",
destination: "/:path",
statusCode: 301
}
];
}
};
@Janpot Love it - this will bring us half-way i.e. have some sort of support for redirects without having to create a custom server. This is still going to be imperative because for every route the user adds, they would have to set up a redirect in the next.config.js
- or maybe we could just use a regex to catch all cases like @bitjson mentioned:
invalidPath
.replace(/\/+$/, '')
.replace(/\/+#/, '#')
.replace(/\/+\?/, '?')
.replace(/\/+/g, '/')
In either case, if the core team is prioritizing this RFC, I would highly recommend we go one step further and make it a built in config that one can opt out of like so
// next.config.js
module.exports = {
ignoreStrictRoutes: false, // default value: true
};
All in all, I think this is a great step forward - good stuff @Timer!! 🔥
@nik-john The path I specified in "/:path*/"
Should catch all (:path
catches a single segment, *
makes it catch 0 to n instances.)
@Janpot Ah my bad 🤦♂ I am guessing we would also need to consider any trailing query params in that regex
Also, I still stand by the second part though:
In either case, if the core team is prioritizing this RFC, I would highly recommend we go one step further and make it a built in config that one can opt out of like so
// next.config.js
module.exports = {
ignoreStrictRoutes: false, // default value: true
};
If you are using a custom server and want to ignore strict routes you can also use a custom route handler instead of making a redirect.
app.render(req, res, urlWithoutTrailingSlash, query);
This way we are able to support both /path
and /path/
and resolve to the same page.
Oauth federation providers often require trailing forward slashes so this behavior makes a simple flow very complicated. What is the technical challenge in implementing this behavior? Or is this a design decision from next?
I haven't seen it mentioned thus far in this thread, but I am not experiencing this issue after deployment with Now, I'm only experiencing it locally when testing with now dev
.
const removeTrailingSlashes = (req, res, expressNext) => {
if (req.path.substr(-1) === '/' && req.path.length > 1) {
const query = req.url.slice(req.path.length);
res.redirect(301, req.path.slice(0, -1) + query);
} else {
expressNext();
}
};
got this from stackoverflow and worked perfectly. this solution work with express.
@GaneshKathar I don't see how this will work if you account for Next.js not using express
I think we can't agree on this and it should be configurable.
I actually want the trailing slash always, the relative urls are easier to reason about when all pages end with trailing slash.
For instance it makes no sense that /about/index.tsx
is /about
instead of /about/
, but understandable now that next expects without trailing slash. If all pages were to end in slash it would allow pages to contain subpages in the future, which I think is more extensible way for pages.
Making relative links inside /about/index.tsx
file is now cumbersome. If you make a link ./mysubpage/
it points to root of the site instead. This makes the subpages non-renameable. I can't make a directory /about/
full of pages that I can just rename, because I should go and edit the relative links too.
Also the wget -r
site produces sensible results with having always trailing slashes, producing index.html files.
However changing this setting is massively breaking change since all sites expect non-trailing slashes, so it must be configurable.
I'm using version 9 and this issue is still not resolved
I was able to make it work by using something like the following on my next.config.js
:
exportPathMap: async function() {
const paths = {
'/': { page: '/' },
'/authors/index.html': { page: '/authors' },
};
return paths;
},
Accessing /authors
gives 302 pointing location
to /authors/
. I'm testing with http-serve
, not sure if this behavior is server-specifiy.
when I faced this issue I came up with this solution
in my _error.js
page
Error.getInitialProps = ({ res, err, asPath }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
const checkForTrailingSlashes = () => {
if (asPath.match(/\/$/)) { // check if the path ends with trailing slash
const withoutTrailingSlash = asPath.substr(0, asPath.length - 1);
if (res) {
res.writeHead(302, {
Location: withoutTrailingSlash
})
res.end()
} else {
Router.push(withoutTrailingSlash)
}
}
}
if (statusCode && statusCode === 404) {
checkForTrailingSlashes();
} else {
//
}
return { statusCode };
}
is it a good way to overcome the issue ?
How about this?
pages/_app.jsx
import App from 'next/app';
export default class MyApp extends App {
render() {
const { Component, pageProps, router: { asPath } } = this.props;
// Next.js currently does not allow trailing slash in a route.
// This is a client side redirect in case trailing slash occurs.
if (asPath.length > 1 && asPath.endsWith('/')) {
const urlWithoutEndingSlash = asPath.replace(/\/*$/gim, '');
if (typeof window !== 'undefined') {
window.location.replace(urlWithoutEndingSlash);
}
return null;
}
return <Component {...pageProps} />;
}
}
@cnblackxp thanks for the suggestion. That helped me. Here is how I implemented it in order to keep the default behavior for non-trailing 404s (i.e. I am simply re-exporting the default Error
implementation):
import Error from "next/error";
import Router from "next/router";
export default Error;
Error.getInitialProps = ({ res, err, asPath }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
if (statusCode && statusCode === 404) {
if (asPath.match(/\/$/)) {
const withoutTrailingSlash = asPath.substr(0, asPath.length - 1);
if (res) {
res.writeHead(302, {
Location: withoutTrailingSlash
});
res.end();
} else {
Router.push(withoutTrailingSlash);
}
}
}
return { statusCode };
};
yep that'll do @cansin as long as nothing else is decided :) cheers!
Small improvement to @AlexSapoznikov's workaround:
render() {
const { Component, pageProps, router: { asPath } } = this.props;
// Next.js currently does not allow trailing slash in a route.
// This is a client side redirect in case trailing slash occurs.
if (pageProps.statusCode === 404 && asPath.length > 1 && asPath.endsWith('/')) {
The only difference here is checking that the status code is 404. I ran into issues using Link for dynamic routes where they were always rendering on the server because of the redirect. If you want client-side routing to work, you can't add a trailing slash to the Link href
prop, but then you need to make sure you don't redirect in this case.
trailing slash in link for legit page works for client side navigation but leads to not found bundle and 404 on hard refresh (ssr)
Bug report
Describe the bug
let me know if title needs further clarification.
all relevant issues has been closed with reasoning that its been fixed in 6-canary (I believe it is not) or by improved serve (which is true only in perhaps production static export).
I'm rewriting my existing blog to next.js and i previously used trailing slashes. Latest
serve
can help with it once i build my next.js powered blog. But in order to fix dev env i need either to get rid of trailing slashes and utilize301 Moved Permanently
in prod; or live with broken trailing slash support in dev.To Reproduce
Here is minimal reproducible case (link to repro repo is below snippet):
Minimal reproducible repo https://github.com/iamstarkov/next.js-trailing-slash-bug-demo
git clone https://github.com/iamstarkov/next.js-trailing-slash-bug-demo
cd next.js-trailing-slash-bug-demo
yarn
yarn dev
http://localhost:3000/_next/static/development/pages/about.js
being 200edhttp://localhost:3000/_next/on-demand-entries-ping?page=/about/
being 200edhttp://localhost:3000/about/
being 404edhttp://localhost:3000/about/
Client pings, but there's no entry for page: /about/
Expected behavior
/about/
shouldnt be resolved as404 not found
/about/
should be resolved as200 ok
Client pings, but there's no entry for page: /about/
/about
and/about/
should work the same wayScreenshots
N/A
System information
Additional context
Add any other context about the problem here.
If you change this code in https://github.com/zeit/next.js/blob/459c1c13d054b37442126889077b7056269eeb35/server/on-demand-entry-handler.js#L242-L249
or
node_modules/next/dist/server/on-demand-entry-handler.js
locallyand restart
next dev
and open http://localhost:3000/ and click about link then:/about
/about/
:I think the problem (at least part of it) is in inability of onDemandEntryHandler's middleware to find page in entries if page has trailing slash.
I hope my 2 hours of investigation and preparation can help with fixing this issue.