Skip to content

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

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

Open
dflorean opened this issue Nov 8, 2022 · 12 comments

Comments

@dflorean
Copy link

dflorean commented Nov 8, 2022

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
Copy link
Owner

@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
Copy link
Author

dflorean commented Nov 14, 2022

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

  • go to the link
  • see the homepage data are loaded correctly (title "Test Home" and text "lorem ipsum dolor"
  • navigate from left NavMenu to page fetchdata
  • see the data are NOT loaded correctly, page stuck on loading (cause API for data is not reachable)
  • refresh the page and see the data are now loaded (they are correctly taken from PersistentComponentState where i put them)
  • you can do this again for the homepage. Navigate to homepage from the menu
  • see the data you see in the second point (Test home title and lorem ipsum text) are NOT loaded correctly, the page is stuck at loading
  • refresh this page too and the data are loaded correctly from PersistentComponentState

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
Copy link
Owner

@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
Copy link
Author

@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
Copy link
Author

dflorean commented Nov 15, 2022

@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:

  • keep the state in a cascadeparameter service that is persisted and shared between components (but could have to store a lot of data in some scenarios) and still problem in the component first loads
  • override default behaviour of navlink and make it do a NavigateTo with forceload of page. This is working and i updated the website so you can try, but maybe an ugly solution. This can be done at App component level intercepting the LocationChanged event and is working, but there is a first flash load of the page before the correct navigation
  • override on App Router OnNavigationAsync and NavigateTo page, but i couldn't publish with your package with this solution, is throwing exception
  • use new .NET 7 NavigationLock component to prevent default navigation and NavigateTo page (probably this is the best way?)

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
Copy link
Author

dflorean commented Nov 15, 2022

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
Copy link
Author

dflorean commented Nov 16, 2022

@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
Copy link
Author

@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
Copy link
Owner

@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.

  • The API endpoints are available during the Blazor Wasm app is prerendering.
  • In the prerendering process, the app fetches the data from those endpoints and renders them as HTML, and stores the data in the component state store (that is part of HTLM output).
  • Static rendered contents are deployed on a release site.
  • But, the API endpoints are no longer available from the user browser.
  • When the first page is loading, the Blazor WebAssembly app that was loaded on the user's browser can render from persisted data in the component state that is embedded in the HTML.
  • Next, when the user navigates to other pages, the Blazor WebAssembly app that is running on the browser tries to render the page user routed, but there is no persisted state on the app's memory. So the app tries to fetch the data from the API endpoints.
  • However, the API endpoints can not be reachable from the user's browser. So the user will see the error that the HttpClient can not access the API endpoints.

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
Copy link
Author

dflorean commented Nov 17, 2022

@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
Copy link
Owner

@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
Copy link
Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants