Ignore React and all the community specific acronyms, you're talking about a very fundamental problem that is technology agnostic and it's important to understand it from that perspective, so you can take this knowledge with you to any tool.
Whenever you try to precompile or even just cache an HTML file that relies on run time data, you are going to have to build your application in a way that is sympathetic to that. In most cases, you're going to have to figure out how to fetch the missing data on the client side and populate it into the template/runtime context.
This is an issue in Ruby, PHP, Javascript, React, Angular, Python... every single application with caching or compilation.
This is one of the (many) reasons why I've started using Phoenix LiveView for all the projects, when I can, that need either partial or full client-side functionality.
By simply keeping all the state, logic, and rendering on the server, all sorts of issues are avoided and various optimizations are possible, and I get full server-side rendering for free!
The client-side, meanwhile, via websockets, only sends events to the server and diffs whatever chunks of markup are sent back. Most of this is via server-side provided attributes (phx-click, phx-change, etc.), but for the slightly more 'exotic' stuff I can add my own events.
Obviously there are cases where this solution isn't practical, but this hasn't been the case for the vast majority of my projects.
Precisely. DDP is also based on WebSockets and does essentially the same thing - server side computation, client side optimistic UI updates, caching etc.
As with any stateful solution it has problems scaling. I'm not sure LiveView is the right answer.
From what I understand the number of open websocket connections is not really much of an issue, because this is one of the things Phoenix/Elixir excel at.
Memory might become an issue though, I suppose. Every user's state will be in-memory in 'their' LiveView process.
On the other hand, there's much less state needed than with a client-side solution like React, because it can be very quickly fetched when needed.
This is true but also the reason that abstractions that try to hide these issues from developers can have some surprising edge cases.
Next and Gatsby make it seem like you can just directly translate your front end React experience to SSR but in practice there are, as you say, some significant differences.
I don’t quite understand why this pattern would be necessary. I would prefer to render a ‚loading‘ state for dynamic components at build time, hydration sees a valid state and the user gets a clear indication of what is going on. Am I missing something?
No. This is a useful technique in the case where that's not feasible, but the choice of example is infelicitous in that rendering a loading state (or the unauthenticated nav, maybe) would be fine.
I have to admit that I didn't use Gatsby for anything serious yet outside of playing around with it so my genuine question is: In which case wouldn't it be feasible?
Asynchronous state updates regardless if they come from UI events or data fetching should be handled the same on the rendering side in my mental model. This has nothing to do with SSR/SSG.
If one would just write client side rendered code as if the whole application was a SPA, then this problem never arises as long as there is a clear separation of concerns between GUI and state management.
A React component doesn't have to know where its data is coming from nor where it is rendered. Wrapping a component in a 'client only' higher order component seems to add unnecessary complexity and brittleness by complecting the rendering tree with side-effects and state management:
What if at some point there needs to be a different render of this component with data fetched during build time?
In my opinion the example illustrates how moving state management out of components (Redux and so on) is a more sane idea, compared to encapsulating local state and side effects into components.
The component in question should not render content during build time, it is a UI element that represents the login state, which you certainly do not want to have pre-rendered at all, let alone be indexed.
Often, the data lives in localStorage, so it isn't really "loading". If your data needs to be fetched asynchronously, a loading state is a fine solution.
Even in this case, a loading solution works just fine, but it's the exact same pattern. You'd just `return <Spinner />` instead of `return null`. You also face the same issue, if you don't wait until post-mount.
I personally would rather defer reading from localStorage than defer rendering a component. I don't want my components to know when and where they are rendered.
At some point one just has to step back and question how necessary all of this complexity is. This "best of both worlds" hybrid approach adds so many hidden moving parts. I also don't really see why it's needed; the twin paradigms of "lightweight web page" and "heavyweight web app" really seem like they neatly cover the vast majority of cases.
20 comments
[ 2.4 ms ] story [ 55.4 ms ] threadWhenever you try to precompile or even just cache an HTML file that relies on run time data, you are going to have to build your application in a way that is sympathetic to that. In most cases, you're going to have to figure out how to fetch the missing data on the client side and populate it into the template/runtime context.
This is an issue in Ruby, PHP, Javascript, React, Angular, Python... every single application with caching or compilation.
By simply keeping all the state, logic, and rendering on the server, all sorts of issues are avoided and various optimizations are possible, and I get full server-side rendering for free!
The client-side, meanwhile, via websockets, only sends events to the server and diffs whatever chunks of markup are sent back. Most of this is via server-side provided attributes (phx-click, phx-change, etc.), but for the slightly more 'exotic' stuff I can add my own events.
Obviously there are cases where this solution isn't practical, but this hasn't been the case for the vast majority of my projects.
Also Phoenix is written in Elixir which could easily handle millions of stateful connections.
As with any stateful solution it has problems scaling. I'm not sure LiveView is the right answer.
Memory might become an issue though, I suppose. Every user's state will be in-memory in 'their' LiveView process.
On the other hand, there's much less state needed than with a client-side solution like React, because it can be very quickly fetched when needed.
Next and Gatsby make it seem like you can just directly translate your front end React experience to SSR but in practice there are, as you say, some significant differences.
Asynchronous state updates regardless if they come from UI events or data fetching should be handled the same on the rendering side in my mental model. This has nothing to do with SSR/SSG.
If one would just write client side rendered code as if the whole application was a SPA, then this problem never arises as long as there is a clear separation of concerns between GUI and state management.
A React component doesn't have to know where its data is coming from nor where it is rendered. Wrapping a component in a 'client only' higher order component seems to add unnecessary complexity and brittleness by complecting the rendering tree with side-effects and state management:
What if at some point there needs to be a different render of this component with data fetched during build time?
In my opinion the example illustrates how moving state management out of components (Redux and so on) is a more sane idea, compared to encapsulating local state and side effects into components.
The component in question should not render content during build time, it is a UI element that represents the login state, which you certainly do not want to have pre-rendered at all, let alone be indexed.
Even in this case, a loading solution works just fine, but it's the exact same pattern. You'd just `return <Spinner />` instead of `return null`. You also face the same issue, if you don't wait until post-mount.