Hi there, I'm trying to upgrade my website to the ...
# kobweb
r
Hi there, I'm trying to upgrade my website to the latest version however, apparently, there were changes in how the routes are created that are crashing my website once launched... I have a dynamic route that starts with a parameter:
/{lang}/article/{article_id}
, which works fine until
0.20.0
, but I couldn't update further. I could reproduce the issue on both
0.20.1
and
0.20.2
. Looking into the dev tools, I see the following exception:
Copy code
IllegalStateException {
    "cause": undefined,
    "message": "User attempted to register a dynamic route that conflicts with one or more routes already set up. \"{lang}\" is being registered but [\"\", \"about\", \"articles\", \"project\", \"resume\"] route(s) already exist.",
    "name": "IllegalStateException",
    "stack": "IllegalStateException: User attempted to register a dynamic route that conflicts with one or more routes already set up. \"{lang}\" is being registered but [\"\", \"about\", \"articles\", \"project\", \"resume\"] route(s) already exist.\n    at RootNode.createChild_922k2c_k$ (webpack-internal:///./kotlin/kobweb-common-client-server-internal.mjs:258:118)\n    at RouteTree.register_q504rs_k$ (webpack-internal:///./kotlin/kobweb-common-client-server-internal.mjs:514:22)\n    at Router.register_vkebx7_k$ (webpack-internal:///./kotlin/kobweb-frontend-kobweb-core.mjs:1528:25)\n    at main$lambda (webpack-internal:///./kotlin/portfolio.mjs:421:30)\n    at initKobweb (webpack-internal:///./kotlin/kobweb-frontend-kobweb-core.mjs:564:3)\n    at main (webpack-internal:///./kotlin/portfolio.mjs:138:92)\n    at mainWrapper (webpack-internal:///./kotlin/portfolio.mjs:593:3)\n    at eval (webpack-internal:///./kotlin/portfolio.mjs:19302:1)\n    at ./kotlin/portfolio.mjs (<http://localhost:8080/portfolio.js:486:1>)\n    at __webpack_require__ (<http://localhost:8080/portfolio.js:520:41>)"
}
It looks like the code that is throwing the issue is this: https://github.com/varabyte/kobweb/blob/8f104e3bed69bf05fead154d57d4d66ec3adadc2/c[…]c/commonMain/kotlin/com/varabyte/kobweb/navigation/RouteTree.kt I would like to ask if there was a change in how we define the page routes that I might need to create a migration to support the
/{lang}/article/{article_id}
route, as it was supported before
0.20.1
. I saw the issue related to the
tryRoutingTo
, but I believe it isn't related since the exception also happens on
0.20.2
. Also, if that is a real issue, I can create a ticket in GitHub, I just want to double-check if I'm not missing anything.
d
(For next time, these are great details! But probably better contained inside a thread)
I'm surprised that what you were doing worked in 0.20.0!
r
Yeah, now that I see in the mobile, it doesn't look good all the details in the same message, my bad 😅
👍 1
d
Basically, the logic for navigating the routing tree asks, at each level, which child should handle it
For example, if you have "a/b", "a/c", and "a/d" registered, you've created a node "a" with three children "b", "c", and "d", and when you visit "a/c" in the browser, the tree first has node "a" accept the first part of the URL, then node "c" accepts the next part
but when you register a dynamic node, you're saying that should collect everything -- or at least, that was my mental model
I'm guessing you're saying you want this to work: "/a" "/b" "/{else}/x" where visiting "/a" would go to the first, "/b" would go to the second, and "/c/x" would go to the third?
What's the layout of your site that you're trying to support?
(Thinking about it, I can definitely support the case where I allow static pages and dynamic pages at the same level, as long as you'd be OK with the static pages taking precedence over the dynamic ones, if present)
I actually think I can support what you want
r
"/a"
"/b"
"/{else}/x"
where visiting "/a" would go to the first, "/b" would go to the second, and "/c/x" would go to the third?
Yes, that is the expected behaviour.
Thinking about it, I can definitely support the case where I allow static pages and dynamic pages at the same level, as long as you'd be OK with the static pages taking precedence over the dynamic ones, if present
Yeah, I think that, too, the static routes must get precedence over the dynamics. The only case I would think the dynamic should get precedence over the static is if the dynamic parameter was mapped to some value, and the value matched.
d
The more I think about it, the more I think there's no reason to not allow both, as long as static takes precedence
And it looks like NextJS works this way too, even though it doesn't seem officially documented anywhere I could find
This may have worked before just by chance 🙂
After today it will work on purpose and be verified by tests
r
Something like: •
/a
/b
/{param}/x
, where
param
is c. So, when you navigate to
/c/x
it redirects to the page, and
/d/x
redirects to 404. However I don't expected that to be supported
d
Here's a nastier question
r
This may have worked before just by chance 🙂
That was indeed working before, I have it running on my website, using
0.20.0
haha
d
Do you need to register these at the same time? •
{x}
{else}/x
{else}/y
r
In my use case, I don't think so. However, I don't see why not...
d
'else
, {a}/x`,
{b}/y
,
{c}/z
,
{k}/{j}
....
r
I can see this as some routes: •
{user_id}
{user_id}/profile
{user_id}/messages
d
I think dynamic node with the same name is fine
but otherwise it will be one dynamic node allowed per level
r
Or even: •
{message_id}
{user_id}/profile
{user_id}/messages
But I can understand having a single dynamic route at some level of the route tree. It would be very convoluted. How would the tree identify if:
/abc
should open:
/{message_id}
or
/{user_id}
?
d
Well I mean if it's {a}/x or {b}/y I could parse the whole URL and resolve ambiguity when I hit x or y
r
So, I would say that: •
/{user_id}
/{user_id}/profile
/{user_id}/messages
is OK, however, the following: •
/{message_id}
/{user_id}
/{user_id}/profile
/{user_id}/messages
isn't.
d
It's doable but that's like solving a sudoku board I think
Not worth it anyway!
But yeah multiple routes same name sounds good to me
r
So, do you want me opening an issue in the project or you will handle that?
d
Ah no worries, I'll handle it!
So this has been trickier to get right than I thought, but while I have something working, here's a case that's causing me to pause
Site v1 You register • /a • /b • /{else} Everyone is happy. People visit "/x" a lot. Site v2 You register • /a • /b • /x/y/z • /{else} User visits "/x". What should happen??
Pretty sure intuitively, we'd expect the {else} case to handle it, but actually, now the "/x/y/z" path intercepts it. Since there is no "/y/z" part, Kobweb thinks the route is incomplete and 404s.
For current Kobweb, static routes and dynamic routes are mutually exclusive. So it's not possible to register "/a", "/b", and "{else}" in the first place. But let's say you register just "{else}" for v1. That's fine... And then try to add "/x/y/z" for v2. That will also instantly fail, because "/x" and "{else}" cannot exist at the same time. So the current approach may be limited but prevents you from getting into a realllly confusing 404 situation.
I need to sleep on this.
I think I need to rethink how to resolve routes and this new approach might work out while still supporting the old way. Basically if a route has three parts, only registered routes with three slashes need to be checked.
One final weird case, just going to write it down before crashing for the night: You register: • /a/{b} • /{a}/b User visits /a/b. Which page should handle it?
in last case imho first should win as explicit/static paths should take precedence
r
> Site v2 > You register > • /a > • /b > • /x/y/z > • /{else} > User visits "/x". What should happen?? > Pretty sure intuitively, we'd expect the {else} case to handle it, but actually, now the "/x/y/z" path intercepts it. Since there is no "/y/z" part, Kobweb thinks the route is incomplete and 404s. I might be missing something, but when I think about page routes, I expect the user to access only the routes I have registered. It would be weird for me the user accessing
/x/y/z
just by using
/x
by default if I haven't mapped
/x
to be redirected to
/x/y/z
. So, to me, the correct would be
/x
to access
/{else}
instead of redirecting to
/x/y/z
automatically. > One final weird case, just going to write it down before crashing for the night: > > You register: > • /a/{b} > • /{a}/b > > User visits /a/b. Which page should handle it? I guess in that scenario, the user should access the first page. As the first part of the url matches with a static first path of a route, it should navigate to it.
d
@kqr @Rafael Tonholo So we all fell into the trap, which is, looking at it technically, it seems clear enough to choose one to have precedence. But the deeper issue is you can have a site that has been doing fine for years having registered
/{a}/b
, and then someone else goes ahead and creates a page
/a/{b}
without realizing the unexpected havoc they've just created. In practice, maybe this will be a very rare issue. However, I'm always concerned about cases in codebases where you pull a lever OVER HERE and get a bug that happens OVER THERE as a result due to unexpected coupling. In short, allowing static AND dynamic route nodes to both live side by side increases flexibility but opens up this really nuanced potential for introducing breakage. Not allowing them and failing fast prevents some site designs but can always be reasoned about cleanly.
What I'm really struggling to think through is how important it is to allow those minority of sites that want to allow dynamic and static routes at the same level. @Rafael Tonholo would it be so bad to structure your site like this: •
/about
/projects
/resume
/lang/{lang}
r
I wouldn't use
/lang
as a path cause it doesn't make sense, but I can move the parameter one leve without much trouble. I guess the following declaration works:
/path/{lang}/{id}
, right?
d
Yes, for now, if you move the dynamic route to a folder of its own, it will work.
r
What do you mean with "for now"? Haha Can that stop working in the future?
d
Oh, opposite. Dynamic segment in isolation will always work. I am contemplating adding dynamic and static route siblings as discussed in this thread for the future.
Besides Spring Boot, there is also precedence in Astro, which a major Kobweb contributor just pointed out to me: https://docs.astro.build/en/guides/routing/#route-priority-order
I may be cautious but I don't think I can ignore precedence of this scale. I'll look into adding support for finishing up sibling dynamic routes. I think there's a good chance my code is already 90%+ there. Will ping here when done.
👍🏼 1
Alright, I have updated Kobweb at
0.20.3-SNAPSHOT
which now supports a more flexible routing algorithm. •
/x/y
and
/{else}
now work as expected:
/x/y
->
/x/y
,
/x
->
/{else}
/{a}/{b}
and
/{a}/b
and
/a/{b}
can all be registered at the same time.
/a/b
will match
/a/{b}
(whichever route has the earliest static route match in it) •
/{user_id}
,
/{user_id}/profile
, and
/{user_id}/messages
can all be registered. Best of all, this should be completely backwards compatible with all existing Kobweb sites. (Always easier to go from stricter to less strict than the other way around...) @Rafael Tonholo I would love it if you could give this a shot and confirm if it's working on your end or not, if it is not too late (and you haven't already migrated away from your old site organization).
r
I guess it worked, but I'm getting a different error now which I'm trying to understand what is happening 🫠
Copy code
"Key [object Object] is missing in the map."

NoSuchElementException: Key [object Object] is missing in the map.
    at getOrImplicitDefault (webpack-internal:///./kotlin/kotlin-kotlin-stdlib.mjs:19176:13)
    at getValue (webpack-internal:///./kotlin/kotlin-kotlin-stdlib.mjs:19276:10)
    at _toModifier (webpack-internal:///./kotlin/kobweb-frontend-silk-foundation.mjs:1936:93)
    at toModifier (webpack-internal:///./kotlin/kobweb-frontend-silk-foundation.mjs:1929:14)
    at Scaffold (webpack-internal:///./kotlin/portfolio-core.mjs:4191:108)
    at HomeContent (webpack-internal:///./kotlin/portfolio.mjs:14261:80)
    at HomePage (webpack-internal:///./kotlin/portfolio.mjs:18594:5)
    at ComposableSingletons$MainKt$lambda_1$lambda_sdpc0d (webpack-internal:///./kotlin/portfolio.mjs:156:5)
    at ComposableLambdaImpl.invoke_z8di7s_k$ (webpack-internal:///./kotlin/compose-multiplatform-core-compose-runtime-runtime.mjs:38737:162)
    at eval (webpack-internal:///./kotlin/portfolio.mjs:150:23)
But it is unrelated with the route issue, so I would say the registration worked fine
d
Nuts! That's a modifier exception, which I think is related to a css style not getting registered correctly.
DM me if you can share your project?
r
Sure