withastro / roadmap

Ideas, suggestions, and formal RFC proposals for the Astro project.
292 stars 29 forks source link

Client-side Routing #532

Closed natemoo-re closed 1 year ago

natemoo-re commented 1 year ago

Body

Summary

Astro is a server-first framework. As such, it has always relied on the Multi-Page Application (MPA) architecture, meaning that navigations result in a full page refresh and server round-trip.

This proposal offers an opt-in Client-side Routing (CSR) experience, typically associated with Single-Page Application (SPA) architectures.

Note: For a great overview of relevant terms (MPA/SPA/PEMPA/PESPA), see The Web’s Next Transition by Kent C. Dodds.

Background & Motivation

As users build increasingly complex experiences with Astro, UI Persistence has surfaced as a clear missing piece in the full Astro story. Client-side routing is currently the only way to achieve UI persistence across navigations.

MPAs are the backbone of the web and remain a natural default for Astro projects. SPAs come with their own set of incredibly complex considerations and drawbacks—importantly, we do not believe that client-side routing is a "solved problem" despite becoming the default approach in the industry.

However, the team has discussed a mythical "SPA mode" since the earliest days of the project. Now that we are extremely confident in Astro's mental model and internal priorities, there is no better time to begin layering in new primitives to round out Astro's use cases.

Goals

🔵 points are not finalized, should be discussed and resolved during prototyping

Non-Goals

frankstallone commented 1 year ago

Really happy New client-side routing behavior should be entirely opt-in is a first on the list, and a finalized goal. Without this, Astro looses it's original appeal.

natemoo-re commented 1 year ago

Really happy New client-side routing behavior should be entirely opt-in is a first on the list, and a finalized goal. Without this, Asto looses it's original appeal.

Yep, great callout @frankstallone! This will be an entirely optional feature for those that want it—if it's not relevant for your projects, feel free to ignore. This won't change anything about how Astro works today, nor are we abandoning our goal of being a performant, server-first framework that ships zero JS by default.

jaredcwhite commented 1 year ago

Has an approach similar to Turbo been discussed? Technically, I believe one could apply Turbo to an existing Astro site and essentially get SPA-like behavior out of the box. That doesn't solve things at a FE framework level per se if that's what somebody's looking for, but I think it's a fantastic next step up from a pure MPA.

SrJSDev commented 1 year ago

This is an exciting upcoming feature!

As @jaredcwhite mentioned, my thoughts immediately turn to Turbo-like lib for Client-Side Routing (CSR) of a Multi-Page Application (MPA). However, I imagine there would be some hydration issues to consider if app state needs to persist across page navigation boundaries.

natemoo-re commented 1 year ago

@jaredcwhite Has an approach similar to Turbo been discussed?

Yes definitely something we've explored. This would be a fairly straightforward drop-in. I actually built micromorph a year ago when we first approached this—it works well and is a totally viable option!

The reason we didn't ship that is because we think there might be a better, as of yet undefined, solution. My job over the next few weeks is to explore all the alternatives so can compare them fairly before deciding.

rtr1 commented 1 year ago

An HTMX Astro integration would handle most of the stated goals.

The only challenge I’ve had using HTMX for CSR is handling of how <head> content gets swapped in and evaluated on “page” transitions, but they have a head-support extension that handles that now.

The interesting part to me would be if we can leverage Astro’s dynamic JS loading into this process. Using vanilla HTMX to swap a DOM element’s innerHTML with head-support will load relevant JS and violate “zero JS by default”. The question then is, can Astro serve a single component over the wire (as opposed to the full page)? If it can, and we can leverage Astro’s dynamic loading of JS, that would allow vanilla HTMX to hit any Astro endpoint to grab the innerHTML to swap in.

tony-sull commented 1 year ago

unpoly is another library similar to HTMX worth checking out!

@rtr1's point is also a really interesting one here - is it worth buildlng in our own client-side solution or are we just missing some primitives/APIs for other libraries to use?

We might be able to get pretty far if there was a way to have Astro render HTML partials in some form. I didn't hit any issues using HTMX in an ecommerce shop I was mocking up with Astro SSR, but I'm not 100% sure if small things like always including <!doctype> in partials or always wrapping in an <html> tag might cause issues with some tools 🤔

Brian-McBride commented 1 year ago

The reason I want Client-side-routing is mainly for an app-like experience. I don't need to be remounting my toolbar on each page. If I have a profile picture there, while it is likely cached, I'd be fetching it each time, right? And then I trust the browser not to do any annoying flashes of empty content or format updates.

I'd really like to see an optional router that allows me to persist island based on routes. Ideally, evaluating the component tree and deciding on what islands can be persistent and what islands need to be replaced. Of course, islands could be flagged never to persist.

That's an interesting pattern. I love what Astro is doing to the final code. Small, lightweight, smarter ways to hydrate. Mix and match frameworks - nice. But I still can't really replace my web apps with Astro. Websites, a lot of marketing materials, blogs, etc... for sure.

frankstallone commented 1 year ago

The reason I want Client-side-routing is mainly for an app-like experience. I don't need to be remounting my toolbar on each page. If I have a profile picture there, while it is likely cached, I'd be fetching it each time, right? And then I trust the browser not to do any annoying flashes of empty content or format updates.

I'd really like to see an optional router that allows me to persist island based on routes. Ideally, evaluating the component tree and deciding on what islands can be persistent and what islands need to be replaced. Of course, islands could be flagged never to persist.

That's an interesting pattern. I love what Astro is doing to the final code. Small, lightweight, smarter ways to hydrate. Mix and match frameworks - nice. But I still can't really replace my web apps with Astro. Websites, a lot of marketing materials, blogs, etc... for sure.

The reason I have stayed away from client-side apps is because of the lack of accessibilty. For me, that trumps any seamless transition between pages that an all-JS-solution provides. I'd be curious if this could be implemented in an accessible way. IIRC Ryan Florence had a heck of a time over the years trying to get good accessibility with React Router. Not sure he ever succeded.

unbiased-dev commented 1 year ago

The reason I have stayed away from client-side apps is because of the lack of accessibilty. For me, that trumps any seamless transition between pages that an all-JS-solution provides.

I'm curious, how are SPAs less accessible then MPAs in your experience?

frankstallone commented 1 year ago

The reason I have stayed away from client-side apps is because of the lack of accessibilty. For me, that trumps any seamless transition between pages that an all-JS-solution provides.

I'm curious, how are SPAs less accessible then MPAs in your experience?

Not sure this is the right thread for this particular topic but focus management is a good example.

pilcrowOnPaper commented 1 year ago

I'm personally interested in client side routing within MPA pages, rather than just a choice between MPA or SPA. If I just want a SPA, Astro isn't my first choice since you have to create an entirely new component and import if you want client side interactivity with JS frameworks.

jaredcwhite commented 1 year ago

Saw the shoutout to this thread from a very recent Rich Harris talk, and wow do I have thoughts. 😂 But for the sake of professional decorum, let me just say this:

Even if Astro ends up switching on an SPA-like mode, I really really really really hope the actual code coming over the wire continues to be HTML for the most part. I personally find the cascade of minified JS gibberish clogging up the network tab that you see with (ahem) other "SSR" JS frameworks to be unacceptable. If I'm on Page A, and I click Page B, I want the HTML of Page B to be what I see in my network tab, regardless if that happened due to standard MPA mechanics or due to a client-side router. (Again, libraries like Turbo, HTMX, unpoly, etc. are all built around these concepts. HTML pages + fragments, with the occasional JSON or JS chunk fetches only if/when strictly needed. This is the way…)

Brian-McBride commented 1 year ago

@jaredcwhite The thought of "less" down the wire makes perfect sense. Javascript isn't going away. We need something to provide client-side interactivity logic. Obviously, if you can leverage the built-in logic when possible (such as the CSS engine) that can result in a better client experience. I'd much rather have really good bundlers throwing minified JS than have developers try and self-manage everything like in the "old days".

Static generation, where possible, followed by hydration as needed is my ideal goal. "Page by Page" is fine for marketing sites, documentation, and a number of other uses. I'd like to see "pages" where I can maintain an island mounted between the two routes. All with the same ideals as Astro. HTML-first, interactive only as needed. Downloading only the delta changes between the pages.

jaredcwhite commented 1 year ago

have developers try and self-manage everything like in the "old days"

I'm not sure what you mean.

Downloading only the delta changes between the pages

As a potential technique in response to a particular user activity (form submission, say), perhaps, but not as an architectural principle across the entire application. A whole new class of complexity and debugging hassle arises when we just wave our hands and say "let JS bundlers and component runtimes figure it all out". Not the kind of magic I'm looking for!

NaCoLiu commented 1 year ago

Looking forward to SPA while watching. This is very useful for my project. In a single page design mode project, I need to use SPA, or I want to implement a completely detached music player that exists in state management and renders it on a single page. After switching pages, I want to preserve the state. Or rather, I want the header nav to always exist on the page, which is obviously not something that Astro Basic projects can do yet. My Project : Blog

BingeCode commented 1 year ago

I'm relatively new to MPAs/Astro and find the idea of client-side routing intriguing mostly because of enabling things like the View Transitions API which is currently not supporting cross-document transitions (but is being planned for a future update to the API according to MDN).

Someone used the experimental Navigation API to emulate SPA behavior within astro and implement View Transitions. It works pretty decently.

Curious as to what the best approach would be to implement smooth View Transitions in the MPA architecture of astro as part of this feature.

xriter commented 1 year ago

Goal proposal: 🔵 Don't rely on a manifest exposing all existing routes.

A goal that I myself would like to see added, is that when client side routing is enabled, this won't 'expose' all the possible routes that exist in your project, like all the other major frameworks do. They create some sort of manifest for the client containing every existing route in the project, even the ones you wish weren't publicly exposed. If this can be achieved, that would be a major plus for Astro in my opinion.

To further illustrate my point: Routes 'can' include some business sensitive data. For example the names of unreleased products, api end points, etc. Of course one can then start an argument that these routes shouldn't exist at all then, but still, not having them exposed at all is a first defence. Let's not provide every user with a fully detailed floorpan of our website, so that they can start poking around to find vulnerabilities.

jigz commented 1 year ago

Goal proposal: 🔵 Don't rely on a manifest exposing all existing routes.

A goal that I myself would like to see added, is that when client side routing is enabled, this won't 'expose' all the possible routes that exist in your project, like all the other major frameworks do. They create some sort of manifest for the client containing every existing route in the project, even the ones you wish weren't publicly exposed. If this can be achieved, that would be a major plus for Astro in my opinion.

To further illustrate my point: Routes 'can' include some business sensitive data. For example the names of unreleased products, api end points, etc. Of course one can then start an argument that these routes shouldn't exist at all then, but still, not having them exposed at all is a first defence. Let's not provide every user with a fully detailed floorpan of our website, so that they can start poking around to find vulnerabilities.

it's a false sense of security, some web apps rely too much on hiding routes that they neglect to secure their api, if it's sensitive then lock it behind a bulletproof api rather than hide it and hope nobody sees it, I think major frameworks expose all routes to stop this malpractice.

Brian-McBride commented 1 year ago

I hope that Astro doesn't just wait for the View Transition API to be solid before offering a path to a solution. While valuable, that API, in my mind, is a visual tool but does not solve the app-like experience in a more complicated site.

I appreciate the goal of keeping the payload as light as possible for the client. HTLM + CSS obviously is a great minimum target. I've never completely understood the complete anti-js stance. Users like interactivity, animations, etc. Without going too much into the merits of JS or SPA vs MPA. Here is what I find as goals to deliver experiences that my client have come to expect.

  1. Avoid reloading/mounting common UI components. There is no reason to send my header bar and remount it on every page. This is worse if you happen to display login/avatar information in your title bar. Of course, you could SSR fetch this data on every page request. In my case, I'd prefer to leverage the distributed computing and storage of all my visitor's devices rather than scale my servers and caching solution.

  2. Off-line support.

  3. Interperate javascript once. Say I am using Framer Motion for visual candy. I don't want to speed up the time to initialize that codebase for every page refresh. Once a site starts to pass a "minimalist" design, the cost of fetching each page can be much higher than the previous hydrated SPA method.

It seems that maybe some of this could be a self-serve solution. A few ideas as a walk/crawl/run

It isn't nearly as nice as flagging islands to persist or having the platform understand the delta between changing islands and only replacing them. But it can be a step to allow someone to drop an app/spa framework into a larger site.

xriter commented 1 year ago

Goal proposal: 🔵 Don't rely on a manifest exposing all existing routes.

it's a false sense of security, some web apps rely too much on hiding routes that they neglect to secure their api, if it's sensitive then lock it behind a bulletproof api rather than hide it and hope nobody sees it, I think major frameworks expose all routes to stop this malpractice.

I agree that a bulletproof API is crucial. However, I believe there's a misunderstanding of the argument I'm putting forth. I'm not suggesting a "security through obscurity" strategy, nor am I implying that hiding routes should be a substitute for more robust security measures. It's rather a complementary step in a "defense in depth" approach to security.

Consider the scenario where the build files generated by the framework expose routes such as:

"/admin"
"/admin/takeovertheworld"
"/admin/ban-all-users"
"/admin/suppliers/our-secret-supplier"
"/admin/secret-product/work-in-progress"
"/(marketing)/shop"
"/(marketing)/shop/unreleased-product-name"
"/api/......"

This is somewhat equivalent to installing a lock on your door, but providing a potential intruder with a detailed floorplan of your house. While securing the door (API) is essential, I am essentially proposing to not leaving a blueprint 🗺️ of the house (all routes) lying around for a potential intruder to find. Yes, an experienced burglar 🥷 might eventually figure out the layout by examining the exterior or through repeated attempts, but why make it easier for them?

Although many major frameworks expose all routes, it doesn't mean it's a best practice which they implemented intentionally for this reason, or the only approach. It's not about choosing between removing all windows and locking the front door; it's about doing both, thereby ensuring the house is as secure as it can be. The point here is to minimise exposure and follow the principle of least privilege, a fundamental concept in secure software development. A client (or a potential attacker) should only have knowledge of the routes they absolutely need to know.

Routes can reveal more than just entry points. They can disclose aspects of business logic, proprietary information, or details about the system that could prove useful to malicious actors. For example routes that reference yet-to-be-released products, exclusive promotions, admin-routes, etc. I mean, if they were to discover the /admin route, that's fine. They'll just hit an auth wall. However, them being able to see all the existing (but protected) routes behind that auth wall is very undesirable. Every exposed piece of information can potentially be used maliciously. Hence, it is prudent to limit exposure wherever possible.

While I'm in total agreement that securing APIs is vital, I also maintain that we shouldn't dismiss the potential value of making it harder for potential attackers by not exposing all our application routes. It's not about opting for one security measure over another. Rather, it's about implementing as many layers of security as possible.

matthewp commented 1 year ago

@Brian-McBride That idea sounds a lot like the previous "persistent island" proposal. You can mark an island to persist and it will remain in the page between navigation. I'm working on a new proposal and this will be part of it.

@xriter We're not going to expose routes in the client. Instead you'll opt-in to a page to using client-side routing and any navigate from there will use CSR.

matthewp commented 1 year ago

First draft of the new RFC is up: https://github.com/withastro/roadmap/pull/607

mikehwagz commented 1 year ago

This is exciting! And timely, because I've been working on adding pjax-style CSR to my astro site this weekend. My 2 cents: I would probably never use the built-in animations. It would be great to expose an ability to animate pages by hand using another library such as GSAP.

matthewp commented 1 year ago

Closed via https://github.com/withastro/roadmap/pull/607 being merged.

mahdisoultana commented 9 months ago

Hello guys , please i have question about , is there any why to show progress bar between pages ? like nextjs has

nextjs-progressbar