Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove trailing slash from match.url #4841

Closed
shawnmclean opened this issue Mar 26, 2017 · 45 comments
Closed

Remove trailing slash from match.url #4841

shawnmclean opened this issue Mar 26, 2017 · 45 comments

Comments

@shawnmclean
Copy link
Contributor

Version

4.0.0

Test Case

https://reacttraining.com/react-router/web/example/recursive-paths

Steps to reproduce

In the mini browser, put a trailing slash after a resource like: /3/

Expected Behavior

nested url should look like /3/2

Actual Behavior

nested url looks like /3//2

Is this something thats possible?

@pshrmn
Copy link
Contributor

pshrmn commented Mar 26, 2017

That is what the strict prop is for. I'm not in a position to test it right now, but I believe that that would fix the recursive example. Anyways, once relative path support is merged in, this will be moot for most users since React Router will handle the path joining for them.

@shawnmclean
Copy link
Contributor Author

The strict didn't help my case because I'd need to support both trailing and non trailing slash. This does have to do with relative paths. So for now, I'm removing the trailing slash from match.url when combining it with my relative path. This is messy but will have to do until your PR is merged in. Any plans to fix the merge conflicts and merge it in?

@pshrmn
Copy link
Contributor

pshrmn commented Mar 26, 2017

Relative <Route>s and <Link>s should happen at the same time (probably a short beta to verify they work as expected). I have the code rebased, but I am waiting on a decision for what to do when the parent match is null (caused by using <Route children>) before moving forward.

For the time being I would just use

const join = (base, path) => {
  return base.charAt(base.length-1) === '/'
    ? base.slice(0, -1) + path
    : base + '/' + path
}

<Route path={join(match.url, '/rest')} component={Rest} />

@otbe
Copy link

otbe commented Jun 20, 2017

Very unexpected behavior at a first glance. Some guidance would be nice :)

We use a lot of this <Link to={`${match.url}/foo`}>Foo</Link> which will result in example.com/bar/foo or example.com/bar//foo whether you use example.com/bar or example.com/bar/ from a user point of view.

Note: a node path's like join function is totally fine for me :)

@robmoorman
Copy link

Experiencing the same unpredictable route match.url trailing slashes, especially with nested routes

@filoxo
Copy link

filoxo commented Nov 14, 2017

I solved this at the parent route level by redirecting to the no-slash-url.

<Switch>
  ...
  <Redirect strict from={url + '/'} to={url} />
  <Route path={url} component={App} />
</Switch>

This way the url matched never contains the trailing slash. Just another option.

@tomachinz
Copy link

Yeah I'd like some guidance. Should I be aiming to have slashes at end or not? Coming from WordPress the answer is yes I'd like a / at the end of everything. As I began to re-work my code to support that I begin to see example.com/bar//foo as well, making me think no trailing slash is the standard with React.

@leite08
Copy link

leite08 commented Dec 4, 2017

I struggled for quite some time to get a workaround for this.

I also learned about strict, but as @shawnmclean mentioned, it doesn't fix the main issue here: to configure React Router (RR) to remove the trailing slash at the end of the URL by default.

This comment on a strict-related issue worked for me:
#4170 (comment)

Our structure ended up like this:

const RemoveTrailingSlash = ({location}) => {
...
}
...
<Router>
      <Navigation {...props} />
      <Route path="/" render={RemoveTrailingSlash} />
      <Grid>
        <Switch>
          <Route...

I really suggest there is a configuration on RR that would remove all the trailing slashes on URLs.

Hope this comment can help people looking for this.

This was referenced Dec 4, 2017
@todesstoss
Copy link

todesstoss commented Dec 20, 2017

@leite08 thanks for the solution, it works for most cases. But when I have top-level <Route> component with multilingual configuration, e.g path="/:lang(en|it)?" and try to use <Link to={`${match.url}/foo`} so URL could respect the current matched language, it works only if lang param is not omitted e.g: site.com/en/foo, but for default case, I end up with a URLs like this: site.com//foo. It happens because match.url contains slash for root level URL.

@hazelsparrow
Copy link

Having two routes -- the first being strict and exact and the other being a fallback route -- solves the problem.

      <Redirect strict exact from={match.path} to={`${match.url}/foo`} />
      <Redirect exact from={match.path} to={`${match.url}foo`} />

Any thoughts on potential problems/caveats?

@inoyakaigor
Copy link

I think trailing slash should be removed from match.url. This behavior is not obvious and should be clarified in documentation

@tomachinz
Copy link

It is also "Not The Unix Way" of doing things to use /something when it's not at root.

@tomachinz
Copy link

Having two routes -- the first being strict and exact and the other being a fallback route -- solves the problem.

      <Redirect strict exact from={match.path} to={`${match.url}/foo`} />
      <Redirect exact from={match.path} to={`${match.url}foo`} />

Any thoughts on potential problems/caveats?

This is good I will use this double spec approach if my troubles come again.

@hilo2120
Copy link

hilo2120 commented Jun 11, 2019

I'm facing the same problem,
I solved it in the main App's Switch Component using regex to delete the last trailing slash from the path

const pathname = window.location.pathname
// ...
<Switch>
     {
          pathname!== "/" && (/\/$/).test(pathname) &&
          <Redirect to={pathname.slice(0, -1)} />
     }
     <PrivateRoute path={'/'} exact component={Dashboard}/>
     <Route path={'/login'} exact component={Account}/>
     ...
</Switch>

It's working now but looks a little bit not right!

@dylanwulf
Copy link

I was using @filoxo 's solution, but it recently stopped working in the 5.0.1 release

@LAITONEN
Copy link

Any progress?

@jrrdev
Copy link

jrrdev commented Aug 21, 2019

I was using @filoxo 's solution, but it recently stopped working in the 5.0.1 release

@dylanwulf You can try to use the following code, it works for me with the 5.0.1 release :

<Switch>
  {/* Removes trailing slashes */}
  <Route
    path="/:url*(/+)"
    exact
    strict
    render={({ location }) => (
      <Redirect to={location.pathname.replace(/\/+$/, "")} />
    )}
  />
  {/* Removes duplicate slashes in the middle of the URL */}
  <Route
    path="/:url(.*//+.*)"
    exact
    strict
    render={({ match }) => (
      <Redirect to={`/${match.params.url.replace(/\/\/+/, "/")}`} />
    )}
  />

A little bit hackish but I get correcting routing also with:

  • /foo/////bar
  • /foo/bar///

@hosseinalipour
Copy link

I used the following code in my main component I define the router, but I'm not sure If this is the best practice for React Router

  if (props.location.pathname.endsWith('/')) {
    props.location.pathname = props.location.pathname.slice(0, -1);
  }

@Ovi
Copy link

Ovi commented Sep 17, 2019

I did a workaround:

export const path = url => {
  const fixedPath = url.endsWith('/')
    ? path(url.substring(0, url.length - 1))
    : url;

  return fixedPath;
};

then:

<Redirect to={path('/dashboard/')} />
// returns => /dashboard

<Redirect to={path('/dashboard///')} />
// returns => /dashboard

@divonelnc
Copy link

I tried the workarounds above that detect the trailing '/' and fix it with a <Redirect/> but they triggered a page reload at every redirect.

To fix that I used this code instead:

const pathname = window.location.pathname;
if(pathname !== "/" && (/\/$/).test(pathname)){
    props.history.replace(pathname.slice(0, -1));
}

Using replace removed the trailing space without reloading the page.

@hosseinalipour
Copy link

hosseinalipour commented Oct 23, 2019

@divonelnc, did you check for other browsers?

@divonelnc
Copy link

@hosseinalipour Tested on Chrome, Firefox, and Edge so far. Any reason why it wouldn't work on some browsers?

@stale
Copy link

stale bot commented Dec 23, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale label Dec 23, 2019
@inoyakaigor
Copy link

This issue is not a stale stupid bot

@stale stale bot removed the stale label Dec 23, 2019
@timdorr timdorr added the fresh label Dec 27, 2019
@rollacaster
Copy link

rollacaster commented Mar 23, 2020

I am not sure if this is related but I fixed the my trailing slash issues by changing my webpack config.

If I started webpack-dev-server within my /src folder and if one of the folder names within my /src folder clashed with a route name I got an additional trailing slash after a browser refresh.

I started webpack-dev-server in another folder and it worked fine.

@bertho-zero
Copy link

My problem is that if my nested routes are in a parent route like /supervisor it works, if my parent route is '' then it becomes '/' with useRouteMatch.

My workaround:

const { path, url } = useRouteMatch();

<Link to={`${url.endsWith('/') ? url.slice(0, -1) : url}/announcement`} />

<Route path={`${path.endsWith('/') ? path.slice(0, -1) : path}/announcement`} />

@RhysTowey
Copy link

Adding a <Redirect> is the cleanest solution I could find

function App() {
    const { pathname } = useLocation();
    return (
        <div>
            <Switch>
                <Redirect from="/:url*(/+)" to={pathname.slice(0, -1)} />
                <Route exact path="/" component={Home} />
                <Route path="/test-page" component={TestPage} />
            </Switch>
        </div>
    );
}

Credit to: https://jasonwatmore.com/post/2020/03/23/react-router-remove-trailing-slash-from-urls

@bertho-zero
Copy link

It is not enough to build Links and Routes in the nested routes.

useRouteMatch continues to return a slash for path and url instead of an empty string, which does not allow to construct an url like that:

`${url}/announcement`

@Akatsukac
Copy link

Akatsukac commented May 12, 2020

            <Redirect from="/:url*(/+)" to={pathname.slice(0, -1)} />

Hey just want to let you know that navigating to something like /hello//world with this redirect pattern results in the url being rewritten in an unexpected manner (to /hello in this example). I would not recommend using this solution.

@bertho-zero
Copy link

bertho-zero commented May 13, 2020

There is no need to navigate to a url ending in /, if the url is '' then it becomes automatically and in any case '/', which is annoying for composing sub-routes.

In my case it works perfectly if I have /prefix, for example, but in my storybook without 'base' it doesn't work.

The only solution that works for me is: #4841 (comment)

@Akatsukac
Copy link

Akatsukac commented May 13, 2020

@dylanwulf You can try to use the following code, it works for me with the 5.0.1 release :

<Switch>
  {/* Removes trailing slashes */}
  <Route
    path="/:url*(/+)"
    exact
    strict
    render={({ location }) => (
      <Redirect to={location.pathname.replace(/\/+$/, "")} />
    )}
  />
  {/* Removes duplicate slashes in the middle of the URL */}
  <Route
    path="/:url(.*//+.*)"
    exact
    strict
    render={({ match }) => (
      <Redirect to={`/${match.params.url.replace(/\/\/+/, "/")}`} />
    )}
  />

A little bit hackish but I get correcting routing also with:

  • /foo/////bar
  • /foo/bar///

For those interested in leveraging redirects, here's a slight improvement on removing all extra slashes:

{/* Removes duplicate slashes */}
<Route exact strict path="(.*//+.*)" render={({ location }) => <Redirect to={location.pathname.replace(/\/\/+/g, '/')} />} />

This handles cases where you have extra slashes at the beginning, middle, or end and makes all replacements at once so you don't have to chain redirects (ie ///hello//world//url -> /hello//world//url -> /hello/world//url -> /hello/world/url).

+1 on @jrrdev approach to removing the trailing slash.

@Bielik20
Copy link

Bielik20 commented Jan 13, 2021

I also did change replace value from '' to location.search so that we do not loose query params:

export const RemoveTrailingSlash: React.FC = () => {
  return (
    <Route
      path="/:url*(/+)"
      exact
      strict
      render={({ location }) => <Redirect to={location.pathname.replace(/\/+$/, location.search)} />}
    />
  );
};

@sahil-ag
Copy link

sahil-ag commented Apr 7, 2021

For those interested in leveraging redirects, here's a slight improvement on removing all extra slashes:

{/* Removes duplicate slashes */}
<Route exact strict path="(.*//+.*)" render={({ location }) => <Redirect to={location.pathname.replace(/\/\/+/g, '/')} />} />

This handles cases where you have extra slashes at the beginning, middle, or end and makes all replacements at once so you don't have to chain redirects (ie ///hello//world//url -> /hello//world//url -> /hello/world//url -> /hello/world/url).

Modified this a little to keep the URL same -

<Redirect
    exact
    strict
    from="(.*//+.*)"
    to={location.pathname.replace(/\/\/+/g, '/') + window.location.search + window.location.hash}
/>

This also ends up handling URLs like - //hello/world?a=123#112 or ///hello/world?a=123#112 and hence can be added without any issues.

@Ovi

This comment has been minimized.

@mleister97
Copy link

mleister97 commented Apr 18, 2021

Actually I don't think this is an issue but the intended behavior.
The provided hooks just return the current location/path/whatever

The following snippet will always remove the trailing slash and should therefore fix your issue:

<Switch>
    <Redirect from="/:url*(/+)" to={window.location.pathname.replace(/\/+$/, window.location.search)} />
    // Your other routes are probably here
</Switch>

Important note: As the routes are probably defined close to the root in the dom tree I would not consider using any react-router hook (that far up in the tree) as It will cause this element to rerender every time (or at least comparing more elements in the virtual dom) the url changes. The provided solution will prevent some unneccessary rerenders.

With this solution you should always be able to push a new route like this

const {url} = useRouteMatch();
<Link to={`${url}/ADD_SOMETHING`}>A link!</Link>

// or instead
const {url} = useRouteMatch();
const history = useHistory();
history.push(`${url}/ADD_SOMETHING`)

@Archivec2
Copy link

Archivec2 commented Aug 13, 2021

<Redirect strict from={url + '/'} to={url} />

This would require the "exact" parameter to not redirect all subsequent path that follows

@chaance
Copy link
Collaborator

chaance commented Sep 5, 2021

Maybe I'm missing something here, so feel free to correct me if so, but this feels like it should be solvable with a relatively simple function.

function Component() {
  let { url } = useRouteMatch();
  let { id } = useParams();
  return (
    <Route path={joinPaths(url, id)} component={View} />
  );
}

function joinPaths(...segments) {
  return segments.join('/').replace(/\/+/g, '/');
}

We could do this internally (unlikely with <= v5 since it's a breaking change) or expose it as a helper, but is this not sufficient?

@MeiKatz
Copy link
Contributor

MeiKatz commented Sep 6, 2021

@chaance this is a good work-around. But it is a work-around and it should be done without it. Therefore it should be a basic behaviour of RR without the need to think about it.

@chaance
Copy link
Collaborator

chaance commented Sep 6, 2021

I'm not 100% sure that I agree. match.url is correct here. Cleaning up the value that you provide to either useRouteMatch or Route means we have to make assumptions about your app that might not be true for others.

If trailing slashes aren't important to you, you can always compose our useMatch or useRouteMatch:

import { useRouteMatch as useRouteMatch_ } from "react-router";

export function useRouteMatch(path) {
  return useRouteMatch_(trimTrailingSlashes(path));
}

@MeiKatz
Copy link
Contributor

MeiKatz commented Sep 6, 2021

Why not add a default behaviour and if this default behaviour doesn't match what a developer wants he/she can disable it? I think in the most cases triming slashes is exactly what you want.

@chaance
Copy link
Collaborator

chaance commented Sep 6, 2021

We could certainly discuss something like that for v6, but at least in v4/v5 changing the default behavior would be a breaking change. @mleister97 is correct that this is currently the intended behavior, so I'd suggest adopting one of the workarounds for now.

@chaance
Copy link
Collaborator

chaance commented Sep 6, 2021

Closing this, but see #8022 to pick up on potential changes for v6.

@chaance chaance closed this as completed Sep 6, 2021
@tomachinz
Copy link

There is some nice solutions here posted. I'm happy with these. For me it was more of a spiritual thing - a test of how I felt about React router. These are good enough for me.

@erdum
Copy link

erdum commented Sep 22, 2022

I solved like this:

  1. Put base tag at the top of your head tag inside your public/index.html <base href="%PUBLIC_URL%">
  2. Give relative paths to all of your assets <link rel="stylesheet" href="./assets/foo.min.css">
  3. Put assets folder inside your src folder
  4. Set homepage field in your package.json "homepage": "/",
  5. (If you are using react-router and you want to remove trailing slashes)
import * as React from "react";
import { useLocation, Navigate } from "react-router-dom";

const RemoveTrailingSlash = () => {
  const location = useLocation();
  
  return (
    <>
      {location.pathname.match(/.*\/$/) ? (
        <Navigate
	  to={{
	    pathname: location.pathname.replace(/\/+$/, ""),
	    search: location.search,
	  }}
	/>
      ) : null}
    </>
  );
};

export default RemoveTrailingSlash;

consume this component inside your BrowserRouter

<BrowserRouter>
  ...
  <RemoveTrailingSlash />
  ...
</BrowserRouter>

Note: this is not a guaranteed solution I am sharing this solution because it worked for me.

@Tim4497
Copy link

Tim4497 commented Dec 10, 2024

If you do not want to alter your routerConfig ( I was not able to add a <RemoveTrailingSlash /> Route to my createRoutesFromElements for example)

You can add this small function on the very beginning of your router component, this will remove every multi slashes

( function normalizeUrl() {
  const { protocol, host, pathname, search, hash } = window.location;

  const normalizedPath = "/" + pathname.split( "/" ).filter( Boolean ).join( "/" );

  if ( normalizedPath !== pathname ) {
    const newUrl = `${protocol}//${host}${normalizedPath}${search}${hash}`;
    window.location.replace( newUrl );
  }
} )();

This will do:

localhost/// -> localhost
localhost//test//-> localhost/test etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests