5

My URLs have double colon on them.

I push a path to Nuxt router which has : as a part of it.

  export default {
  router: {
    extendRoutes (routes, resolve) {
      routes.push({
        name: 'custom',
        path: 'towns' + '(:[0-9].*)?/',
        component: resolve(__dirname, 'pages/404.vue')
      })
    }
  }
}

When I point to http://localhost:3000/towns:3 , for example, the : is translated as %3Aon the URL leading to this error message:

Expected "1" to match ":[0-9].*", but received "%3A2"

How to revert this to : ?

I tried encodeURI(), decodeURI(), encodeURIComponent() and decodeURIComponent() in vain.

A demo for the ones who wants to try: nuxt-extend-routes

Any suggestions are welcome

Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130

2 Answers2

6

Vuex is using vue-router and vue-router is using path-to-regexp to parse router path configuration

It seems to me, that you are trying to use Unnamed Parameters which doesn't make sense because vue-router/vuex need the name of the parameter to pass it down to Vue component behind the route

Why don't just use named parameters ?

{
      path: '/towns:id(:\\d+)',
      name: 'Page 3',
      component: Page3
    }

Sure, result will be that $route.params.id value will be prefixed with : and all router-link params must be :XX instead of 'XX' but that's something you can deal with. vue-router (path-to-regexp) is using : to "mark" named path parameters ...there's no way around it

You can take a look at this sandbox. Its not Nuxt but I'm pretty sure it will work in Nuxt same way....

Update

Well it really doesn't work in Nuxt. It seems Nuxt is for some reason applying encodeURIComponent() on matched path segments and throws an error. It works when server-side rendering tho (it throws some error on client still)...

Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • 1
    That's fine. Change your regex as you wish, just make sure to prefix it with `:myparamname` so `vue-router` make the regex result available to you in `$route.params.myparamname` – Michal Levý Dec 05 '19 at 18:48
  • Unfortunately for Nuxt, this did not work (getting the error message I shared above). But thank you very much for the feedback – Billal Begueradj Dec 11 '19 at 06:31
  • Yes, that is how it behaves (before posting, I tried `path: '*' + 'towns' + decodeURIComponent('(:[0-9].*)?/'), ` but got the same error message). – Billal Begueradj Dec 11 '19 at 14:44
  • 1
    Changing `path` won't help. There is no problem with `:` in the route definition. Problem is with the code that is matching actual url (from browser nav bar or from `nuxt-link`) against that path during navigation. It's a bug in Nuxt IMHO... – Michal Levý Dec 11 '19 at 23:21
3

Firstly, I concur with Michal Levý's answer that there's a library bug here. The line throwing the error is here in the Nuxt source:

https://github.com/nuxt/nuxt.js/blob/112d836e6ebbf1bd0fbde3d7c006d4d88577aadf/packages/vue-app/template/utils.js#L523

You'll notice that a few lines up the segment is encoded, leading to : switching to %3A.

However, this line appears to have originated from path-to-regexp:

https://github.com/pillarjs/path-to-regexp/blob/v1.7.0/index.js#L212

It isn't trivial to fix this bug because the encoding is not simply 'wrong'. There's a lot of stuff going on here and by the time that line is reached the parameter values have been URL decoded from their original values. In the case of our unencoded : that causes problems but in other cases, such as matching %3A, the encoding would be required.

The handling of encoding within path-to-regexp is a delicate topic and we aren't helped by the old version being used. This also makes it more difficult to come up with a suitable workaround in your application.

So, let's see what we can do...

To start with, let's consider the path:

path: 'towns' + '(:[0-9].*)?/',

Bit odd to concatenate the strings like that, so I'm going to combine them:

path: 'towns(:[0-9].*)?/',

The / on the end isn't hurting but it seems to be unnecessary noise for the purposes of this question so I'm going to drop it.

On the flip side, not having a / at the start can cause major problems so I'm going to add one in.

The .* is suspicious too. Do you really mean match anything? e.g. The current route will match towns:3abcd. Is that really what you want? My suspicion is that you want to match just digits. e.g. towns:3214. For that I've used [0-9]+.

That leaves us with this:

path: '/towns(:[0-9]+)?',

Now, the : problem.

In general, route paths are used in both directions: to match/parse the URL and to build the URL. Your use of an unnamed parameter makes me wonder whether you only intend to use this route for matching purposes.

One option might be this:

path: '/towns:([0-9]+)',

By moving the : outside the parameter it dodges the encoding problem.

There are two problems with the code above:

  1. The colon/number suffix is no longer optional on the URL. i.e. It won't match the path /towns as the original route did. This can be solved by registering /towns as a separate route. I'm not aware of any other way to solve this problem with the available version of path-to-regexp.
  2. You won't be able to use it to build URLs, e.g. with nuxt-link.

If you need to be able to use it to build URLs too then you could use a named parameter instead:

path: '/towns::town([0-9]+)',

The :: part here is potentially confusing. The first : is treated literally whereas the second : is used as a prefix for the town parameter. You might then use that with nuxt-link like this:

<NuxtLink :to="{ name: 'custom', params: { town: 4 } }">
 ...
</NuxtLink>
skirtle
  • 27,868
  • 4
  • 42
  • 57
  • Interesting solution. Works much better with Vue Router than mine. But unfortunately last example still doesn't work in Nuxt. Link works (parameter is passed to target component) but `href` is rendered as `/towns::` so parameter is lost after refresh.... – Michal Levý Dec 17 '19 at 10:21
  • @MichalLevý Interesting. I don't see that problem myself. I cloned the GitHub repo from the original question, so I'm using Nuxt 2.10.2 and Vue Router 3.0.7. I did originally experiment with `path: '/towns\\::town([0-9]+)',` as a way to explicitly escape the first colon but it didn't seem to be necessary so I didn't include it in my answer. As I can't reproduce the problem you're seeing it's difficult to know where to start trying to debug it... – skirtle Dec 17 '19 at 11:01
  • Nevermind. I was testing it in Codesandbox and it apparently has it's quirks. After restarting the container, it works like a charm. Good job! This should be accepted answer ... – Michal Levý Dec 17 '19 at 11:07