jsakamoto / BlazorWasmPreRendering.Build

When you publish your Blazor Wasm app, this package pre-renders and saves the app as static HTML files in your public folder.
https://jsakamoto.github.io/BlazorWasmPreRendering.Build/
Mozilla Public License 2.0
258 stars 15 forks source link

routing behaviour on published prerendered wasm project: not loading the prerendered page #24

Open dflorean opened 1 year ago

dflorean commented 1 year ago

Hello! this package is really interesting, sincerely thinking to become a contributor. I want to produce a static prerendered site at PUBLISH/BUILD time with this package, that could be deployed on a static site and no more needs to calls the API layer (blazor actually misses an automatic tool that is doing this, like the feature of Nuxt for VUE for example). I got the goal using as you mentioned in the documentation the PersistentComponentState service and it is working, but i'm facing a strange behaviour on routing on hrefs from the navlink menu (but all hrefs are affected): when clicked, it seems the app is trying to load the "non prerendered" page (the one calling the API)" and not the published page the package creates for example in the "fetchdata/index.html". Once you refresh the page the prerendered page is loaded correctly and you can see is totally static and prerendered tryng to refresh it few times in the fetchdata, the data is always the same cause is loaded from the PersistentComponentState. You can reproduce and see this behaviour at this link. https://staticspademo.z13.web.core.windows.net/ Am i missing something in the routing behaviour that i'm doing wrong?

jsakamoto commented 1 year ago

@dflorean Sorry too late since I was busy watching the .NET Conf 2022 that was held this week. (I live in Japan, so I had to wake up at midnight in the Japan Standard Time zone and keep staying up).

By the way, I'm not good at English at all, so it is a bit hard to understand the problem that you reported. Sorry for bothering you, but could you explain your problem again in more straightforward English? And also, I appreciate it if you disclose the source code of the site https://staticspademo.z13.web.core.windows.net/.

dflorean commented 1 year ago

@jsakamoto hello! yes i know i was following .NET conf too :) try to write in simple english: do this

it seems the first time you navigate to a page from the menu is not loaded the blazor page that uses PersistentComponentState but another one trying to reach the API endpoint i uploaded the demo code here https://github.com/dflorean/Daniflorex.StaticSpaHost

the main project is [Daniflorex.StaticSpaHost.Frontend]

[Daniflorex.StaticSpaHost.Backend] the API backend that SHOULD be called only during publish of the frontend (it hosts an instance of Umbraco CMS with sample data) [Daniflorex.StaticSpaHost.Shared] some simple shared models [Daniflorex.StaticSpaHost.SSR/Server] webapp i use to do hosted Prerendering tests

tried to be as clear as possible

thanks

jsakamoto commented 1 year ago

@dflorean Thank you for your kind explanation. I could understand what the problem is.

Unfortunately, the behavior that the data are not loaded inter pages navigation after a Blazor Wasm app is loaded on a browser is caused by the design of Blazor. That behavior happens from the structure and architecture of a Blazor Wasm, and from it's the pre-rendering system with states.

It is very hard for me to explain in more detail about that with my low English skill, but I believe you can understand eventually after spending time researching by yourself.

Fortunately, there is a possibility that we could take some hacks and resolve this issue. This is still a vague idea, so I can't say any specifications of this idea at this time. I'll investigate how to resolve this issue.

dflorean commented 1 year ago

@jsakamoto thank you! I think i might have an idea of what is happening, probably something involved in not loading the entire new page but only some parts of the page (it's still a single page application in the end). If you want some kind of help let me know, i am really interested in this package and to have some kind of static site generator for Blazor, would be really happy to contribute!

dflorean commented 1 year ago

@jsakamoto i investigated more and understood the problem. The client navigation router is not loading the entire page but only reloading the page component and in that moment the PersistentState is empty cause is designed to persist state when pausing and resuming the app and not during the first load from webassembly. Possible fixes:

The last could be a nice solution for your package maybe and a plus feature to have some near to a static generated site/offline experience, but ATM still unable to make it work.

i updated the demo site and the repo with these considerations

dflorean commented 1 year ago

I saw someone implemented a NavigationLocker for .NET 6 too, that could be a good solution indeed, i will try. But if u are planning to update the package to 7 this is useless.

EDIT: i tried to use the .NET 7 NavigationLock and it creates a loop cause NavigateTo method is itself triggering the onbeforeinternalnavigation event.

dflorean commented 1 year ago

@jsakamoto find the fix, it was stupid: with .NET 7 and NavigationLock i overridden the OnBeforeInternalNavigation as i already told, but the key to avoid redirection loop was check the context IsNavigationIntercepted property (indicates whether the navigation was initiated via code or via an HTML navigation, so is true when someone clicked a link and false when we forced a NavigateTo) NB obviously there is page refresh between pages i pushed the code on my demo repos and i'm deploying on the blob storage too.

Maybe we could integrate a fix like this in your package (need to update to .NET 7 but i see that added to a .NET 7 wasm app is working). Inject a NavigationLock in the App.razor and handle the OnBeforeInternalNavigation. Maybe configurable like other options for the package. I can do a pull request if you want but the project need before to be updated

Let me know!

dflorean commented 1 year ago

@jsakamoto i forked and have a look to the project. Could you give me help to the best way to add this feature? i was looking in the BlazorWasmPreRendering.Build.WebHost in the _Layout (where you conditionally add too), but i have problems with multitargeting framework cause this would be only a .NET 7 feature and don't know how to dynamically add the NavigationLock with the event handler

jsakamoto commented 1 year ago

@dflorean Thank you for your contributions!๐Ÿ‘ But unfortunately, your approach is not applicable for general use cases of the "BlazorWasmPreRendering.Build". Please settle down, please.๐Ÿ˜…

I agree that your approach will be a good solution for your use case. However, your approach will make a Blazor WebAssembly app behave like a traditional Multi Page Application. But most users don't desire such behavior โ€• make every page navigation cause hard loading โ€• for the "BlazorWasmPreRendering.Build". The purpose of the "BlazorWasmPreRendering.Build" is not Static Site Generation. The purpose is prerendering for Single Page App.

Instead, you can resolve your problem in your app itself, independent of the "BlazorWasmPreRendering.Build". If you want to use NavigationLock to implement the behavior you need, please do that in your app, such as App.razor or so on. There is no need to inject an additional process to the _Layout.cshtml of the "BlazorWasmPreRendering.Build".

Moreover, if your app doesn't have any dynamic user interactions like the "Counter" page, there is no need to hack like you are trying. Just comment out the <script src="_framework/blazor.webassembly.js"></script>. Then, the site will behave like a traditional Multi Page Application site. When users click a NavLink on that site, it will cause load the next page's content that is static prerendered from the web server every time because there is no runtime process on the page.

But I have another idea that might be better than the above options. I'd like to show you my idea, but it will take some time. Please give me a few days.

Organize the issue.

Hint: The data persisted to the component state store has been embedded as HTML text in each static prerendered HTML file deployed on the release site web server.

dflorean commented 1 year ago

@jsakamoto sorry you are right, don't worry take your time๐Ÿ˜Š! For me this is only something from what i'm trying to create a POC of a static generated site, the goal is demonstrate the utility of an architecture where you could only have one very little Backend/API machine (AppService or so) that can be always sleeping except when you want to update and publish some new contents (for example for a big contents only static site), then trigger a pipeline when publishing (DevOps or GH Actions) that build and automatically deploy the static site to a static host (github pages or azure static for example). This way you could have a very fast website with a CMS backoffice at very little (or almost nothing in some cases) hosting costs. I've been able to do this with angular/gridsome and vue/nuxt.js, it would be great to have this possibility with blazor too. I know this isn't the goal of this package, sorry ๐Ÿ˜”

and thank you for the help!

About the hint: yeah i know, i understand that we miss the persisted data HTML of components of other pages in current page and is the reason why the components can't find them and are trying to retrieve from the API endpoints. Probably needing to persist all the prerender data from all the components in the current page when building.

jsakamoto commented 1 year ago

@dflorean I created a sample project to represent one of the solutions to resolve the problem discussed in this issue thread. The sample project is in the following GitHub public repository.

https://github.com/sample-by-jsakamoto/Blazor-Reuse-ComponentStates-From-PreRenderedHTML

Would you like to check it out?

P.S. that is just one of the solutions. I also have other ideas, but another time.

dflorean commented 1 year ago

@jsakamoto very smart approach! You wrapped the PCS in a small service that is retrieving the persisted value fetching the pages prerendered from your package and matching/decoding the HTML prerendered blazor fragment for the page component.

Gonna try this out in these days and get a feedback back but i'm for sure liking the solution