- Notifications
You must be signed in to change notification settings - Fork27k
[Complete] RFC: Incremental Hydration#57664
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Authors:@thePunderWoman In Angular version 16, we launched full application hydration in Angular to improve your Core Web Vitals, server side rendering performance, and improve the overall initial render experience when using server side rendering. Then, in version 17, we added deferrable views, which allowed you to easily defer less important code until it was needed later, which further improved core web vitals and reduced initial bundle size. We've since also added event replay, which means none of your users' actions will be lost before hydration kicks in. Building on this functionality, we're excited to share more concrete plans for the next SSR improvement: incremental hydration. With incremental hydration, deferred content is rendered on the server side and skipped over during client-side hydration and bootstrapping - it's leftdehydrated. This allows Angular to bootstrap the initial page in less time, and keeps the code for those dehydrated components out of your initial bundle. Angular will leave those parts of the UI dehydrated until your declared hydration conditions are met, at which point it will download the necessary code and hydrate those components on demand. You already know the set of hydration triggers, as they're the same familiar Incremental hydration allows you to apply these familiar client-side What kind of feedback are we looking for?While we're excited to hear any and all comments on incremental hydration in Angular, we have additionally called out specific discussion points throughout the RFC for directed feedback. When you leave a comment, be sure to note the open question(s) to which you're responding. As always, keep our code of conduct in mind. We know that a proposal of this scope significantly impacts Angular and the way you use it in your projects. We appreciate your commitment and investment in this project and ask to keep comments respectful. Goals
Dehydrated ContentWhen Angular leaves content on the page in a dehydrated state, it behaves as static HTML:
In other words, the content appears exactly as it did during server-side rendering. Only when your hydration condition is met does Angular download the code for the components/directives within the content and hydrate it: instances are created, lifecycle hooks execute, and change detection updates the content based on its bindings. It's possible that users could interact with your dehydrated content before it can hydrate, especially since the hydration process requires fetching code and is therefore asynchronous. Angular addresses this using the Event Dispatch library, by adding event listeners to your dehydrated content and replaying events after hydration occurs (similar to the SyntaxDeferrable views, or defer blocks, are the building (defer) block and hydration boundary for incremental hydration in Angular. A defer block becomes a incremental hydration boundary by adding a hydrate on, hydrate when, or hydrate never condition. For example: <example-cmp/>@defer (on immediate; hydrate on interaction) {<deferred-cmp/><another-deferred-cmp/>} Similar to defer triggers and prefetch triggers, you can use multiple hydrate triggers at once, and hydration will occur when any of those triggers fire. Hydration triggers apply when a
Trigger TypesThe trigger types for incremental hydration are similar to the main@defer, with a few differences. Hydration triggers always apply to the actual rendered body of the@defer block and thus don't allow element references. They only apply to@defer blocks that are server-side rendered. idleidle triggers hydration once the browser has reached an idle state (see interaction
immediate
timer(x)timer(x) triggers hydration after a specified duration. hoverhover triggers deferred loading when the user hovers their mouse over the server rendered content. This condition is useful if hovering the mouse is a good indication that the user might soon interact with the content. viewportviewport triggers hydration when the server rendered content enters the viewport (detected using the never
whenJust like regular and prefetch deferrable view triggers, you can provide a custom condition which triggers hydration when it becomes Nested HydrationWhen multiple @defer(hydrateonhover){ @defer(hydrateontimer(15s)){ ...}} The outer block will hydrate if the mouse is hovered over any of the content inside of it. Even if the block is never hovered, the inner block's timer will trigger hydration after 15 seconds. There is one exception to this behavior: @defer(hydrateonhover){ @defer(hydratewhenuser()!==null){ ...}} If the outer block has not triggered, then regardless of the value of When hydration is triggered within a nested block, any parent block(s) are first hydrated if necessary, and then the triggered block is hydrated. Because each hydration step requires fetching the code for dependencies, this means that nested dehydrated blocks require a waterfall of code loading in order to hydrate.
Common Potential PitfallsRoot text nodesThe hydration triggers of Frequently Asked QuestionsQ: I have a question, but it's not ready yet.…ok Q: When will this feature be available?When it's fully hydrated. Q: Islands?We're thinking about them. Q: Does this mean Angular has server components now?No, it does not. Server components should always render on the server and never on the client. hydrate triggers only apply to first load. Q: When hydration is triggered on nested blocks, each with hydrate triggers, will that result in cascading requests or waterfalls?Yes, hydrate triggers on nested components will be a waterfall of requests, but this is something we plan to address. Q: OK, my question is ready now. Does this new version of Angular Cereal also have marshmallows?Ah I get it, this is a hydration trigger joke…and yes…marshmallows. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 61😄 24🎉 25❤️ 36🚀 31👀 12
Replies: 17 comments 44 replies
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Answer Question 1: No 🫠 please explain and give us some use case |
BetaWas this translation helpful?Give feedback.
All reactions
-
Use case A: Use case B: Use case C: |
BetaWas this translation helpful?Give feedback.
All reactions
👍 25
-
@thePunderWoman maybe deferred hydration is better name? It implies it's related to defer blocks...... |
BetaWas this translation helpful?Give feedback.
All reactions
👎 1
-
We discussed deferred hydration as a potential name. It's possible to perform full-app hydration on idle, which makes it deferred, but not necessarily incremental. Calling it "incremental deferred hydration" will be accurate, but also too long without bringing too much value. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Incremental Hydration and Partial Hydration both seems natural and reflects exactly what the feature is about. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Question (2): What would be the execution flow of the provided example with a mix of standard and hydration triggers? Syntax is not hard to read but how to reason about it (execution) |
BetaWas this translation helpful?Give feedback.
All reactions
-
mutually exclusive is a good way to put it, yes. Hopefully that's clear and makes sense. We are also utilizing event replay for the content in the hydrate blocks. So if a user interacts with that content, the interaction will not be lost. |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 2🚀 1
-
So I understand that the 'Hydrate' syntax makes the 'dom' structure render without hydration. But what happens when I use@Placeholder or@Placeholder+@loader together with this function? Does that mean they will be skipped on the server side? |
BetaWas this translation helpful?Give feedback.
All reactions
-
This is correct. In the SSR / hydrate case, your defer main template contentIS your new placeholder. The |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Ok, but if I don't use the word 'Hydrate', the placeholder will come from the server. I just want to be sure. |
BetaWas this translation helpful?Give feedback.
All reactions
-
@zygarios If you don't use the word |
BetaWas this translation helpful?Give feedback.
All reactions
🚀 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I'm super happy to see this RFC, everything here makes sense. I have just one feature request: please consider adding the “time” argument to “idle”. That would load the HeavyComponent either on hover or after a 2s delay after “idle”. There are just components that are far enough from the user’s path, and heavy enough to load them not on just “idle”, but with a small delay - because more important components will take a chance to be loaded on “idle”. Overall, this RFC is awesome and I hope it will be implemented in v19. |
BetaWas this translation helpful?Give feedback.
All reactions
👎 1
-
I'd advise |
BetaWas this translation helpful?Give feedback.
All reactions
-
@e-oz This is definitely more of a feature request for |
BetaWas this translation helpful?Give feedback.
All reactions
-
Sure!#57814 |
BetaWas this translation helpful?Give feedback.
All reactions
-
Will there be support for placeholder and loading blocks? |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
This is additional syntax for |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4
-
Discussion Question 1: Yes, it's clear Discussion Question 3: The syntax doesn't seem to be overly complicated. However, it is important to clearly describe the dependencies between different states, as I mentioned in point 2. Discussion Question 4: I don't have an opinion on this matter Discussion Question 5: I don't have an opinion on this matter |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
The RFC is great, good job!
Additional feedback:
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 3❤️ 1
-
When I read about this RFC, the first use case I imagined could be a potential win was routerLink handling in dehydratated content; I'd love this |
BetaWas this translation helpful?Give feedback.
All reactions
-
As a quick follow up, the only real concern around routerLinks in dehydrated code would be when routerLinks exist in |
BetaWas this translation helpful?Give feedback.
All reactions
-
Exactly, that is why it'd be wonderful that in hydrate never it'd still trigger an SPA navigation without needing to hydrate. For simple use cases shouldn't be that complex. The event of a hard navigation could be intercepted in the root element, the url parsed, and router execution triggered. It's nice to see that it may be explored 😃. This way only m truly interactive regions would need to be hydrated, |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
@thePunderWoman One additional question on this statement:
|
BetaWas this translation helpful?Give feedback.
All reactions
-
@samuelfernandez The event replay behavior makes sure the original click is not a hard navigation in this case. So it will behave as you would want, with the replay and the client side navigation. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
A little bit mixed with
Syntax makes complete sense.
No. Especially if Angular warns us about non-usable combinations, as it does with
No, but I do see common use with
I don't think so. |
BetaWas this translation helpful?Give feedback.
All reactions
-
@e-oz So |
BetaWas this translation helpful?Give feedback.
All reactions
-
Sure. I meant that |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I'd want the outer component to be hydrated while keeping the ad-unit component unhydrated.
What if some of the hydration triggers could also be added to Component config, so that whenever we use the component it's default hydration settings is applied, with option to overwrite with |
BetaWas this translation helpful?Give feedback.
All reactions
-
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Q2: Any reason @hydrate(onhover){<my-component/>} |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yes, I would assume Client Side Render could still use a |
BetaWas this translation helpful?Give feedback.
All reactions
-
@brandonroberts but since it's called@hydrate, which is specifically a server side rendered behavior, does that not send a confusing message? You can't hydrate client side rendered content. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Ok, I misunderstood the connection with CSR then. I was thinking of this in terms of shorthand syntax that works in both scenarios. I thought if you happen to use |
BetaWas this translation helpful?Give feedback.
All reactions
-
It wouldn't make sense to use defer and hydrate conditions without SSR. Similarly it wouldn't make sense to use |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I agree that@defer is more appropriate. In case of partial hydration we are in fact deferring the hydration to a more appropriate time. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I guess that I would generally use different triggers as in most cases, placeholders are short-lived as they don't make as much sense to the user as a dehydrated view which can often be hydrated way later. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Could you please provide an example where you have a server-side rendered (SSR) page, and you'd like to hydrate a child component based on a specific trigger? Additionally, in the case where the user navigates to the same page via client-side navigation, you want that component to be defer-loaded using a different trigger. I almost always ended up using the same trigger for both cases. |
BetaWas this translation helpful?Give feedback.
All reactions
-
A heavy datepicker that users don't often interact with. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
That’s an interesting use case, and while you might have specific requirements, I would generally avoid server-side rendering a datepicker. When a datepicker is rendered on the server, it might display the server's date and time, which could be confusing or inaccurate for users in different time zones. This could lead to errors if the displayed date doesn't match the user's local time. So, if you do SSR a datepicker, it’s crucial to hydrate it as soon as possible or during idle time to ensure the correct date is shown. However, I understand you may have unique needs. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I think about it alot. walking around not trusting anyone |
BetaWas this translation helpful?Give feedback.
All reactions
-
My gut feeling is that most users would prefer the default behavior to be Is there a reason why |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yes there is a reason we didn't make |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2❤️ 1🚀 1
-
@defer block doing hydrate on idle by default feels more natural to me 😀 |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2👎 2
-
💯 for
There could be an instruction to opt-out from hydration, even though it will be tough to name 😅: |
BetaWas this translation helpful?Give feedback.
All reactions
-
Perhaps something like: it doesn't need to be a |
BetaWas this translation helpful?Give feedback.
All reactions
-
I am not sure what |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yes. I am afraid that this can become quite confusing, especially for newcomers. Another thing that could help is something like reusable policies: // defer-policies.tsconstlowPriorityPolicy=createDeferPolicy({defer:'idle',hydrate:'interaction'});// my-cmp@Component({template:` @defer(policy lowPriorityPolicy) { ... } `})classMyCmp{lowPriorityPolicy=lowPriorityPolicy} or a better syntax if possible. The policies would of course have to be static so that the compiler can understand them. @thePunderWoman if you think this could be interesting, I can open an issue for this as it is not directly related to the RFC. |
BetaWas this translation helpful?Give feedback.
All reactions
👎 1
-
Being able to create reusable policies is definitely a thing we've considered. So if there's enough interest, it could be a future feature add. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3❤️ 1
-
What if I want the whole app to be set as hydrate never and then hydrate individual child components on demand e.g. hydrate some components on interaction and some on when they enter the view port. Something like an app wide confg that makes the whole app a static app with almost zero JavaScript unless a user starts interacting with the app. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
That concept is essentially what resumability is. We love this idea, but it is quite a bit more challenging and complicated to get to this point. Dependency injection, data dependencies, and a number of other complications all factor into making this a workable feature with Angular. We think incremental hydration is a step towards that possibility in the future. So stay tuned. |
BetaWas this translation helpful?Give feedback.
All reactions
👀 4
-
I use an ngx-image-cropper component, that access window, and document objects right at the start. In Angular SSR application this component throws an error: "ERROR RuntimeError: NG04002. Two solutions helped me: @defer (on immediate) { or <image-cropper ngSkipHydration With incremental hydration, what solution should I choose? |
BetaWas this translation helpful?Give feedback.
All reactions
-
Ideally you would update |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
-
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
Sorry. RFC conversations don't typically discuss expected timelines. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Sounds like an amazing game 🤩 Loge to see the future of apps with Angular. This is very amazing chunk to even not load KB's of code until we interact. |
BetaWas this translation helpful?Give feedback.
All reactions
-
tl;dr; thank you for all the feedback - our intention is to implement APIs described in this RFC and roll them out in developer preview for Angular v19! Shipping it as a developer preview in Angular v19!Thank you so much to all of you that left comments, questions, and feedback in this RFC. It was helpful in understanding people's impressions of this API and functionality. Summary of the discussion questions:
Mostly the answer here was "yes", but there were a few clarifying questions people asked.
The answer here is also largely "yes it makes sense", with some suggestions for a
The answer here is that the syntax is clear, but there might be confusion around the behavior between the two trigger types. We hope to address this with clear documentation.
There wasn't much response here to indicate there were concerns.
Similarly there wasn't much response to this question. I think we'll likely get more feedback as people try this feature out. Wrap upBased on all the comments and feedback we didn't find any use cases that were not covered in this design. There's some interest in a specific Thanks everyone! |
BetaWas this translation helpful?Give feedback.
All reactions
🎉 5