angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
94.88k stars 24.76k forks source link

feat(DomRenderer): allow partial DOM hydration from pre-rendered content #13446

Closed jeffbcross closed 2 months ago

jeffbcross commented 7 years ago

I'm recapping a discussion I just had with @alxhub and @tbosch. This is mostly Tobias' design.

I'm submitting a ... (check one with "x")

[x] feature request

Current behavior

Typically when a page is pre-rendered, such as with Universal, the Angular application bootstraps in the browser, then blows away the pre-rendered content, and replaces it with newly created nodes. The DOM tree that is pre-rendered is often very similar, or identical to the DOM tree created on the front end, but there are issues that make it difficult for Angular to take over the existing DOM tree (otherwise referred to as DOM hydration).

The actual handoff of destroying the old DOM and showing the new DOM is pretty fast and seamless, so it's not necessarily a major UX issue in and of itself. Where it becomes problematic is in cases like ads that load in iframes (which is pretty much all display ads). If these ad iframes are pre-rendered -- which is a business requirement for many publishers -- and the iframe gets moved in the DOM, the iframe will refresh. This causes some ad networks to suspect abuse, as if publishers are trying to sneak more ad views.

Why Not Use the Already-Rendered DOM?

One issue is that with asynchronicity+conditional DOM (i.e. *ngIf="data | async"), the tree in the client may be rendered before the condition is truthy, whereas the pre-rendered version may have the full tree with async data resolved.

Another challenge is that text nodes are not able to be selected by CSS selectors, which would mean the renderer would have to rely on child ordering in order to associate pre-rendered nodes with client-rendered nodes (which is not always correct). Similar challenge goes for elements in an *ngFor, the order must be assumed to be identical.

The renderer would also be responsible for cleaning up pre-rendered orphan nodes. i.e. if 30 items in an *ngFor were pre-rendered, but only 20 were rendered in the client, the additional 10 nodes would need to be removed to prevent unexpected behavior.

Proposal: Optional, explicit, partial DOM Hydration

Allow setting a user-specified attribute on elements to associate the pre-rendered version with client-rendered version. If the renderer comes to a node that it can't associate with an existing node, it will blow away the node and re-create it. The developer would be responsible for setting these ids on the elements they care about. Example:

import { HydrateDomConfig, NgModule } from '@angular/core';

@NgModule({
  providers: [
    { 
      provide: HydrateDomConfig, 
      useValue: {
        hydrate: true, // default false for backwards compat
        attribute: 'pid', // default 'id'
      } 
    }
  ]
})

Component:

@Component({
  template: `
    <div pid="header">
      <header-ad pid="header-ad"></header-ad>
      <div>
        <!-- this will get blown away and re-created since it lacks attribute -->
      </div>
    </div>
  `
})

This design allows the DomRenderer to traverse the DOM tree and match elements for each branch starting at the root until it can't go any deeper, at which point it would blow away the descendants and re-create them.

Text nodes would all be destroyed and re-created with this design, as well as any node that doesn't have the set attribute, pid.

I don't expect that the rendering would be user-perceivable, other than if there are discrepancies between pre-rendered and client-rendered DOM, but that's a concern even without this feature.

CC @gdi2290 @pxwise

@tbosch & @alxhub please feel free to add anything I missed (or misrepresented).

pxwise commented 7 years ago

Ad wipeout on client bootstrap is a real world issue for us using universal and this hydration proposal should solve it. Our current workaround gets us partway there, moving server DOM into the same position in client rendered DOM but does not outsmart ad verification services that watch for DOM mutations, hiding the ad upon client bootstrap.

Big thumbs up for opt-in hydration.

PatrickJS commented 7 years ago

LGTM, Keep in mind this is a huge problem with all js frameworks so for Angular to have a solution is a great win for when comparing different SSR solutions. The real solution is having Ads actually work together with js frameworks but that will never happen, other than AMP. I can see how it would be implemented by rewriting selecting root element and providing a different render path.

For the |async you definitely have that problem with Promises due to the microtask while Observables do return synchronously. On the client, we can assume there will be batched data sent from the server to the client which gives us all of the results immediately. For Universal, we need to have the data available to the client synchronously anyways to reuse the server Http responses

var res = {data: 'ok'};
var api = Rx.Observable.of(res);
var api2 = Promise.resolve(res);

var vm = {};
var vm2 = {};
api.subscribe(function(data) { vm = data; });
api2.then(function(data) { vm2 = data; });
console.log(vm); // {data: 'ok'}
console.log(vm2); // {}
jeffbcross commented 7 years ago

An alternative design would be to provide an alternate renderer that would extend DomRenderer, rather than modifying DomRenderer to behave differently depending on the hydrate value.

@NgModule({
  providers: [{
    provide: Renderer, useClass: DomHydrationRenderer
  }]
})
PatrickJS commented 7 years ago

+1 for DomHydrationRenderer

IgorMinar commented 7 years ago

Two things:

playground commented 7 years ago

+1 either solution will be beneficial and vital for us to move forward.

tbosch commented 7 years ago

We can still make the work generically, when using auto generated ids based on the place in the element hierarchy (and not based on the creation order).

I think we should think this through as well before we decide.

FahadMullaji commented 7 years ago

is HydrateDomConfig part of @angular/core? I get error when I try to compile the application.

error TS2305: Module '"/home/fahad/Workspace/siteCuriouss/node_modules/@angular/core/index"' has no exported member 'HydrateDomConfig'.

DzmitryShylovich commented 7 years ago

@FahadMullaji it's only a proposal.

josephliccini commented 6 years ago

This proposal is super exciting!

Has any thought been given to pre-rendered lazy routes? We can achieve pre rendered lazy routes via https://github.com/angular/universal/blob/master/modules/ng-module-map-ngfactory-loader/README.md

Is it possible for the hydration to occur after lazy route is fetched and then rendering begins?

Just curious on thoughts here.

Universal has been great and straightforward to use so far; thanks to everyone involved!

mcferren commented 6 years ago

What couples the ComponentRef obj to the Dom string (cmpref.changeDetectorRef.rootNodes[0] & cmpref.hostView.rootNodes[0] ect)? Is there an explicit reference(i.e. dom string selector) or does this binding live in memory? Is there a possibility of calling createComponent with an existing dom node as an argument?

vytautas-pranskunas- commented 5 years ago

Any updates on this - i have big issue with this as everybody here :(

FahadMullaji commented 5 years ago

I quit working in Angular JS a year ago. I just got tired of all the problems and false promises.

On Sat, Aug 18, 2018 at 11:37 AM Vytautas Pranskunas < notifications@github.com> wrote:

Any updates on this - i have big issue with this as everybody here :(

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/angular/angular/issues/13446#issuecomment-414070398, or mute the thread https://github.com/notifications/unsubscribe-auth/ABU4JqXoyDHyfxps21n2DGm0jYang4h6ks5uSEK0gaJpZM4LMcBh .

-- Regards Fahad Mullaji

vytautas-pranskunas- commented 5 years ago

Is there dom hydration problem solved in ather spa libraries?

On Mon, Aug 20, 2018, 9:26 PM Fahad Mullaji notifications@github.com wrote:

I quit working in Angular JS a year ago. I just got tired of all the problems and false promises.

On Sat, Aug 18, 2018 at 11:37 AM Vytautas Pranskunas < notifications@github.com> wrote:

Any updates on this - i have big issue with this as everybody here :(

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <https://github.com/angular/angular/issues/13446#issuecomment-414070398 , or mute the thread < https://github.com/notifications/unsubscribe-auth/ABU4JqXoyDHyfxps21n2DGm0jYang4h6ks5uSEK0gaJpZM4LMcBh

.

-- Regards Fahad Mullaji

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/angular/angular/issues/13446#issuecomment-414433412, or mute the thread https://github.com/notifications/unsubscribe-auth/ADvMl2WKIU4PSv84mXBXAe-33WD-jbmAks5uSw1ggaJpZM4LMcBh .

PatrickJS commented 5 years ago

@vytautas-pranskunas- Every framework, other than angular, has solved this problem. With that said it might not be a problem once the new ivy renderer is released.

vytautas-pranskunas- commented 5 years ago

gdi2290 What makes you think that ivy renderer will solve this sunce i have not found any mentions of solving this or introducing virtual doms in ivy?

I wonder why Angular team is silent about this forcing more people to be dissapointed...

On Tue, Aug 21, 2018, 1:40 AM PatrickJS notifications@github.com wrote:

@vytautas-pranskunas- https://github.com/vytautas-pranskunas- Every framework, other than angular, has solved this problem. With that said the problem might not be a one with the new ivy renderer

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/angular/angular/issues/13446#issuecomment-414498736, or mute the thread https://github.com/notifications/unsubscribe-auth/ADvMl-LwoyJmtroffUkIvEGkXDtsMjm3ks5uS0jjgaJpZM4LMcBh .

naveedahmed1 commented 5 years ago

For the ads, I think the ad code shouldn't initialize on sever, if it does I think it would be a policy issue for most of the ad networks/advertisers including Google Adsense.

For this we can check and include ad code only when in browser.

You can check if its running in browser

import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

private isBrowser = isPlatformBrowser(this.platformId);

constructor(@Inject(PLATFORM_ID) private platformId) { }

Adding ad code to SPA is little tricky, if your page is making an ajax request and main content of the page depends on completion of that request you should also wait for that request to complete before you add ad code to the page. Otherwise, if ad code is initialized before ajax request completes (and contents are populated) it would also cause ad network policy issue.

PatrickJS commented 5 years ago

yeah the issue isn't rendering ads on the server so much as trying to make sure angular doesn't remove/edit/change the ssr element. There was a solution which reattach the ad dom from the server to the new dom created by the client. Again the problem with that is the ad detecting dom manipulation. So the only reason why people want hydration is for ads since it's always faster just to replace the ssr view with csr view. There were solutions made to also keep the element in the dom and insert/remove around that element but would require some rewriting of the renderer to keep track of the elements.

Ivy render will likely solve this since you can choose which elements you want to boot angular into and ignore the ad elements.

vytautas-pranskunas- commented 5 years ago

Adds are not only reason why people want hydration. Hydration needed for prerendering because in scenario when page content is fetched after bootstrapping SSR has no flashing but prerendered. So hydrations needed for prerendered pages also and not sure if ignoring will solve this problem or you will have to ignore whole body because content is everywhere.

I think best solution would be something simmilar like React Virtual DOM which checks what parts where changed and update only those one.

trotyl commented 5 years ago

Hydration is only a lib stuff, nothing tied to the view layer, one can already provide custom Renderer to achieve it.

Clear existing contents is an implementation-specific behavior of default Renderer2#selectRootElement provided in platform-browser.

vytautas-pranskunas- commented 5 years ago

Do you have any example of custom Renderer2 implementation and how to use it after?

trotyl commented 5 years ago

@vytautas-pranskunas- There's an article for old V1 Renderer: https://blog.nrwl.io/experiments-with-angular-renderers-c5f647d4fd9e, should be easy to migrate:

vytautas-pranskunas- commented 5 years ago

trotyl thanks for this. However instead forcing all users to write own renderers it would be benifitial for Angular renderer to update only those dom parts that has changes. Because comparing SSR or prerendered DOM with new one is not on day task if we want to do it correctly not just keep SSR content...

trotyl commented 5 years ago

Yes, the feature request is totally valid, but since it might still need to wait, one can made it in 3rd party library and share to public. Community inputs could also be very beneficial.

4z5lz commented 5 years ago

is there any progress with this feature request?

aaronfrost commented 4 years ago

So many things were blocked behind the release of Ivy. I am so ignorant to the many wonderful things that Ivy enabled the Angular team and Angular lib authors to do, so I am going to ask: does the release of Ivy enable this ticket to be worked on?

With 2020 being the year that the Angular community got improved pre-rendering with Angular Universal and also got wonderful pre-renderer in Scully (I am biased), this would be a fantastic time for hydration to become a feature that Angular devs can count on. Perhaps not this exact flavor of hydration. But some flavor that allows developers to opt-in to a "don't delete my nodes if they still work" type of functionality.

Anything?

jeffbcross commented 4 years ago

Good question (I don't know the answer), but we at Nrwl have clients who would benefit from this, and might be willing to sponsor work.

juristr commented 4 years ago

but we at Nrwl have clients who would benefit from this, and might be willing to sponsor work.

Oh yes, +1 for this 👏

CaerusKaru commented 4 years ago

I can’t speak in an official capacity without official sign-off from the team, but this is one of the Universal team’s priorities post-v9. What this will end up looking like will depend almost wholly on commitment and design collaboration with the framework team, which is currently pending. We’ll post updates here as they come, but I will certainly relay this interest higher up.

aaronfrost commented 4 years ago

I hope that whatever is the finalized solution is, that is available to Scully and other pre-render solutions out there as well.

Thanks for the update @CaerusKaru

h3d0 commented 4 years ago

Here is another case:

The app aiming for a max potential Lighthouse 6 Performance score gets:

image

Looking at the trace:

image

What happens is:

Solution

Fix re-hydration, by keeping the same DOM (not replacing it).

Suddenly, the score starts looking like:

image

Trace:

image

What happens:

In the example above I have commented the pollyfill zone.js pretty much stopping the app from executing JS which triggers rehydration. The DOM is fully legit and styled (with the data loaded from the DB via API), except for it does not contain any JS listeners..

--

@CaerusKaru

The https://github.com/angular/universal/blob/master/README.md#angular-50-and-beyond contains:

In Progress Static site rendering Planning Full client rehydration strategy that reuses DOM elements/CSS rendered on the server

Are there any chances this will include the fix for LCP as well? Adding that to Universal makes Angular Universal a pretty much ultimate PWA solution with all the tools for devs, SEO and clients.

h3d0 commented 4 years ago

One more important note. Yesterday Google has officially announced that Web Vitals become a major factor for updated page rank.

This means that FCP, LCP and FID will affect how the apps are ranked in the search results.

The bare-bone Angular Universal + PWA project will have numbers similar to this:

image

Looking at the trace:

image

This is pretty-much the minimum main bundle one can possible have (without any 3rd party code):

image

And the Total Blocking Time already around ~500ms.

With this input in mind, users of Angular are not allowed to have 100% score even with the minimum setup. Now what happens when the actual app code is added?


Ivy-universal live?

There is a very interesting project by vikerman - https://github.com/vikerman/ivy-universal that could possible resolve this and many other issues. Creating a 12kb main bundle with ability to code split on the component level. Doing that + smart re-hydration (without destroying existing DOM) would be a huge leap. Are there any plans to continue working in this direction?

mgechev commented 4 years ago

There's a difference between hydration and progressive hydration. The demo by Vikram implements the latter.

In general, once an app has been pre-rendered or server-side rendered the user would get a large FCP. Later on, the page would reference script files which contain the framework as well as our Angular app. Once the framework takes over, there are two scenarios:

  1. It (re-)renders the UI
  2. It tries to hydrate the UI

If, as developers, we do our job well, after the framework takes over users would see the exact same UI, no matter if we went through 1. or 2. This means that theoretically LCP and FCP for server-side/pre-rendered application done right should be the same (I'm not saying they are the same right now).

Of course, ideally we'd want to progressively hydrate the application. This way we'd not only improve LCP and FCP but also TTI.

Let me quickly share what's progressive hydration and why it's that hard to achieve. With progressive hydration we have the same SSR or pre-rendering behavior. Together with the rendered page we also ship a tiny bit of JavaScript. This tiny bit of JavaScript tries to understand when it should trigger hydration of a particular component in the component tree. Things get a little more complicated when we start talking about state, so let's ignore state for now.

Imagine the user clicks a button. The tiny bit of JavaScript would detect that interaction, it'll figure out which is the associated component with this button and will trigger a network request, downloading the component and all of its dependencies that need to be loaded synchronously.

See how we only hydrated the component the user was interested in and its synchronous dependencies, nothing else. This means that we got the smallest amount of JavaScript that can handle the user action. That's how we'd speed the TTI up.

Now, why is this hard? Imagine the component which handled the button click emits an event that needs to be handled somewhere in the component tree, outside of the synchronous dependencies that we downloaded. By event I don't mean an output or a DOM event, I mean any event, for example:

const pubSub = new EventEmitter();
pubSub.emit('foo', bar);

How can the framework understand that this event was triggered? How can it understand who's listening for it? There's no way, unless the message bus abstraction comes from the framework and the dependency graph was statically analyzable (i.e. at build-time we know who is going to listen for foo so we can add a loading instruction somewhere).

This is the hard part. We need to set constraints on how folks can use Angular so that we can enable progressive hydration. Theoretically, you can use this subset of Angular and build apps with progressive hydration today. The example app from above implements progressive hydration and also uses a subset of Angular that's compatible with this paradigm today. It's not clear whether the constraints the progressive hydration sets are viable at the moment.

Aside from that, if you notice significant differences between the timestamps for FCP and LCP for apps which do not render additional content on the screen when the framework takes over, please comment here.

h3d0 commented 4 years ago

@mgechev thank you for an explanation.

if you notice significant differences between the timestamps for FCP and LCP for apps which do not render additional content on the screen when the framework takes over, please comment here.

Here is an example.

Steps to reproduce

  1. Run ng new --routing foo.
  2. Run ng add @nguniversal/express-engine.
  3. Clean up app.component.html all code, keep <router-outlet></router-outlet>.
  4. Run ng generate module --module=/ --routing=true bar.

At this point, this is the most minimal setup possible. There is pretty much a root app component and a bar component which is supposed to be loaded on demand (code splitting).

Performance

Now have a look at the Performance trace:

image

This is the same setup used by Google Lighthouse

FCP is at 1.2s mark:

image

LCP is at 2.5s mark!

image

This already barely falls under Google's Core Web Vitals:

image

Taking into account Lighthouse 6 updated calculations (LCP (25%) and TTI (25%) both affect 50% of the rating). This already puts the users (devs) of Angular Universal in a disadvantage. Adding any meaningful 3rd party tools (Google's Angular Material, fonts, animations) will make the performance result even worther.

Potential problems

NOTE: The simulation (used by LH as well) adds ~560ms round-trip delay to all Network events.

  1. The client makes a request and the server returns a rendered HTML page. This part is completely on the dev (optimizing server TTFB via using a viable hosting service, tuning the proxy, using compressions and cache):

image

  1. The client receives the data and starts loading the resources (JS, styles, fonts, images). After this is done the FCP occures:

image

The data is used for SEO (crawlers) and to provide an early FCP. If the dev does everything right (and do not run different code on client and server side) - the FCP is also a LCP, because Universal renders the exact copy of the view.

What happens next is the client discovers the lazy-loaded chunk (for the bar component) and start loading it - resulting in another 600ms delay! (Remember LH uses 560 round-trip delay for measurements). It takes extra 600ms to load the 700B JS file:

image

Thoughts

In my opinion the root of the problem is the code that cause LCP at the very end. I have found a similar issue reported with a comment:

You've bumped into an unfortunate edge case where the heuristic used by first meaningful paint fails. Apparently the hydration step of angular universal touches/adds enough elements to fool the heuristic into selecting the later paint instead of the real first one.

The case I've shown is the "Hello world" Angular Universal app - it makes every app powered by Universal an edge case and is clearly a show stopper for falling under Google's Core Web Vitals.

naveedahmed1 commented 4 years ago

I share the same concerns as pointed out by @h3d0 . Maintaining great Lighthouse score has always been very challenging with Angular Apps; with Lighthouse 6's updated calculations and Web Vitals it has now become more difficult.

mgechev commented 4 years ago

@h3d0 thanks for providing this analysis. We'll discuss the implementation of LCP with the right folks and come back to this thread when we can.

mgechev commented 3 years ago

Just to give you heads up. We got in touch with Chrome. The way we hydrate the view is not yet impacting your search ranking, but may do in the next a couple of months.

We're in a process of discussing how to reduce the risk of this happening.

mgechev commented 3 years ago

Chrome is experimenting with a new definition of LCP, which would have much lower impact on Angular apps (if any). You can track the progress here.

Based on my understanding, Google Search does not yet penalize Angular apps with SSR.

pitAlex commented 3 years ago

What can we do here? I've been racking my brain for 2 days now in trying to make this work and in my case, simply rendering just the header of profile page - no extra parts, still that goes way above Google's threshold

h3d0 commented 3 years ago

Chrome is experimenting with a new definition of LCP, which would have much lower impact on Angular apps (if any).

Thank you for escalating this with the Chrome team. While been an excellent and fun to develop with, I am grateful that Angular will be treated fairly by the new performance measurement algorithms.

Based on my understanding, Google Search does not yet penalize Angular apps with SSR.

That is correct. If I understood Google's statement correctly as well:

A note on timing: We recognize many site owners are rightfully placing their focus on responding to the effects of COVID-19. The ranking changes described in this post will not happen before next year, and we will provide at least six months notice before they’re rolled out.

So the Chrome team has at least 6 months to address the issue.


@pitAlex Angular has many ways to increase the performance of the app (both start-up and run). Ivy renderer improves compiling and reduces the bundle size, Universal got new ways of debugging your app (running a production-like build, while being moderately fast to update).

I would suggest to get more knowledge at places like Web.dev or Angular InDepth. If you still struggle with your issue, start a thread and provide more details (setup, what has been done, what issues have popped).

pitAlex commented 3 years ago

I don't see how this can be fixed if Google starts using the client version of the page and this is what we see in our current search console analytics. For example, the angular bundle size of a page is 191kb and that takes 2.4s to download on a simulation of "4x slowdown" with "fast 3g". As it flushes the DOM it ends up with LCP of 6.3s when measured. But if I take the same page and using angular node rendering, without insert any js, I am below 2s with a perfect score - always. This his how we set up our pages: for search engines we server non-js version and for users, we give the one with angular. But if Google is now ignoring the search engine version, there is no point in even using frameworks like angular. If it takes 2.4s to download, I have almost nothing left for executing the respective bundle and rendering the page. And no production page will be as simple as a "hello world" template that I keep seeing in all these guides. Let me see, for example, that the angular.io can meet these requirements. That's a realistic production page.

mgechev commented 3 years ago

I definitely appreciate you sharing your concerns, they are a valuable input for the team.

As I mentioned above, we're in a process of resolving this issue, working in collaboration with Chrome. Fixing the problem is a top priority for us.

At the moment there are no Angular apps impacted. I'd suggest to follow the progress from the ticket I shared above. We're looking into the implementation of LCP together with the original authors. I strongly believe we're on the right track.

I'll keep you posted in this issue if there are updates.

naveedahmed1 commented 3 years ago

This his how we set up our pages: for search engines we server non-js version and for users, we give the one with angular. But if Google is now ignoring the search engine version, there is no point in even using frameworks like angular.

If you are serving two different versions of the same page, one for the search engines and one for your users, isn't it clocking? and wont the search engines consider it as spamdexing technique?

And since you mentioned:

I don't see how this can be fixed if Google starts using the client version of the page and this is what we see in our current search console analytics.

There's a possibility that Google is already ignoring search engine specific version of your page and choosing to index what an end user actually see when they visit your website.

As announced in last year's Google I/O, I think Google's crawlers Googlebot is now “evergreen,” which means the crawler will always be up-to-date on the latest version of Chromium so it will see your page exactly or at least close to as a normal users see it in browser.

Ref: https://webmasters.googleblog.com/2019/05/the-new-evergreen-googlebot.html

We're using Angular + SSR/Universal since v2 and so for we haven't seen any search engine indexing issue with our website. The only issue we noticed is the new LCP matrices introduced in Lighthouse 6, but I'm confident that the way Angular team is working closely with Chrome team, we will see a fix soon.

housseindjirdeh commented 3 years ago

Hi folks 👋🏾.

Dropping some updates here (I work with the Chrome team):

This issue was prioritized after noticing it affects server-rendered sites that re-render DOM on the client, so thanks to all of you for flagging!

h3d0 commented 3 years ago

@mgechev @housseindjirdeh what is the current status of this issue?

Chrome 84 has been officially released around 4 days ago. Lighthouse 6.1.0 still incorrectly puts LCP to the very end, while Angular SSR returns 100% unchanged DOM on FCP:

image

The DOM has no single change between FCP and LCP, Angular would only re-create the nodes and add JavaScript to it (re-hydrate).

This change already hit our commercial project Performance:

image

the Performance score dropped from 97-100 to 67 - without any changes have been made to the project code itself.

h3d0 commented 3 years ago

Lab tests

There is a spread in the metric got from different sources. Ran 5 tests for each method.

Web Dev Measure

https://web.dev/measure

image

Pagespeed Insights

https://developers.google.com/speed/pagespeed/insights/

image

Chrome Lighthouse extension (Chrome 84, latest extension)

Incognito mode

image

NPM Lighthouse 6.1.0

lighthouse <url> - https://www.npmjs.com/package/lighthouse

image

Summary

From all the tools only NPM Lighhouse 6.1.0 sometimes, on some pages correctly counts the LCP (1.5 sec in case of our proj for that page).

I would like to point out that from the developer's perspective - having 4 tools which does the same thing and getting different results is a complete mess. Secondly, it seems like only the NPM Lighthouse correctly measure the LCP.

Which tool does Google use for ranking a page? We kindly need a single verified working source of truth for measuring web-apps performance.

mgechev commented 3 years ago

The change hasn't been reflected in Google search yet (see comments above).

There are also no changes in the metric implementation. @housseindjirdeh and I will make sure to share any updates.

mgechev commented 3 years ago

Hi folks, the Chrome team has been working hard on the new implementation of LCP and it is already available in recent versions. You can give it a try with your Angular apps using:

  1. Use a Chrome version that's at least 85.0.4182.0 (Canary or Dev right now).
  2. Make sure chrome://ukm shows ENABLED, otherwise relaunch chrome with --force-enable-metrics-reporting.
  3. Load the website (don't do any early inputs to avoid terminating the algorithm early), and then close that tab.
  4. Refresh the chrome://ukm and find PaintTiming.NavigationToExperimentalLargestContentfulPaint within the correct URL with the PageLoad event (compare with the current version: PaintTiming.NavigationToLargestContentfulPaint).

The expected behavior is NavigationToExperimentalLargestContentfulPaint being less than NavigationToLargestContentfulPaint. Keep in mind that if you have layout shift (i.e. you don't render the exact same content), you may still see both markers showing the same value.

If you see a different behavior, please share an example where we can reproduce the incorrect LCP.

ghost commented 3 years ago

It looks good ! Thanks for the update @mgechev Leaving this in case it can help anyone ( make sure to close the tab where you loaded the site before refreshing chrome://ukm )

Screenshot 2020-07-22 at 11 04 44 Screenshot 2020-07-22 at 11 10 37
mgechev commented 3 years ago

@h3d0, would you also check both metrics?