Proposal: Prefetching in Phoenix LiveView

Currently, there are ongoing discussions about enhancing Phoenix LiveView, particularly focusing on improving performance and user experience. One prevalent area of exploration is the introduction of prefetching capabilities. This feature would allow the application to preload content before it is requested by the user, leading to significantly quicker responses and a more seamless interaction with the interface.

While many Phoenix developers have outlined the potential benefits of prefetching, they often fall short in detailing the implementation process. To address this, my proposal emphasizes clarity and conciseness in articulating how prefetching can be integrated into LiveView.

Benefits:

  • Preload likely-to-be-needed content before user interaction.
  • Significantly reduce perceived latency in view transitions.
  • Maintain LiveView’s simplicity while adding powerful optimization options.

To streamline feedback and contributions, I have created a dedicated repository on GitHub. I invite you all to review the detailed proposal, provide your insights, and contribute to its development. You can find the repository here: LiveView Prefetching Proposal.

Although the proposal might not be completely ready yet, I welcome all contributions and updates from the community. We are committed to seeing this feature implemented soon.

Looking forward to your feedback and contributions!

5 Likes

I read through this and I think it’s an interesting proposal. It seems like you’ve thought about this a lot :slight_smile:

The main differentiator between LiveView and other server-rendered frameworks is that it’s stateful, and can therefore be used for building pretty complex interfaces. As a result, it’s important to consider the consistency guarantees of a UI with pre-fetching built in.

For example, imagine we had a “forum” UI, with tabs representing categories and a “new post” button and accompanying modal. Now imagine a user hovered over a tab (triggering a prefetch), did not click it, and then opened the modal and made a new post in that category.

Then, after selecting the category, the old prefetch assigns would clobber the updated state (the state which includes the new post), and the user would be confused: “I just opened this, why is it out of date?”.

Of course, this does not preclude the inclusion of this feature (a perfectly valid counter would be “don’t use pre-fetching for that”), but I think it’s important to keep in mind that LV apps are usually more interactive and therefore have different expectations with regards to consistency than simple “static” websites. This feature would have to be used carefully.

2 Likes

Thanks for your feedback!

This is not going to happen, If I’ve understood what you said correctly.

Because the state of prefetches won’t be mixed with the state of the current view; as it’s stated in the proposal.

However, if you believe this is still not true, feel free to open an issue in the repo.

No, but when the prefetch is applied to the page it will be stale (a consistency violation).

To be clear, this is not a problem that has a solution. Prefetches will be stale by definition (it’s essentially a form of caching). I just think it’s worth highlighting that you would have to be careful using such a feature in an interactive app (just like you would have to be careful using caching).

1 Like

You’re right. This behavior is a part of the nature of prefetching.
However, to my knowledge, if we redirect the user using the push_navigate function, the assigns will also be reset on the server (which is where the state of prefetches is stored). The expected behaviour is that the client also flashes its prefetching cache when the socket is reset in this scenario.

Yes, you could for example manually clear the prefetches after making a post. This would recover “read-your-writes” consistency, but certainly not serializability. You would have to be very careful to do this properly for every mutation initiated by the user, or there would be consistency bugs.

(Tragically, even most databases do not offer full (strict) serializability, but I digress.)

And potentially even other users if there’s any soft real time collaboration on shared resources.

In scenarios where prefetching would likely becomes stale, it’d be nice to have a way for the client to render a prefetched/cached view with a “still syncing latest changes” indicator/signifier as it checks with the server to quietly refresh the view if needed. It’d require even more from the server, but users would get their first contentful/meaningful paint sooner with weak/eventual consistency.

1 Like

Hey @MatinDevs,

thank you for the detailed proposal. Here are some comments:

  1. State Management
    • “When the server responds with a prefetched view, it stores all of the assigns related to that view inside the current assigns collection”
      • this cannot work because LiveViews are by definition their own process. Therefore prefetching must also use a separate process as usual to not break existing guarantees (imagine someone having a <%= send(self(), ...) %> in their template. This message must not escape to the LiveView that initiated prefetching.
    • “When the client sends a prefetch request, the server sends the view, which is cached by the client in the browser session storage”
      • just a comment: LiveView already has the concept of a Rendered structure that stores a view’s static and dynamic parts in memory in the browser. This is what could also be used when implementing something like prefetching. Using the browser’s session storage would most likely not be a good idea here.
  2. Rendering Logic for Prefetching
    • “During prefetching, the system renders the view with all elements and logic except for asynchronous functions […] (this decision ensures maximum compatibility with previous versions of LiveView)”
      • if the goal is to reduce latency, why should async tasks not be executed?
  3. “Prefetching logic can be bundled into a separate, optional file, allowing projects that do not require prefetching to exclude it entirely for simplicity and performance optimization.”
    • The LiveView JavaScript bundle always contains all the LV code. There is no tree-shaking or similar at the moment. I also don’t think that prefetching would justify adding it. So this part of the proposal can be removed because it’s not needed.
  4. Prefetch Cache Management
    • “To enhance control, a function will be introduced to clear the prefetch cache for the entire site. This is particularly useful in scenarios like user logout, ensuring sensitive data is not inadvertently cached.”
      • Logged in and logged out LiveViews should use separate LiveSessions and I’d say views across those must not be prefetched. A request to prefetch those should be denied by the server.
      • If implemented through in-memory Rendered objects, a full page refresh would clear the prefetch state automatically.

With all that said, a realistic implementation of prefetching would almost definitely require adoption of Liveview to be implemented first. As mentioned, separate LiveViews are always their own processes and this is not something we can change. But even if did have adoptable LiveViews - meaning we can spawn a new LiveView process beforehand for prefetching and “adopt” it when the real navigation happens, there is still one problem:

How would patch navigation work? By definition, a patch goes to the same LiveView and therefore the same process. This is a predicament since:

  • a prefetch should (I’d say must) not have side-effects on the current LiveView, but we cannot transparently calculate the new state in the same process
  • a patch navigation can depend on the current state of the LiveView, therefore just using a separate process also won’t just transparently work
  • → one solution would be to just not support prefetching for patch

Those are all my thoughts at the moment. While I don’t see a good way forward for this at the moment, I still want to thank you very much for the proposal and the thoughts you put into it!

1 Like