Partial rendering and hydration of react on the client - #893
Conversation
|
PRbuilds results: 💚 AMP validation LightHouse Reporting --automated message |
|
Interesting stuff @oliverlloyd, thanks for the detailed description!
To complete the circle I have to ask, why not Islands for everything? Islands sounds closer to a progressive enhancement ideal (render as much as possible including placeholders on the server, enhance on the client). With Portals it sounds like we're presenting loading spinners and waiting for everything to be ready before displaying anything useful. Even if progressive enhancement is not possible for a particular piece of content, isn't it better (i.e. less complex) to have "one way of doing things"? I can't think of any tradeoffs with the Islands approach that necessitates using Portals in some circumstances. This is my naive reading, please correct me if I have misunderstood 😄 |
You're right, replacing Portals with Islands would make no discernible difference to the user experience so having one approach makes sense. This content won't be rendered on the server in any case, not the way things are currently setup, so the initial render would still be empty for these elements. The advantage to Portals is it's just one instance of react, not one per island. So that means you can share state between different portals. This is nice, but as things stand we don't have a requirement for this. Changes in context (navigation, changing edition, etc.,) trigger page loads so there's no state to manage. I'll have a look at migrating everything to islands in this PR. |
|
Thanks @oliverlloyd, interesting points. I agree with the YAGNI argument against shared context. However, I wonder if there's a hidden cost in called |
|
I had the same thought. I guess it depends on how fast 5 x is vs. 1 x |
| ...defaultStyles, | ||
| Comment: palette.opinion.faded, | ||
| AdvertisementFeature: palette.neutral[85], | ||
| AdvertisementFeature: palette.neutral[86], |
There was a problem hiding this comment.
I think that's where I'm polluting this PR by merging in master, sorry!
| // }, | ||
| // true, | ||
| // ); | ||
| window.guardian.modules.sentry.reportError(error, 'rich-link'); |
|
|
||
| const richLinkContainer = css` | ||
| width: 8.125rem; | ||
| ${until.wide} { |
There was a problem hiding this comment.
hmm it's weird that these changes are showing up here when they were made on my branch/merge
There was a problem hiding this comment.
That's me merging in master to fix conflicts. Sorry, I should have waiting for the reviews
philmcmahon
left a comment
There was a problem hiding this comment.
I've looked through this and agree with the motivation behind the PR and the code looks good to me. I would:
Deploy to CODE dotcom:rendering
- Test multiple different pages - both with/without ads.
- This is a page with a rich link https://m.code.dev-theguardian.com/film/shortcuts/2015/may/18/mad-max-fury-road-crazy-guitar-guy-doof-warrior-turning-it-up-to-11
|
(also looks like cypress tests are failing for some reason in teamcity, I'm happy to help debug this) |
nicl
left a comment
There was a problem hiding this comment.
I think I am for this as an improvement over what we have now. But would be great to get numbers for the performance impact before we merge as that is the primary motivation for the change.
|
There was a talk by Yuzhi Zheng, the React team manager, at ReactConf the other day about a feature they're working on as part of the react core which is very relevant to this PR: Progressive/Selective Hydration. It's only a few minutes long but a really interesting window into how they think our problem should be solved. Essentially Progressive means it lazy loads islands (renders a section, then hydrates it, then renders the next section, then hydrates it...). And Selective means it will decide the order to render/hydrate islands based on mouse position. This made me realise we should think about the order that we hydrate islands. For example: it makes a lot of sense to hydrate the Pillar menu and EditionDropdown before the ShareCount or MostViewed. |
@nicl Totally agree. I did try some comparisons using our internal webpagetest server but there was no notable difference in the reported numbers. I suspect wpt isn't giving us a true TTI value. I also worked with Phil to setup our performance monitoring on Code so we could use that to compare but the granularity isn't great (once a day) and there's a real risk other deploys will impact our control. I'm interested in the script you demo'd at the client side meeting the other day, maybe we can see if that helps here? I suspect the key will be gathering stats using a slower device. These are the users who we're doing this work for, people like you and I with modern laptops and phones are unlikely to feel much benefit, esp. not at this stage of the project where we have relatively few interactive elements on the page.. |
|
@nicl I used your |
Wow! Is the reduction accounted for by components that are rendered on the server only? Or do you think something else is being excluded? |
|
I guess it's a bit like we've implemented code splitting. Before, the code path traversed the whole app, importing everything into the bundle. Now, with the change in this PR, it is limited to certain routes, so many components are never touched. The number of components touched drops from 94 to 54. These images show the webpack bundle analyser output for the react bundle before and after. You can see that with islands there are substantially fewer files under Before (without islands)After (with islands) |
This will be another big advantage. Right now we pass a lot that we don't need, so reducing that and being more specific will be great.
This is another key IMO - Islands mean we don't have to do any hydration of bottom of article related content until we reach a scroll position (and probably some other components only if use mouse position or even interaction!) |
|
Oh and this means we can include useful libraries on the server, without having to use them in the client. In particular, this example: #694 |
| component: IslandType['component']; | ||
| // props: any because TS is locking it's checks to the first type | ||
| // in the IslandProps union, and then failing subsequent items | ||
| // in out array of islands |
There was a problem hiding this comment.
| // in out array of islands | |
| // in our array of islands |
|
|
||
| <body> | ||
| <div id="app">${html}</div> | ||
| ${html} |
…tcom-rendering into oliver/partial-react





What does this change?
This replaces our current approach of fully hydrating the client app with partial hydration of separate react nodes, only where required.
React Islands
Currently we render content on the server and then hydrate the entire app on the client. This technique only hydrates the specific areas of the app that require react, leaving the rest as static html.
The components using this approach are:
Nav (the main pillar menu in the Header)EditionDropdownMostViewedReaderRevenueLinksRichLinksShareCountThe user experience is that the page will load and whatever content that exited the server render will be present on the page. React is then added to each dynamic part of the page with the following impacts:
Nav (the main pillar menu in the Header)The main pillar menu is immediately present on page load. The More button only starts working after react is loaded
EditionDropdownThe dropdown control is visible on the page but does not become active until react is added
MostViewedThe entire component is missing on initial load and only appears after react is loaded and the api call to get the content returns
ReaderRevenueLinksThis component is missing on initial render and only appears after react is loaded
RichLinksThese are hidden on initial load and pop in once react is present and the api call returns
ShareCountThis is hidden on initial load and appears once react is present and the api call returns
Next steps
This code tests well and is functional. It is logical that it should be more performant but the improvement is difficult to measure so discussion around an approach for implementation is required.
With this PR in place it would be logical to review the data we pass to the client using
window.guardian. This could likely be substantially reduced.Why?
Performance
Link to supporting Trello card
https://trello.com/c/cA7ymUYg/781-spike-portals