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

Optional route with matcher followed by rest parameters #7548

Closed
pdrbrnd opened this issue Nov 8, 2022 · 6 comments · Fixed by #7753
Closed

Optional route with matcher followed by rest parameters #7548

pdrbrnd opened this issue Nov 8, 2022 · 6 comments · Fixed by #7753
Labels
Milestone

Comments

@pdrbrnd
Copy link

pdrbrnd commented Nov 8, 2022

Describe the bug

Given a [[lang=lang]]/[...path] route, kit will fail to catch non-matched URLs with the rest parameters route.

The same doesn't happen if the optional route is followed by a static one: [[lang=lang]]/t/[...path].

// params/lang.js
export const match = (param) => {
  return ['fr', 'de'].includes(param);
};

I would expect:

/home -> { lang: undefined, path: 'home' } -> gives 404
/de/home -> { lang: 'de', path: 'home' } -> correct

In the [[lang=lang]]/t/[...path] scenario, it works as expected:

/t/home -> { lang: undefined, path: 'home' } -> correct
/de/t/home -> { lang: 'de', path: 'home' } -> correct

A common use case for this would be an i18n site with dynamic pages being served by a CMS.

Reproduction

Here's a stackblitz reproducing the issue:
https://stackblitz.com/edit/sveltejs-kit-template-default-4srbff?file=src/routes/+layout.svelte

And here's another one showing the correct expected behaviour by placing a static route after the optional one:
https://stackblitz.com/edit/sveltejs-kit-template-default-a3vmdd?file=src/routes/+layout.svelte

Logs

No response

System Info

System:
    OS: macOS 13.0
    CPU: (8) arm64 Apple M2
    Memory: 50.08 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.10.0 - ~/.nvm/versions/node/v18.10.0/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 8.19.2 - ~/.nvm/versions/node/v18.10.0/bin/npm
    Watchman: 2022.10.03.00 - /opt/homebrew/bin/watchman
  Browsers:
    Brave Browser: 107.1.45.116
    Chrome: 107.0.5304.87
    Firefox: 106.0.5
    Safari: 16.1
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.87
    @sveltejs/kit: 1.0.0-next.538  => 1.0.0-next.538
    svelte: ^3.44.0 => 3.52.0
    vite: ^3.1.0 => 3.2.3

Severity

serious, but I can work around it

Additional Information

No response

@dummdidumm dummdidumm added bug Something isn't working router labels Nov 8, 2022
@dummdidumm dummdidumm added this to the 1.0 milestone Nov 8, 2022
@dummdidumm dummdidumm removed the bug Something isn't working label Nov 8, 2022
@dummdidumm
Copy link
Member

dummdidumm commented Nov 8, 2022

Added the bug label too soon - I think this works as expected. The matcher works greedily from left to right. The matcher says "home" isn't a valid language, so the rest of the route is not taken into account, because the rest is a, well, rest route. To me that's the intuitive behavior. In your case you probably want to only have a [...rest] route.
Keeping this open to gather opinions from other maintainers.

@pdrbrnd
Copy link
Author

pdrbrnd commented Nov 8, 2022

That was my opinion as well until I noticed that placing a static path after the optional one would make it work differently – which IMO makes it inconsistent.

One way of looking at it is: if the matcher says "home" isn't a valid language it should not take into account anything inside.
But arguably, given the lang path is marked as optional, it could still look inside when the matcher returns false.

This is pretty much the scenario from the docs, but using a [...path] route instead of home

What seems buggy is that it does look inside if it's a static route, but it doesn't if it's a rest route, as shown in the two repros above.

@dummdidumm
Copy link
Member

Thinking about this again, I lean more towards "works as designed" and "limitation of the Regex approach". The route is matched using a Regex, and that regex works greedily. In this situation on optional route segment is followed by other optional segments, so the Regex will always assign the first segment to the optional parameter, and then continue to look at the matcher. Doing it this way ensure route matching is very fast even for huge apps, and the greedy nature of it makes it more straightforward to reason about in your head. This specific case should probably mentioned in the docs though.

dummdidumm added a commit that referenced this issue Nov 8, 2022
Document a gotcha around optional parameter with matcher followed by rest parameter
Closes #7548
@pdrbrnd
Copy link
Author

pdrbrnd commented Nov 8, 2022

@dummdidumm thank you for the clarification.

I never thought of the [...path] route as optional (shouldn't it be [[...path]])?
I understand your rationale tho and it seems that these are, in fact, optional routes in SvelteKit in which case I guess it makes sense for the regex to assign the first segment to the first optional.

On the other hand, this seems a very basic use case for any multilingual website that has all pages generated by a CMS.
Do you have any suggested workarounds? Maybe having a single [...path] page + a handle hook extracting the lang from the URL and passing it to event.locals?

@dummdidumm
Copy link
Member

[...rest] includes zero to n segments, so in a sense it's also optional, yes. I don't know for what you use the lang parameter, but you could still infer it from the rest segment. For de/home the params object would be { rest: "de/home" } so you could add your own little check to that and extract the language from that. You could do that inside your load function, no handle hook needed.

@pdrbrnd
Copy link
Author

pdrbrnd commented Nov 8, 2022

That's right. I see that we have access to params on every load function, even in a layout outside.

Even tho I understand the limitation of the greedy Regex approach, it still feels a bit inconsistent when taking into account static routes.
I feel that it's easy for a case like the current example in the optional routing docs to evolve into a catch-all route and, suddenly, the behaviour changes because of implementation details.
Anyway, the addition to the docs is most welcome.

Feel free to close the issue if this is indeed working as designed.

Thank you for all the support :)

Rich-Harris added a commit that referenced this issue Nov 22, 2022
Rich-Harris added a commit that referenced this issue Nov 22, 2022
* add failing test for #7548

* refactor

* fix

* always add trailing slash, that logic is way out of date

* fix tests

* DRY out

* track more param info

* check for missing matcher at manifest creation time

* working, i think

* shuffle around

* ok try this

* update tests

* fix tests

* lint

* fix

* changeset

* unit tests

* make the code a bit more comprehensible

* Update .changeset/forty-icons-switch.md

* fix another case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
2 participants