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

相对路径的redirect问题 #565

Open
Repraance opened this issue Oct 12, 2023 · 6 comments
Open

相对路径的redirect问题 #565

Repraance opened this issue Oct 12, 2023 · 6 comments

Comments

@Repraance
Copy link
Contributor

Repraance commented Oct 12, 2023

问题

在修复 #563 时,我发现e2e tests 里包含SSR时,相对路径的redirect。

一般来说,我们的功能应该是同构的,因为我也增加了CSR的对应e2e test,随后发现CSR时,相对路径的redirect无法正常工作。

例子

具体来说。

SSR 相对路径的redirect 的test case,是正常的。假设我们有以下页面

/
/c
/x,其loader会return redirect('c') 即相对路径redirect
/x/c

当SSR时,直接访问/x,会302到/x/c,相当于,此次redirect是基于/x的。这是我们的当前预期。

然而,当前端路由跳转时,如,在/页面,点击/x的链接,结果却跳到了/c 页面,也就是说,此处redirect是基于跳转前的路径/

同时,当在SPA模式下,直接访问/x,页面url会跳转至/x/c,但是页面显示404。这里应该是另一个bug

也就是说,对于同一个页面,不同情况下的访问表现出截然不同的行为。

分析

我认为,相对路径的redirect,是一个bad practice。

通常来说,前端路由导航由页面中的链接按钮触发,当点击链接导航时,可以是相对路径,并且是由跳转之前的url为base url。
也就是说,对于单纯的路由导航,其跳转地址可以是相对路径,并且规则很明确。

但是对于redirect,情况则不同了。
前端路由导航中下,redirect 包含三个对象:

  • navigate from
  • navigate to
  • redirect to

如果redirect地址是一个相对路径,那么,这个是相对于navigate from,还是navigate to呢?我认为规则是不清晰的。

shuvi允许在loader中进行redirect。对于开发者来说,如果使用相对路径进行redirect,自然想到的是以loader所在的路由作为base。
但事实上,loader所在的路由是navigate to。

在上面的例子中(shuvi 的 e2e test中),SSR时访问/x,redirect至 /x/c,正是以 navigate to 作为base。

而前端路由跳转时,却redirect到 /c,此时则变成了以 navigate from 作为base。vue-router 也是如此。

其他框架的做法

使用相对路径进行redirect时,vue 的 base 是navigate from
remix 和react-router 是基于 navigate to
next.js 待确认

解决方案

相对来说,我倾向于以navigate to 作为base,比较符合直觉。

因为,无论是使用route config还是使用hook,还是loader来配置redirect。
redirect总是配置在navigate to上面的。
而navigate to是很明确的,所以这时如果有redirect,自然是应该基于当前的navigate to来计算实际路径。

另一方面,用户在跳转时,navigate from可以是任何地址。如果redirect的相对路径base是navigate from,那么最终地址也是五花八门的,是完全不可预料的,这样也就没有意义了。

@liximomo
Copy link
Contributor

liximomo commented Oct 12, 2023

解释的非常清楚,这个问题核心是client和server行为不一致,这种不一致确实存在,对server来讲,他其实没有navigate from这个字段,所以只能使用 navigate to,但客户端是否应该也用navigate to,这个需要参考下其他成熟方案。

remix没有纯客户端路由,只考虑server端的话,我们其实和remix是一致的。类似的逻辑,next.js可能也没有可对比性,我们需要验证一下react-router在纯客户端场景下的行为作参考。

@Repraance
Copy link
Contributor Author

OK
我再验证下react-router的行为

@Repraance
Copy link
Contributor Author

react-router (v6)是基于navigate to,与remix 一致(remix就是基于react-router的)

@Repraance
Copy link
Contributor Author

Repraance commented Oct 17, 2023

vue-router 基于 navigate from,但是 react-router 基于 navigate to 的原因探究
redirect是导航的一部分。框架的导航机制包括路由,路由钩子,Link 组件等等,一般来说,框架的导航机制是整体性的。
router.push, link to等行为的原则趋向于一致。

注意到,vue的导航原则更偏向于原生html。

原生html的导航使用a标签和history.pushState。
他们的共同点是,当使用相对路径时,会以url中最后一个/ 作为base。
即,如果当前页面路径为 /xx,此时使用a 标签,或者pushState导航至相对路径 yy 时,会跳转至 /yy,而不是/xx/yy

Vue的RouterLink, router.push, redirect 均遵循该原则。

与之相对的,上面一样的例子,shuvi和react-router的相对路由导航均会导航至/xx/yy,也就是以当前url的路由为base。
这应该是react-router最初的发明。

事实上,在vue-router v3版本中,也提供了类似的导航方式,vue-router 称之为 append ,不过最新版已经移除该feature。

不仅如此,我们发现vue-router的导航功能相对来说比较弱,比如,相对路径不支持../xxx这种向上一级的导航。

而 react-router 对于相对路径的导航则有一套完整的RFC,支持各种情况。

react-router 的 Link 支持两种模式的相对路径

  • #以组件所在route层级为base(默认)
  • 以当前url为base

而 redirect 也是采用默认的 以组件所在route层级为base,也就是基于 navigate to。
由此可见,react-router 的 redirect基于navigate to是因为他本身有一套不同于原生html的相对路径的跳转规则。
进而,基于这个规则之上,redirect 使用以组件所在route层级为base。

对web应用的开发者来说,react-router 提供的方案是比较完善,且符合直觉的。

而vue-router则相对简陋一些,相对路径的导航行为与原生html保持一致,更像是原生html原教旨主义者。
而redirect这个功能,原生html中并不存在这个概念,所以他的实际行为显得较为不符合常理。

结论

我认为相对路径redirect以navigate to 作为base,是正确。

redirect以navigate from 作为base,是有问题的。

而且,这个问题其实很简单。

因为,无论是使用route config还是使用hook,还是loader来配置redirect。
redirect总是配置在navigate to上面的。
而navigate to是很明确的,所以这时如果有redirect,自然是应该基于当前的navigate to来计算实际路径。

另一方面,用户在跳转时,navigate from可以是任何地址。如果redirect的相对路径base是navigate from,那么最终地址也是五花八门的,是完全不可预料的,这样也就没有意义了。

再举前面的例子:

有以下路由

/
/c
/x,其loader会return redirect('c') 即相对路径redirect
/x/c,
/hello
/hello/world

如果redirect基于navigate from,那么就会变成这样:

当前端路由跳转时,如,在/页面,点击/x的链接,结果却跳到了/c 页面,也就是说,此处redirect是基于跳转前的路径/

问题来了,如果我们在任意页面,点击/x的链接,其结果都是基于当前的url。
比如,在/hello页面点击/x的链接,结果跳转至/hello/c
/hello/world页面点击/x的链接,结果跳转至/hello/world/c

这样的redirect就毫无意义了。

@Repraance
Copy link
Contributor Author

Repraance commented Oct 17, 2023

补充,react-router v6 才支持了相对比较完善的relative path。
之前v5也是不支持的,其行为与vue-router 一致。
参考
https://reactrouter.com/en/main/upgrading/v5#note-on-link-to-values

值得吐槽的一点是,在解释为何这么做时,react-router 表示很多人也搞不清楚原生html的相对路径是怎么跳转的。

The decision to ignore trailing slashes while matching and creating relative paths was not taken lightly by our team. We consulted with a number of our friends and clients (who are also our friends!) about it. We found that most of us don't even understand how plain HTML relative links are handled with the trailing slash. Most people guessed it worked like cd on the command line (it does not). Also, HTML relative links don't have the concept of nested routes, they only worked on the URL, so we had to blaze our own trail here a bit. @reach/router set this precendent and it has worked out well for a couple of years.

@Repraance
Copy link
Contributor Author

最终结论:
即使redirect基于navigate to,在loader的场景中,如果开发者把逻辑抽出去,这样依旧无法得知实际的跳转地址。
因此我们认为这仍然不是一个完美的解决方案。

故此,我们决定,保持现有逻辑,并对相对路径redirect增加waring,让开发者不要使用这种方式。

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

2 participants