Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blazor] Add lighthouse score to automated perf tests #29303

Closed
drma-tech opened this issue Jan 14, 2021 · 29 comments · Fixed by aspnet/Benchmarks#1944
Closed

[Blazor] Add lighthouse score to automated perf tests #29303

drma-tech opened this issue Jan 14, 2021 · 29 comments · Fixed by aspnet/Benchmarks#1944
Assignees
Labels
affected-medium This issue impacts approximately half of our customers area-blazor Includes: Blazor, Razor Components feature-blazor-boot-up feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-seo feature-templates Perf Pillar: New Blazor Priority:1 Work that is critical for the release, but we could probably ship without severity-minor This label is used by an internal tool
Milestone

Comments

@drma-tech
Copy link

image

image

Is there anything I can do to improve the performance of the system or any future plans for Blazor to do that out of the box?

@javiercn javiercn added area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly labels Jan 14, 2021
@javiercn
Copy link
Member

@drma-dev thanks for contacting us.

We do some things out of the box to make loading efficient, but there are more things you can do.

You could use preload link tags/headers to download critical assets early, like blazor.boot.json and the dlls so that the runtime can start faster. Prerendering should also help in this case to improve the score.

@drma-tech
Copy link
Author

<link rel="preload" href="_framework/blazor.boot.json" as="fetch">

image

@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Jan 14, 2021
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Jan 14, 2021
@ghost
Copy link

ghost commented Jan 14, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@ChristianWeyer
Copy link

Do you have a concrete example that works @javiercn ? Thanks!

@SteveSandersonMS SteveSandersonMS added affected-medium This issue impacts approximately half of our customers severity-minor This label is used by an internal tool labels Jan 26, 2021 — with ASP.NET Core Issue Ranking
@ChristianWeyer
Copy link

Did you get any further in your attempts to speed up loading @drma-dev ?

@drma-tech
Copy link
Author

man, I believe that any blazor project happens this

@drma-tech
Copy link
Author

https://github.com/drma-dev/VerusDate

if you can't reproduce, you can use my project

@ChristianWeyer
Copy link

Well, I can reproduce it, of course :-)
I was asking whether you found viable solutions to speed things up.
Thanks!

@javiercn
Copy link
Member

This is in part an artifact of how lighthouse measures LCP.

After we prerender we replace the prerendered contents with the interactive version of the DOM. Lighthouse uses that as the LCP. In reality the content is rendered much earlier (the prerendered content) but lighthouse decides that as we are replacing all the nodes (for the exact same hierarchy of nodes) the LCP is that event instead of the initial prerender.

If you look at the layout shift, you can see that it's 0, because we in fact don't change the visual aspect of the DOM, just the actual nodes after the app boots up.

@ChristianWeyer
Copy link

Yeah, in my Blazor WASM .NET 5 with prerendering demo here http://tt-cw-blazor-wasm-prerendering.azurewebsites.net/ we do observe exactly what you are describing @javiercn

image

(Source: https://github.com/thinktecture/blazor-wasm-things-to-know/tree/main/Prerendering)

@SQL-MisterMagoo
Copy link
Contributor

Hi - just wanted to leave a few notes here about how I recently achieved 100% across the board on Lighthouse.

This was an exercise in achieving 100% in the tests, not in having a perfect web site - however, I do think the performance score being achieved in this way is not just whimsy - my test site works perfectly for me this way - ymmv.

SEO 100%

The SEO test simply requires having a <meta name="description" content="some site description"/> in the head and a robots.txt file.

My robots.txt was :

# Group 1
User-agent: *
Allow: /

Best Practices 100%

I didn't need to do anything here - my Blazor WebAssembly application just passed with 100%.

Accessibility 100%

The requirements seem pretty slim for this, in my project I had to swap the standard Blazor <div id="app">...</div> for <main id="app">...</main> because I had a pretty bare-bones page without any "landmark" regions.
You could have other issues - and this does not mean the site is accessible, just that it scores 100 on this test.

Performance 100%

As noted earlier, Lighthouse doesn't like the way Blazor WebAssembly works, in fact the version on web.dev records errors when Blazor.start() is called - see the Compromise section below

For my site, it was acceptable for me to pre-render the home page, and then delay the Blazor startup like this

  <script src="_framework/blazor.webassembly.js" defer autostart="false"></script>
  <script>
	  document.addEventListener("DOMContentLoaded", function() {
		setTimeout(function() { Blazor.start(); },1200);
	});
  </script>

This has the effect of allowing Lighthouse to render/test the site without tripping over the Blazor load - but the end user has no idea because the home page is so entertaining/exciting 😜 they don't notice the delay.

Reading the MS docs and following the links - I found all these properties that I could use in my .csproj to reduce the size of my application dist.

    <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
    <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
    <DebuggerSupport>false</DebuggerSupport>
    <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
    <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
    <EventSourceSupport>false</EventSourceSupport>
    <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
    <InvariantGlobalization>true</InvariantGlobalization>
    <UseSystemResourceKeys>true</UseSystemResourceKeys>

Compromise

Delaying the startup of the Blazor WASM module only helps to score high in Lighthouse - so I'm not suggesting it is for everyone - but if you have reasons to need to show a good score but not cause too much delay, then maybe a small delay in startup of 200ms or so would be a compromise you could bear.

I have found my site scores around 95% for performance with only a 200ms delay in startup.

Sample Report For 200ms delay

image

Sample Report For 1200ms delay

image

Final Notes

I do not believe Brotli compression was used here - I've had trouble with that, so wasn't specifically enabling it - I think the Azure App Service is serving up gzipped files, just not Brotli.

Another point to note is that if your users have fast/stable internet - you might be better disabling compression altogether - as the time spent uncompressing the files can sometimes be longer than the time it would take to download the larger uncompressed files.

@javiercn @danroth27 Is there any impact on the streaming initialisation of wasm modules from serving them compressed?

@SteveSandersonMS
Copy link
Member

Another point to note is that if your users have fast/stable internet - you might be better disabling compression altogether - as the time spent uncompressing the files can sometimes be longer than the time it would take to download the larger uncompressed files.

Is this an estimate or did you have a scenario where it made a measurable difference? Do you have any timings to measure how much difference it makes?

Is there any impact on the streaming initialisation of wasm modules from serving them compressed?

No, streaming compilation still works with compression.

@SQL-MisterMagoo
Copy link
Contributor

Another point to note is that if your users have fast/stable internet - you might be better disabling compression altogether - as the time spent uncompressing the files can sometimes be longer than the time it would take to download the larger uncompressed files.

Is this an estimate or did you have a scenario where it made a measurable difference? Do you have any timings to measure how much difference it makes?

I experienced this in some testing, but it was not rigorous.

In my simple scenario, I disabled compression in the browser dev tools, cleared all caches and reloaded the site - and I was seeing time to blazor being fully loaded and interactive reduce by a factor of around 5 - but I have not re-visited that testing to be sure it wasn't environmental.

When I do, I'll report back.

@SQL-MisterMagoo
Copy link
Contributor

@SteveSandersonMS This is a .NET 6 Preview 4 AOT site hosted on Azure App Service - free tier in Europe.

Hosting this way may be relevant, but this is what I am seeing:

With browser default network settings and clean cache - 13 seconds

image

With compression disabled in the "network conditions" tab - 3 seconds

image

I can send you the site details via DM if you want - or @danroth27 already has access to the source and the URL.

@SteveSandersonMS
Copy link
Member

That is interesting. It's hard to believe that decompression takes ~10 seconds unless you have an extraordinarily slow CPU. I wonder what else might be going on here.

@SQL-MisterMagoo
Copy link
Contributor

Yea, I don't have a super-fast CPU, but it's not terrible : The yellow line indicates starting to load and everything to the right is while it was loading (within a second or so) - the 100% peak is when it was loading the WASM module I think - network traffic had just stopped at that point.

image

I've sent you a message on Twitter and sent an invite to the repo - if you want to look at it it's there - if not that's also fine - it's not a real-life project so there is no "problem" to solve - it's just interesting.

@SQL-MisterMagoo
Copy link
Contributor

Update on compression - it was Azure and the web.config that got deployed by Visual Studio's Publish.
Using the web.config here brought the load time for compressed files down to 1.69sec
https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/host-and-deploy/webassembly/_samples/web.config?raw=true

@andrew-tevent
Copy link

@SQL-MisterMagoo Did you figure out which part of the web.config was making the difference? i.e. what was it that was causing the client to perform so badly?

@SQL-MisterMagoo
Copy link
Contributor

@andrew-tevent The web.config that had been created by Visual Studio/Azure Publish did not have Brotli compression support I think - this is an example of the default it was producing

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <remove fileExtension=".blat" />
      <remove fileExtension=".dat" />
      <remove fileExtension=".dll" />
      <remove fileExtension=".json" />
      <remove fileExtension=".wasm" />
      <remove fileExtension=".woff" />
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".blat" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dll" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dat" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".json" mimeType="application/json" />
      <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
      <mimeMap fileExtension=".woff" mimeType="application/font-woff" />
      <mimeMap fileExtension=".woff2" mimeType="application/font-woff" />
    </staticContent>
    <httpCompression>
      <dynamicTypes>
        <add mimeType="application/octet-stream" enabled="true" />
        <add mimeType="application/wasm" enabled="true" />
      </dynamicTypes>
    </httpCompression>
    <rewrite>
      <rules>
        <rule name="Serve subdir">
          <match url=".*" />
          <action type="Rewrite" url="wwwroot\{R:0}" />
        </rule>
        <rule name="SPA fallback routing" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          </conditions>
          <action type="Rewrite" url="wwwroot\" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

And the example from Microsoft Docs, which improved things:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <remove fileExtension=".dll" />
      <remove fileExtension=".json" />
      <remove fileExtension=".woff" />
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".json" mimeType="application/json" />
      <mimeMap fileExtension=".woff" mimeType="font/woff" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
      <mimeMap fileExtension=".dat" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dll" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
      <mimeMap fileExtension=".blat" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".js.gz" mimeType="application/javascript" />
      <mimeMap fileExtension=".dat.gz" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dll.gz" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".json.gz" mimeType="application/json" />
      <mimeMap fileExtension=".wasm.gz" mimeType="application/wasm" />
      <mimeMap fileExtension=".blat.gz" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".html.gz" mimeType="text/html" />
      <mimeMap fileExtension=".css.gz" mimeType="text/css" />
      <mimeMap fileExtension=".ico.gz" mimeType="image/x-icon" />
      <mimeMap fileExtension=".svg.gz" mimeType="image/svg+xml" />
      <mimeMap fileExtension=".js.br" mimeType="application/javascript" />
      <mimeMap fileExtension=".dat.br" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".dll.br" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".json.br" mimeType="application/json" />
      <mimeMap fileExtension=".wasm.br" mimeType="application/wasm" />
      <mimeMap fileExtension=".blat.br" mimeType="application/octet-stream" />
      <mimeMap fileExtension=".html.br" mimeType="text/html" />
      <mimeMap fileExtension=".css.br" mimeType="text/css" />
      <mimeMap fileExtension=".ico.br" mimeType="image/x-icon" />
      <mimeMap fileExtension=".svg.br" mimeType="image/svg+xml" />
    </staticContent>
    <httpCompression>
      <dynamicTypes>
        <remove mimeType="text/*" />
        <remove mimeType="application/javascript" />
        <remove mimeType="image/svg+xml" />
      </dynamicTypes>
      <staticTypes>
        <remove mimeType="text/*" />
        <remove mimeType="application/javascript" />
        <remove mimeType="image/svg+xml" />
      </staticTypes>
    </httpCompression>
    <rewrite>
      <outboundRules rewriteBeforeCache="true">
        <rule name="Add Vary Accept-Encoding" preCondition="PreCompressedFile" enabled="true">
          <match serverVariable="RESPONSE_Vary" pattern=".*" />
          <action type="Rewrite" value="Accept-Encoding" />
        </rule>
        <rule name="Add Encoding Brotli" preCondition="PreCompressedBrotli" enabled="true" stopProcessing="true">
          <match serverVariable="RESPONSE_Content_Encoding" pattern=".*" />
          <action type="Rewrite" value="br" />
        </rule>
        <rule name="Add Encoding Gzip" preCondition="PreCompressedGzip" enabled="true" stopProcessing="true">
          <match serverVariable="RESPONSE_Content_Encoding" pattern=".*" />
          <action type="Rewrite" value="gzip" />
        </rule>
        <preConditions>
          <preCondition name="PreCompressedFile">
            <add input="{HTTP_URL}" pattern="\.(gz|br)$" />
          </preCondition>
            <preCondition name="PreCompressedBrotli">
            <add input="{HTTP_URL}" pattern="\.br$" />
          </preCondition>
          <preCondition name="PreCompressedGzip">
            <add input="{HTTP_URL}" pattern="\.gz$" />
          </preCondition>
        </preConditions>
      </outboundRules>
      <rules>
        <rule name="Serve subdir">
          <match url=".*" />
          <action type="Rewrite" url="wwwroot\{R:0}" />
        </rule>
        <rule name="Rewrite brotli file" stopProcessing="true">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTP_ACCEPT_ENCODING}" pattern="br" />
            <add input="{REQUEST_FILENAME}" pattern="\.(js|dat|dll|json|wasm|blat|htm|html|css|ico|svg)$" />
            <add input="{REQUEST_FILENAME}.br" matchType="IsFile" />
          </conditions>
          <action type="Rewrite" url="{R:1}.br" />
        </rule>
        <rule name="Rewrite gzip file" stopProcessing="true">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTP_ACCEPT_ENCODING}" pattern="gzip" />
            <add input="{REQUEST_FILENAME}" pattern="\.(js|dat|dll|json|wasm|blat|htm|html|css|ico|svg)$" />
            <add input="{REQUEST_FILENAME}.gz" matchType="IsFile" />
          </conditions>
          <action type="Rewrite" url="{R:1}.gz" />
        </rule>
        <rule name="SPA fallback routing" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          </conditions>
          <action type="Rewrite" url="wwwroot\" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

@andrew-tevent
Copy link

@SQL-MisterMagoo Thanks! So you think client was much slower because it was using gzip instead of brotli?

I saw the differences in the web.config - I was wondering if you knew specifically which bits fixed it?

I thought compression was enabled in IIS using the default web.config that Blazor creates? Certainly I see gzip encoding in the HTTP requests in the browser.

I wanted to understand which parts of the bigger web.config I needed - because there's rewrite rules which I don't really understand why they are needed (pretty sure I don't need the "Serve subdir" rule)

@andrew-tevent
Copy link

I tried the sample web.config and it broke my app. First because I was renaming the .dll to .bin files to get around firewall issues - which I fixed by removing the incorrect blazor.boot.json.gz/br files - but then I was getting errors with .js and .css; Felt like server was serving up compressed versions but not setting the Content Encoding header in the response?

@SQL-MisterMagoo
Copy link
Contributor

@andrew-tevent Sorry, no I haven't analysed any further - I just updated and it worked, so I was happy.

@glen-84
Copy link
Contributor

glen-84 commented Nov 20, 2023

With the latest template, for the home page:

Performance

Performance can be increased from 88 to 99, by:

SEO

SEO can be increased from 88 to 100, by:

  • Adding a meta description.
    <HeadContent>
        <meta name="description" content="Home - BlazorApp">
    </HeadContent>
  • Changing the close element from an anchor to a button.
    - <a class="dismiss">🗙</a>
    + <button type="button" class="dismiss">🗙</button>

I can send a PR if that helps.

@mkArtakMSFT mkArtakMSFT modified the milestones: Planning: WebUI, Backlog Dec 22, 2023
@ghost
Copy link

ghost commented Dec 22, 2023

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@mkArtakMSFT
Copy link
Member

Thanks for your suggestion, @glen-84.

@javiercn, I'm not certain about the desire to enable compression in the template. If you agree with this, I think we can accept a PR from @glen-84 with his proposed changes and actually try to bring it into 8.0 servicing. What do you think?

@ghost
Copy link

ghost commented Dec 22, 2023

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@jirisykora83
Copy link

servicing

If I remember it correctly in older version compression broke hot reload or debugger. I am not sure if it is case anymore.

@glen-84
Copy link
Contributor

glen-84 commented Dec 22, 2023

If I remember it correctly in older version compression broke hot reload or debugger. I am not sure if it is case anymore.

I mentioned this in my comment ...

This likely requires manually adding the browser refresh JavaScript reference.

@mkArtakMSFT
Copy link
Member

We've discussed this with the team and there are multiple work efforts that we have going that will improve the score here.
Those are already tracked in different issues.
As for this specific issue, we'll use this to track adding a metric to our perf infrastructure to monitor lighthouse score for our template so we can make sure we don't regress it as well as improving it with the planned changes.

@mkArtakMSFT mkArtakMSFT added the Priority:1 Work that is critical for the release, but we could probably ship without label Jan 4, 2024
@MackinnonBuck MackinnonBuck changed the title blazor + Lighthouse = very low performance [Blazor] Add lighthouse score to automated perf tests Jan 29, 2024
@danroth27 danroth27 removed the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Mar 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affected-medium This issue impacts approximately half of our customers area-blazor Includes: Blazor, Razor Components feature-blazor-boot-up feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-seo feature-templates Perf Pillar: New Blazor Priority:1 Work that is critical for the release, but we could probably ship without severity-minor This label is used by an internal tool
Projects
None yet
Development

Successfully merging a pull request may close this issue.