-
Notifications
You must be signed in to change notification settings - Fork 10k
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
Improve the load time performance of Blazor WebAssembly apps on low-end mobile devices #42284
Comments
Some more examples of this feedback: https://www.reddit.com/r/Blazor/comments/iorby5/why_is_blazor_loading_so_long/ |
Another report of 15s load time on mobile in this issue: #41909 |
I experience this as well on my oldest iPad (5th gen) - refreshing is still slow (~5-7s of pure loading), so caching is not helping. Nothing fancy in the app. How can we help profile this? |
Hi Team, I have a .Net 6 Blazor WASM app with Server Hosted Code. Before Publishing the APP I have added a few Config lines in the project file like this:-
and Added the Brotli Compression Decoce.js file and some code on the Index.html Page. Now my APP downloads only 1.5 MB of resources and Its loads into 1.5 Secs. SO I have Optimized my app using these settings. If I am anything missing Please suggest me. Thanks |
@mandeep-sps The problem occurs on older devices. Use throttling and do more tests |
For my Blazor WASM app initial loading is taking 5-10 seconds which is bad enough but on click events from buttons are taking around 2 seconds to fire when accessing the site from a mobile device and often not firing at all resulting in a user having to repeatedly press buttons to get something to happen (doesn't happen from a PC web browser) which is obviously making the app extremely sluggish to use. I would understand if these buttons were submitting data to the server, but when they only trigger a modal to pop up with everything happening locally to the client, I would expect this to be a lot faster. |
@ScottKane For the slow event firing issues your seeing, could you please open a separate issue with the specs of the devices where you see this problem including the browser version and a repro project that demonstrates the performance issue? |
@danroth27 is the delay of about a second between Blazor assemblies downloading and the app starting its function normal? (see above: #42284 (comment)) |
@danroth27 I opened #43090 for this, I'm currently rebuilding the client from scratch to figure at at what point the performance dies. So far the only thing I can point to causing it is |
A lot of complaints are about the initial start speed because of several MBs of runtime DLLs that must be first downloaded while on slow mobile networks. Is there any technical difficulty that prevents rendering the page on the server and sent to the device while DLLs are loading in the background? And seamlessly switch from server-generated-client-side to client-side without specifically implementing server-side? Such a hybrid solution would solve the main issue of the Blazor. |
A large contributor is #35302 . |
@Shalxxx Blazor already supports prerendering from the server.
This is something we hope to explore for .NET 8: #38128 |
@danroth27 Doens't solve. The page become unresponsive until the client completely load the big bloated binaries. |
@awillSoftwares You're correct that prerendering only improves the perceived load time of the page. We are discussing how we might in the future enable a combined model where the app starts off with Blazor Server and then transitions to Blazor WebAssembly after the app has been downloaded. Prerendering with authentication is also something we're working on for .NET 7: #27592. |
@danroth27 Can you also consider what happens if user device/browser does not support WebAssembly. Currently the User-Agent header appears to be the only way to detect device details which is pretty 'wild'. It would be nice to have some property or method such as |
@cirrusone most current browsers support WebAssembly, but you won't be able to detect it reliably on the server side. On the client, it's basically as easy as testing for the existence of the window global variable There are some instances when WebAssembly itself is supported, but not enabled. The Edge browser does disable WebAssembly support in some cases when using "Enhanced security", or a group policy might disable it for a company. It essentially means checking for the same thing as above. |
I have same issue in .net 6 blazor wasm on low-level(desktop) devices |
@sajjadarashhh Hey Please try to Add Brotli Compressresion into your WASM Project. I am sharing the code Below. <script type="module"> import { BrotliDecode } from './decode.js'; Blazor.start({ loadBootResource: function (type, name, defaultUri, integrity) { if (type !== 'dotnetjs' && location.hostname !== 'localhost') { return (async function () { const response = await fetch(defaultUri + '.br', { cache: 'no-cache' }); if (!response.ok) { throw new Error(response.statusText); } const originalResponseBuffer = await response.arrayBuffer(); const originalResponseArray = new Int8Array(originalResponseBuffer); const decompressedResponseArray = BrotliDecode(originalResponseArray); const contentType = type === 'dotnetwasm' ? 'application/wasm' : 'application/octet-stream'; return new Response(decompressedResponseArray, { headers: { 'content-type': contentType } }); })(); } } }); </script>
|
@mandeep-sps |
|
@yugabe @jirisykora83 I have added the Brotli Compression into the WASM App. It's working fine at my end. BR applied to all the DLLs files and as well as to Static Files. |
I check your site and you are right i load slowly even at second load (where most of the dll should be cached). That dont have to be low-end mobile it is even on my desktop. Hard to say when currently there isnt easy way to profile WASM app. note: Server location seems to be india (?) and my location is EU so "some" latency is expected. |
The switching model in runtime will be great since we take all advantages of both models. |
Blazor's performance always slow,never fast 😂 |
Where developers can see the future features of Blazor, that are planning to be present in .Net 9? |
@antonKharchenko1997 you can see blazor .net 9 planning issues here |
There are 246 .wasm files In my non-trivial blazor webassembly project. Startup time in mobile device 3-5 seconds. May be will be useful to merge .wasm (.dll) files like ILMerge to improve load time performance? Edit. First load without cache on mobile device: 11 sec. Net8 |
@megafetis That's a lot of assemblies! Can you confirm that you're reporting the number after the app is published and with the .NET trimmer enabled? Have you looked at reducing the number of dependencies, particularly dependencies on libraries that don't support trimming? |
In my projects I've got around 55-60 dependencies with no 3th party deps. So yes. 246 are really a very huge amount of deps. And in my opinion the amount of deps is not the actual problem. It's the boot-time from the whole runtime thats unfortunately slow. 😢 |
@markus Is this just an opinion, or does it reflect an actual measurement? For example, how long does your app take to load the first time before anything is cached versus the second time when the app is already cached? Also, can you try with .NET 9 and see if the load time is significantly improved? We've already done quite a bit of work in .NET 9 the improve the runtime & Blazor startup performance. |
@danroth27 It's not only an opinion. I try really hard to make the app as performant as possible, but if I compare it with an JS approach it is very slow (but not that ugly to write 😄) Off topic: I don't understand why the user has to load something from the TestPlatform. I don't have any references to that. On subsequent runs it takes around 11 seconds with 167kB of data. And that is my problem. If I rebuild that with JS and no PWA it takes about 1.6 seconds. The results are taken from the latest .NET 8 and I didn't test it with .NET 9 because it is not that straightforward to install .NET 9 within WSL2. Disclaimer: Low-End device means: Wired Network, 8GB Ram, Intel Pentium J2900 2.41 GHz Processor with Windows 10. Server.csproj: <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot Condition="'$(Configuration)'=='Release'">true</PublishAot>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Client\Client.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" />
</ItemGroup>
</Project> Client.csproj: <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishTrimmed>true</PublishTrimmed> <!-- breaks hot reload if true -->
<DebuggerSupport Condition="'$(Configuration)'=='Release'">false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dark.Component" Version="2024.6.2.305" />
<PackageReference Include="Dark.CsharpExtension" Version="2024.6.10.69" />
<PackageReference Include="Dark.EventSourcing.Client" Version="2024.6.2.330" />
<PackageReference Include="Markdig" Version="0.37.0" />
<PackageReference Include="MenuPlanner.Shared" Version="2024.6.10.143" />
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<Watch Include="wwwroot\**\*.*" />
</ItemGroup>
</Project> |
Hello @danroth27. I am glad, you answer. I am with blazor since netcore 3.0 preview!
|
Yes. I Achieved 42 points from 21 in Google pagespeed. First load on mobile device is about 11 seconds with many optimizing technics. |
@MarkusRodler That does seem concerning. Can you tell us what's in that 167kB of data that's still being downloaded after the app was cached? Also, how are you making the load time measurements? Are you just using the browser dev tools? Note that having the browser dev tools open will significantly slow things down. Even so, we'd still like to close the gap between Blazor and JS.
Here's how we think you can install and try out .NET 9: https://learn.microsoft.com/dotnet/core/install/linux-debian#install-preview-versions.
What browser and browser version are you using for testing?
This dependency on Dark.CsharpExtension seems to pull in MSTest.Framework. Are you sure you want all that on the client? |
@megafetis I'm not sure how much this will help if you have a modern HTTP/2 connection. It would be interesting though if someone wants to do some experiments with this and see if it makes a significant difference. |
Well that was my fault. The 167kB came from the Adblock extension. I disabled all of them and now nothing is transferred. In this run it took 11.4 seconds. So in the end it doesn't make any difference in load time performance.
Yes with dev tools open. But I also had it open for the JS-only variant ;-)
I tried it with .NET 8 previews back then but I didn't managed to do it. In the end I waited for the release and used the package manager.
MS Edge of course 😄 Version: 126.0.2592.56
No I don't want the test framework on the client. But I thought that would be stripped away through trimming? |
@MarkusRodler Blazor by default uses the partial trim mode, which means that only assemblies that are marked as trimmable will get trimmed. It's likely that Dark.CsharpExtension hasn't been set up to support trimming yet. More details on authoring trimmable libraries and trimming options here: |
Oh it is set up for trimming. Here is the csproj: <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>Dark.CsharpExtension</PackageId>
<Authors>Markus Rodler</Authors>
<Company>Dark</Company>
<PackageDescription>This package extends C# projects with various improvements</PackageDescription>
<RepositoryUrl>https://github.com/MarkusRodler/Dark.CsharpExtension</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>LGPL-2.1-only</PackageLicenseExpression>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
</ItemGroup>
<ItemGroup>
<None Include="../README.md" pack="true" PackagePath="." />
</ItemGroup>
</Project> |
@MarkusRodler That will enable Dark.CsharpExtension to be trimmed, but it won't help for MSTest.TestFramework, which I believe isn't trimmable. The partial trim mode will unfortunately keep assemblies that aren't marked as trimmable, even if they're a dependency of a trimmable assembly. |
Ok I don't like that behaviour. But nevertheless, even without that dependency the bootstrap is really slow. I don't know what needs to be recalculated every time the app starts. |
Yeah, it surprised me initially as well and I spent a bunch of time talking to the .NET trimmer folks talking about it. The partial trim mode is intentionally conservative. We'd like to enable support for full trimming in Blazor, but we need to remove a bunch of reflection logic first.
We found in our .NET 9 startup perf investigations that a bunch of time is spent in JSON deserialization, so we switched to using the source generated APIs for that. We've also done a bunch of work on the runtime itself to optimize startup. It really would be helpful if you could try out the latest .NET 9 preview and let us know how much those changes improve your scenario. We'd be happy to help you work through any issues getting .NET 9 set up in your environment 🥺. |
@danroth27 Ok I thought I could try it the "easy" way and just update the csproj-files and use the newer docker images (a link to all of the available tags would be cool in docker hub). <TargetFramework>net9.0</TargetFramework>
<LangVersion>13.0</LangVersion>
...
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0-preview.5.24306.11" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0-preview.5.24306.11" PrivateAssets="all" /> Server <TargetFramework>net9.0</TargetFramework>
<LangVersion>13.0</LangVersion>
...
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0-preview.5.24306.11" /> Dockerfile But it doesn't work: #12 1.146 Determining projects to restore...
#12 7.107 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NET.ILLink.Tasks with version (>= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
#12 7.107 /app/Client/Client.csproj : error NU1102: - Found 25 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
#12 7.107 /app/Client/Client.csproj : error NU1102: - Found 0 version(s) in github [/app/Server/Server.csproj]
#12 7.107 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.DotNet.ILCompiler with version (>= 9.0.0-preview.6.24307.2)
#12 7.107 /app/Server/Server.csproj : error NU1102: - Found 49 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.107 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
#12 7.108 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.NET.ILLink.Tasks with version (>= 9.0.0-preview.6.24307.2)
#12 7.108 /app/Server/Server.csproj : error NU1102: - Found 25 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.108 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
#12 7.108 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NET.Sdk.WebAssembly.Pack with version (>= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
#12 7.108 /app/Client/Client.csproj : error NU1102: - Found 18 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
#12 7.108 /app/Client/Client.csproj : error NU1102: - Found 0 version(s) in github [/app/Server/Server.csproj]
#12 7.115 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Runtime.linux-x64 with version (= 9.0.0-preview.6.24307.2)
#12 7.115 /app/Server/Server.csproj : error NU1102: - Found 164 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.115 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
#12 7.115 /app/Server/Server.csproj : error NU1102: Unable to find package runtime.linux-x64.Microsoft.DotNet.ILCompiler with version (= 9.0.0-preview.6.24307.2)
#12 7.115 /app/Server/Server.csproj : error NU1102: - Found 49 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
#12 7.115 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
#12 7.115 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.AspNetCore.App.Runtime.linux-x64 with version (= 9.0.0-preview.6.24309.2)
#12 7.115 /app/Server/Server.csproj : error NU1102: - Found 164 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.11 ]
#12 7.115 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
#12 7.115 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Runtime.Mono.browser-wasm with version (= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
#12 7.115 /app/Client/Client.csproj : error NU1102: - Found 89 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
#12 7.115 /app/Client/Client.csproj : error NU1102: - Found 0 version(s) in github [/app/Server/Server.csproj]
#12 7.350 Failed to restore /app/Server/Server.csproj (in 5.62 sec).
#12 7.350 Failed to restore /app/Client/Client.csproj (in 5.62 sec).
#12 ERROR: process "/bin/sh -c dotnet publish Server --output /app/publish --runtime linux-x64 --self-contained true /p:DebugType=None /p:DebugSymbols=false" did not complete successfully: exit code: 1
------
> [publish 4/5] RUN --mount=type=cache,target=/root/.nuget --mount=type=cache,target=/app/artifacts dotnet publish Server --output /app/publish --runtime linux-x64 --self-contained true /p:DebugType=None /p:DebugSymbols=false:
7.115 /app/Server/Server.csproj : error NU1102: - Found 49 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ]
7.115 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
7.115 /app/Server/Server.csproj : error NU1102: Unable to find package Microsoft.AspNetCore.App.Runtime.linux-x64 with version (= 9.0.0-preview.6.24309.2)
7.115 /app/Server/Server.csproj : error NU1102: - Found 164 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.11 ]
7.115 /app/Server/Server.csproj : error NU1102: - Found 0 version(s) in github
7.115 /app/Client/Client.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Runtime.Mono.browser-wasm with version (= 9.0.0-preview.6.24307.2) [/app/Server/Server.csproj]
7.115 /app/Client/Client.csproj : error NU1102: - Found 89 version(s) in nuget.org [ Nearest version: 9.0.0-preview.5.24306.7 ] [/app/Server/Server.csproj]
7.115 /app/Client/Client.csproj : error NU1102: - Found 0 version(s) in github [/app/Server/Server.csproj]
7.350 Failed to restore /app/Server/Server.csproj (in 5.62 sec).
7.350 Failed to restore /app/Client/Client.csproj (in 5.62 sec). |
@MarkusRodler That looks like a .NET 9 Preview 6 container, which hasn't shipped yet. Does mcr.microsoft.com/dotnet/nightly/runtime-deps:9.0.0-preview.5-noble-chiseled-aot work? |
Otherwise, you need a nuget.config with a .NET 9 entry, like: https://github.com/dotnet/dotnet-docker/blob/main/samples/releasesapi/nuget.config#L4 |
On this page it looks like it is shipped: https://mcr.microsoft.com/en-us/product/dotnet/nightly/runtime-deps/tags
That worked. I only had to switch to dotnet format instead of dotnet-format and Ok now I deployed the new version. "The results are back": Around 8 seconds for the bootstrap. I like that it improves and shaves off about 3 seconds. But there is still room for improvement. 😅 |
Thanks @MarkusRodler for trying that out! It sounds like we do have some more work to do to improve the startup performance 🤔. To help us understand what's happening during those 8 seconds could you collect a browser performance trace for us? To do this, you'll need to first set |
@danroth27 I tried setting that flag but to compile it needs wasm-tools. So I tried installing them, but it fails: #12 [publish 4/6] RUN dotnet workload restore
#12 1.062 Installing workloads: wasm-tools
#12 1.094
#12 1.738 Skipping NuGet package signature verification.
#12 1.858 Installing workload manifest microsoft.net.sdk.android version 34.99.0-preview.6.339...
#12 1.953 Installing workload manifest microsoft.net.sdk.ios version 17.2.9093-net9-p1...
#12 2.045 Installing workload manifest microsoft.net.sdk.maccatalyst version 17.2.9093-net9-p1...
#12 2.[153](https://github.com/MarkusRodler/dark-menuplanner-site/actions/runs/9675432056/job/26692973017#step:4:154) Installing workload manifest microsoft.net.sdk.macos version 14.2.9093-net9-p1...
#12 2.267 Installing workload manifest microsoft.net.sdk.maui version 9.0.0-preview.1.9978...
#12 2.358 Installing workload manifest microsoft.net.sdk.tvos version 17.2.9093-net9-p1...
#12 2.453 Installing workload manifest microsoft.net.workload.mono.toolchain.current version 9.0.0-preview.6.24324.3...
#12 2.563 Installing workload manifest microsoft.net.workload.mono.toolchain.net6 version 9.0.0-preview.6.24324.3...
#12 2.672 Installing workload manifest microsoft.net.workload.mono.toolchain.net7 version 9.0.0-preview.6.24324.3...
#12 2.785 Installing workload manifest microsoft.net.workload.mono.toolchain.net8 version 9.0.0-preview.6.24324.3...
#12 2.896 Installing workload manifest microsoft.net.sdk.aspire version 9.0.0-preview.4.24229.9...
#12 3.018 Workload installation failed. Rolling back installed packs...
#12 3.022 Installation rollback failed: Workload manifest dependency 'Microsoft.NET.Workload.Emscripten.Current' version '9.0.0-preview.6.24319.1' is lower than version '9.0.0-preview.7.24319.4' required by manifest 'microsoft.net.workload.mono.toolchain.current' [/usr/share/dotnet/sdk-manifests/9.0.100-preview.6/microsoft.net.workload.mono.toolchain.current/9.0.0-preview.6.24324.3/WorkloadManifest.json]
#12 3.025 Workload installation failed: Workload manifest dependency 'Microsoft.NET.Workload.Emscripten.Current' version '9.0.0-preview.6.24319.1' is lower than version '9.0.0-preview.7.24319.4' required by manifest 'microsoft.net.workload.mono.toolchain.current' [/usr/share/dotnet/sdk-manifests/9.0.100-preview.6/microsoft.net.workload.mono.toolchain.current/9.0.0-preview.6.24324.3/WorkloadManifest.json]
#12 ERROR: process "/bin/sh -c dotnet workload restore" did not complete successfully: exit code: 1 |
We've been hearing reports from users that load performance of Blazor WebAssembly apps on low-end mobile devices can be very slow. For example, this user on Twitter reported "bootstrapping is very slow on low-end mobile devices (~10s), caching doesn't help this". We should investigate where the time is being spent on these devices and evaluate options for improvement.
The first step is to collect some performance profiles. Any help is appreciated! 🙏
The text was updated successfully, but these errors were encountered: