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

Cannot find a way to achieve a smooth transition to client app #1184

Closed
UserGalileo opened this issue Jun 26, 2019 · 38 comments
Closed

Cannot find a way to achieve a smooth transition to client app #1184

UserGalileo opened this issue Jun 26, 2019 · 38 comments
Labels
need: repro steps We cannot reproduce the issue with the information given

Comments

@UserGalileo
Copy link

UserGalileo commented Jun 26, 2019

Hi everybody!

I've spent some hours on this platform hoping to find a solution to my main problem with Universal (and SSR in general), I've read a lot of issues but I just can't find a solution. This post may seem a duplicate of many others, but I want to take a different approach: I'd like to ask some questions, hoping to get some useful info from this issue, and hoping it won't be closed and marked as duplicate because it's not my intention to duplicate issues. If what I describe here is the desired behavior of Universal or it's something that you're dealing with right now, perfect, I'm not trying to change things or report bugs.

From what I know:

  • Angular Universal makes no attempt to re-use the server generated HTML when it comes to bootstrapping the client app
  • Rehydration is a difficult topic and it may be really hard to achieve, even in the future
  • Therefore, when the app bootstraps on client, existing HTML is discarded, the DOM tree is recreated from zero, and all of the in-page components (and services) are instantiated again

This behavior causes a slight flicker, because the whole body is discarded (leading to a blank page) and re-rendered (leading to the "old" server-generated page, which is in reality the client app). There are a large number of issues about this "flicker", but it appears that many "flickering issues" may be caused by CSS being re-appended to DOM after the transition. This is not what I'm talking about as I don't have any problems with styles. The "flicker" I'm referring to is the HTML being re-rendered.

The best case scenario is that we have a simple page with a relatively easy DOM being rendered. In this case, the flicker is almost unnoticeable.
In my case, my view is a bit complex. The rendering process can take some time because we are talking about tens of items, with subitems, and a lot of css. In my case, this flicker is unbearable, it just takes about 2 seconds for the page to become blank and back again. I'm just testing it with my computer, I don't even want to think about mobile devices :D

This behavior is especially noticeable with lazy routes. If you try the universal-starter, and if you navigate to the "lazy" route, put Chrome in "Slow 3g" mode and refresh the page clearing the cache, you can clearly see the problem. The same problem is unnoticeable in the main route (maybe because it's simpler? Or maybe it's because lazy routes make this flicker last longer?).

This is my problem. My page is not huge, but it's "slightly complex" and this flicker makes the Universal app unusable. I ask you: what are my options? Am I thinking/doing something wrong? I'm seriously thinking about using a loading spinner to hide the page until the client app has bootstrapped, but it's my last option since my app will be useful for SEO purposes but the user won't see anything in the meantime.

I have also tried using preboot (just the default configuration) which says that it helps to make the transition smoother, but nothing happened.
I asked myself if it could be possible to make the client-app bootstrap in a hidden div and wait for it to be rendered before destroying the static HTML, but I didn't manage to get it right and I'm unsure if it would solve the problem.

Thank you so much for your patience and I hope this issue can make things clear for other people too, and hopefully solve their problems :)

@CaerusKaru
Copy link
Member

I asked myself if it could be possible to make the client-app bootstrap in a hidden div and wait for it to be rendered before destroying the static HTML, but I didn't manage to get it right and I'm unsure if it would solve the problem.

This is essentially what preboot does (when it works).

I'm assuming you've also tried enabling initialNavigation in your RouterModule?

Beyond that you likely need a creative approach, and a good place to find that is on Gitter or StackOverflow.

The problem here is that this is a known quirk of Universal right now, as we wait for hydration in the post-Ivy world. But it's not exactly an "issue" since there are known methods for overcoming this, even with apps of complex sizes and shapes. I wish I could offer more, but again this is not exactly the best place for support of non-issues and non-feature requests.

@UserGalileo
Copy link
Author

UserGalileo commented Jun 27, 2019

@CaerusKaru Thank you for your reply!

I'm assuming you've also tried enabling initialNavigation in your RouterModule?

Yes I've already tried, but I haven't tried it in combination with Preboot, thank you for reminding me. I assumed it was a useful solution for CSS purposes, I didn't think it would have an impact with Preboot, is this the case?

I'm not using StackOverflow or other platforms because first of all I think having the opinion of members/collaborators directly would have been better (and faster) since this is maybe THE main "issue" a lot of people have with Universal (from my point of view, obviously). I'm a consultant and this is a recurring topic in every team I've seen which uses Universal. You said that:

it's not exactly an "issue" since there are known methods for overcoming this, even with apps of complex sizes and shapes

What are you referring to? Which known methods are you talking about? Don't you think that since this is a quirk of Universal we could be more clear about what these methods are? I think it would really really help a lot of people :)

We've made big steps forward with developer experience in many points, also the official Angular documentation page about Universal was improved, but I feel that the docs are missing something, some guidance, some "Ok, you learned how these packages work, now these are your options if you're having these problems", something like the the "gotchas" that we already have a page for. It's not a "StackOverflow type of guidance" I'm talking about, I don't know if I was clear about this slight difference. It's more about best practices and real-world recurring problems and scenarios.

Just as an example, a lot of people think that now that we have TransferHttpCacheModule we don't need to use TransferState anymore, but it's absolutely not true because it's not an alternative and it solves just one particular problem: repeated http calls. Again I'm not saying docs are wrong, just that maybe they miss that common thread that would help more. Another example: personally it wasn't easy for me to understand how I could use Preboot in combination with Universal, thankfully I found that package mentioned in some issues and that made me realize that the package was linked in your README with a one-liner. It was not clear to me that Preboot is practically necessary in a real-world scenario if you want to overcome some of the limitations/quirks of Universal.

I hope I haven't bored you with this wall of text! What do you think about it? Could we improve the docs and make more clear which are the best practices and which are the "known methods" for overcoming these issues? I'd be glad to help too, if you need it.

PS. Also, I forgot about lazy routes: could it be that using lazy loading causes the blank page to last longer? If this is the case, is there a way to make Angular load the lazy module and THEN rebuild the DOM? Or maybe this is already happening and my eyes are wrong? :)

@vikerman
Copy link
Contributor

vikerman commented Jul 4, 2019

setting initialNavigation to 'enabled' basically does what you described towards the end - Wait for lazy loaded route to be ready before swapping in the client rendered DOM so as to avoid a flicker due to empty router-outlet in the lazy loaded route.

@UserGalileo
Copy link
Author

@vikerman Thanks, I'll try again hoping something chages, I'm sure I've tried it not so long ago in the same project and nothing seemed to change, I'll pay more attention this time :)

What do you think about making a little effort in order to have a better documentation for things like these ones? Again, "initialNavigation" is one of those things that you find only through issues, even the official Angular docs only say "Disables the initial navigation", nothing more.

I'd be really glad to help, It would be great if we could gather some people and have some sort of brainstorming to identify common problems and solutions :)

@timadevelop
Copy link

Same issue here.
Everybody is talking about initialNavigation, StateTransfer and TransferHttpCacheModule.
These things do hide the problem @UserGalileo talks about in some cases, but they don't solve it.

The problem is almost unnoticeable on pages with ~500 DOM elements - it takes 100-200 milliseconds for Angular to re-render them.
But assume we have ~1.2-1.5k DOM elements on our page (a list of products or posts with votes, tags, location, author name, price, etc). I'm working on Asus ROG G750 and still waiting 1.5-2s last dom element to be replaced.

But it's not exactly an "issue" since there are known methods for overcoming this, even with apps of complex sizes and shapes.

@CaerusKaru, could you provide some links or keywords to these methods, please?

Here are some related issues I've been reading the last two days:

P.s. I use initialNavigation: 'enabled' + TransferHttpCacheModule + a bunch of lazy loaded modules - it works best for me, but I still can't eliminate the problem @UserGalileo talks about.

@timadevelop
Copy link

timadevelop commented Jul 7, 2019

So I tried to use @angular/preboot with enabled initialNavigation and it works better now, at least I can't see DOM re-rendering.

Here is a commit I made to fix flickering using preboot: timadevelop/servicescope-web-client@efb38ae

Try it out and give me some feedback, please.

The only issue I have now is a mobile view:
I use a simple service in my angular app for detecting if a target device is a desktop or mobile (using it to remove desktop-specific DOM nodes on mobile and vice-versa). And unfortunately, my service re-evaluates target device only after 1 or 2 seconds after the first view was received from SSR.

 // app.routing
 imports: [RouterModule.forRoot(routes, {
	// ...
	initialNavigation: 'enabled'
 })],
 // app.module
 imports: [
	// ...
	PrebootModule.withConfig({ appRoot: 'app-root' })
 ],
 // ...
 providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: function(document: HTMLDocument, platformId: Object): Function {
        return () => {
          if (isPlatformBrowser(platformId)) {
            const dom = ɵgetDOM();
            const styles: any[] = Array.prototype.slice.apply(dom.querySelectorAll(document, `style[ng-transition]`));
            styles.forEach(el => {
              // Remove ng-transition attribute to prevent Angular appInitializerFactory
              // to remove server styles before preboot complete
              el.removeAttribute('ng-transition');
            });
            document.addEventListener('PrebootComplete', () => {
              // After preboot complete, remove the server scripts
              setTimeout(() => styles.forEach(el => dom.remove(el)));
            });
          }
        };
      },
      deps: [DOCUMENT, PLATFORM_ID],
      multi: true
    },

@UserGalileo
Copy link
Author

I tried with initialNavigation: 'enabled' with Preboot and it seems like this solved the problem (or part of it). I still see a bit of a flicker but it's far less noticeable than before.

The problem I have with initialNavigation, is that now, since the initial navigation is performed before the main component is bootstrapped, a lot of my router-related observables (ex. Router.events) stopped working because they miss the first navigation event, so I had to remove router observables from my services and in my components I had to use ActivatedRoute.url which appears to be a cold observable.

Still, I need some testing to see if this behavior is acceptable and I'd leave this issue open for my previous requests regarding the docs. I hope to receive some feedback by members/collaborators.

@vikerman
Copy link
Contributor

We are adding initialNavigation: 'enabled' as part of the express-engine schematics, since that seems like the sane default that will avoid the basic cause for flicker in many cases. We will document it as part of that.

Issue #1200

@giacomo
Copy link

giacomo commented Aug 23, 2019

@vikerman adding initialNavigation: 'enabled' will may works for smaller projects. But as @UserGalileo said observables on RouterEvents will stop to work as expected.
For example using https://github.com/gilsdav/ngx-translate-router will not work and should not redirect to right language route.

I think it can't be the right solution for getting a smoother transition. Right?

@BruneXX
Copy link

BruneXX commented Aug 29, 2019

Hi Guys, any idea why initialNavigation: 'enabled' is firing this warning?
ReferenceError: System is not defined
after that lazyLoad route is taking forever and never loads the page.
I'm experiencing the same issue here, lazyLoad modules are not rendering page source code instead of that I see the tag. Thanks!

@Splaktar
Copy link
Member

Splaktar commented Sep 23, 2019

What do you think about making a little effort in order to have a better documentation for things like these ones? Again, "initialNavigation" is one of those things that you find only through issues, even the official Angular docs only say "Disables the initial navigation", nothing more.

@UserGalileo angular/angular#32571 was opened to report this just a couple weeks ago. In response to that, PR angular/angular#32707 adds the InitialNavigation API to the docs.

In addition, in PR angular/angular#32702 @jbogarthyde updated the initialNavigation docs.

@Splaktar
Copy link
Member

@BruneXX that sounds like a related, but different issue, can you please open a new issue with a reproduction?

@Splaktar
Copy link
Member

Splaktar commented Sep 23, 2019

As I mentioned in #1200 (comment), it seems like the SSR Guide needs to be updated to include information and examples for both Preboot and InitialNavigation: 'enabled'.

However, after reading this and the other related issues, it's not clear that the best practices or recommendations have fully solidified here. Does anyone know of any blog posts that dive deeper into this and provide some recommendations?

The Preboot library has a similar issue and a reproduction that shows that both Preboot and initalNavigation: 'enabled' are not sufficient to solve this: angular/preboot#75

@UserGalileo
Copy link
Author

UserGalileo commented Oct 1, 2019

Unfortunately, although initialNavigation solved some of the problems, it brought other bugs in my app and it's quite frustrating.

As I said, router-based observables behave differently. Router-based libraries don't work. Guards run in a different order (ex. I am authenticating or checking for tokens in AppComponent but guards don't wait for AppComponent to run), so I don't think we can say that this is the solution to all the problems.

Honestly, after weeks, this experience is really driving me crazy... I don't want to sound polemic, this is just my experience.

I don't know @Splaktar , what could we do? I think a solution should be found internally in order to make things behave like initialNavigation: 'enabled' but without messing with the rest of the app. Updating the docs would obviously be fine, but what if we can avoid this practice which leads to other problems?

@zjcboy
Copy link

zjcboy commented Oct 23, 2019

@giacomo
Hello, I'm use ngx-translate-router too, Have you solved this problem?

@ErikWitkowski
Copy link

About this flickering, my approach for now is to put a huge page-size loading overlay until components are rendered correctly on client side, - only benefit here is that the page is still crawlable by no-javascript SEO bots. I hope we can have a better solution for this in the future so we can also benefit of a smoother page load.

@rickvandermey
Copy link

I've got a smooth transition, made with prerendering and preboot combination. Github.com/rickvandermey/angular-starterkit

I use SSR to create static html files for serving and preboot comes in for the transition

@vikerman
Copy link
Contributor

@UserGalileo - We currently don't understand the issue to provide a solution. Is there a way you can create a smaller repro of the issue and share a test repo? Thanks!

@vikerman vikerman added the need: repro steps We cannot reproduce the issue with the information given label Oct 23, 2019
@giacomo
Copy link

giacomo commented Oct 24, 2019

@zjcboy no there is for me no solution at this point - as I said observables on RouterEvents will stop to work as expected.

@vikerman
Copy link
Contributor

@giacomo - Is it possible to share a small repro of the issue?

@UserGalileo
Copy link
Author

@vikerman What would the test repo be like? What do you want to see that you cannot understand now? As I and others said, router observables will stop working: simply try using initialNavigation: 'enabled' and listen for Router.events in AppComponent: the first navigation is missed. And for the "flickering" thing that initialNavigation partially solves, since this is one of the many issues regarding this problem, I think there is no need for further explanation, as you said in your first reply in this thread, this is how Universal works :)

@static2000
Copy link

I have different experiences. I have a route animation,when I remove it, the flicker disappears

@Newbie012
Copy link

I have an introduction animation in my app, so when the app is being bootstrapped, the animation reoccurs. Is there a way to prevent it? For now, I added a loading spinner until the app is bootstrapped, but that's not a good solution.

@giacomo
Copy link

giacomo commented Feb 19, 2020

So I've created a "repro repository" with angular 9. But it seems that the problem doesn't affect angular 9. Could some one look in my code or tell me if the problem still exists. @UserGalileo can you confirm?

REPRO Link: https://github.com/giacomo/angular-initial-navigation-repro

Since the https://github.com/gilsdav/ngx-translate-router isn't fully compatible to angular 9 at the moment I can't install it.

Here are some logs

dev - default

> NavigationStart 
> Angular is running in the development mode. Call enableProdMode() to enable the production mode.
> RoutesRecognized 
> ==========> redirect to new route
> NavigationCancel 
> NavigationStart 
> RouteConfigLoadStart 
> RouteConfigLoadEnd 
> RouteConfigLoadStart 
> RouteConfigLoadEnd 
> RoutesRecognized 
> GuardsCheckStart 
> ChildActivationStart 
> ActivationStart 
> ChildActivationStart 
> ActivationStart 
> ChildActivationStart 
> ActivationStart 
> GuardsCheckEnd 
> ResolveStart 
> ResolveEnd 
> ActivationEnd 
> ChildActivationEnd 
> ActivationEnd 
> ChildActivationEnd 
> ActivationEnd 
> ChildActivationEnd 
> NavigationEnd 
> Scroll 
[WDS] Live Reloading enabled.

dev - initialNavigation: 'enable'

> NavigationStart 
> RoutesRecognized 
> ==========> redirect to new route 
> NavigationCancel 
> NavigationStart 
> RouteConfigLoadStart 
> RouteConfigLoadEnd 
> RouteConfigLoadStart 
> RouteConfigLoadEnd 
> RoutesRecognized 
> GuardsCheckStart 
> ChildActivationStart 
> ActivationStart 
> ChildActivationStart 
> ActivationStart 
> ChildActivationStart 
> ActivationStart 
> GuardsCheckEnd 
> ResolveStart 
> ResolveEnd 
> ActivationEnd 
> ChildActivationEnd 
> ActivationEnd 
> ChildActivationEnd 
> ActivationEnd 
> ChildActivationEnd 
> NavigationEnd 
> Scroll 
> Angular is running in the development mode. Call enableProdMode() to enable the production mode.
> [WDS] Live Reloading enabled.

prod - initialNavigation: 'enable'

> ft 
> yt 
> ==========> redirect to new route
> gt 
> ft 
> Ct 
> St 
> Ct 
> St 
> yt 
> bt 
> xt 
> Ot 
> xt 
> Ot 
> xt 
> Ot 
> vt 
> wt 
> _t 
> Tt 
> Et 
> Tt 
> Et 
> Tt 
> Et 
> pt 
> jt

Using:

Angular CLI: 9.0.2
Node: 10.16.0
OS: win32 x64

Angular: 9.0.1
... animations, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, platform-server, router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.2
@angular-devkit/build-angular     0.900.2
@angular-devkit/build-optimizer   0.900.2
@angular-devkit/build-webpack     0.900.2
@angular-devkit/core              9.0.2
@angular-devkit/schematics        9.0.2
@angular/cli                      9.0.2
@ngtools/webpack                  9.0.2
@nguniversal/builders             9.0.0
@nguniversal/common               9.0.0
@nguniversal/express-engine       9.0.0
@schematics/angular               9.0.2
@schematics/update                0.900.2
rxjs                              6.5.4
typescript                        3.7.5
webpack                           4.41.2

@nicholas-davis
Copy link

nicholas-davis commented Mar 4, 2020

I can conform it's still happening in Angular v9 - tested the flickering using Flex Layout

@haleyDesignhouse
Copy link

It's happening in my Angular 9 project as well. Using Angular Material.

@giacomo
Copy link

giacomo commented Jun 23, 2020

@nicholas-davis could you please create a branch to my repo so that the angular team could reproduce it. Thanks.
https://github.com/giacomo/angular-initial-navigation-repro

@madhavan-sundararaj
Copy link

It's happening with Angular 10 and Angular Universal 10 too, I don't have lazy loaded modules hence initialNavigation was of no help to me.

@iamcco
Copy link

iamcco commented Jul 13, 2020

Still have this issue with angular9

@puneetv05
Copy link

puneetv05 commented Jul 15, 2020

@vikerman initialNavigation:enabled stops the flickering for me, but the navigation in Can activate guard happens after the bootstrap finishes in the result of this: it loads the home page (not guarded) and after that (guarded page).
Expected behavior: It should have rendered the can activate navigated page from the server itself
my all routes are lazy-loaded.

@rickvandermey
Copy link

@puneetverma05 this is probably the guard returns an boolean promise or observable boolean. Return an URLTREE should avoid the homepage

@puneetv05
Copy link

@rickvandermey apparently, The issue was related to the ngrx select

@gitalvininfo
Copy link

gitalvininfo commented Jan 27, 2021

This issue still exist, in my case I have a landing page when first visit, landing page is visible for about 100ms, it is really bad ux for first time visitors of the app.

Already tried solutions like Transferstate but in my case does not meet my expectations to solve the issue. My landing page do not have any XHR request.

I also tried the TransferHttpCacheModule, I notice that first visit, sometimes it loads correctly without the glimpse of landing page, and sometimes it lessens the flicker amount of time for about 20ms but still I can see a glimpse of my landing page, after that it somehow fix my issue, but when I clear my cache, it returns back to previous state which the landing page is visible for about 20ms.

Also tried Preboot as what others is suggesting, but it is not suitable for my app, I have a mat-progress-spinner which acts as loading, when preboot is enabled and I hit F5/refresh my loading icon freezes for a while, it does not animate, which I think is very ugly and after about 1sec it goes back to normal state (app is working as usual like normal angular app).

I'm actually stuck here and I can't just leave it as it is.

@webberig
Copy link

webberig commented Jan 5, 2022

This problem still perists in Angular 13. I created a repro:
https://github.com/webberig/universal-rehydration-problem

The steps to create this repo were:

  1. ng new project
  2. ng g module --routing --route lazy
  3. ng add @nguniversal/express-engine
  4. lazy.component.ts changed, added debugger; in the constructor to pause browser when component is created
  5. main.ts don't execute bootstrap() but expose it to the window object globally.

To reproduce the problem, please follow these steps:

  1. run yarn dev:ssr
  2. Open your browser, open dev tools and navigate to http://localhost:4200/lazy

You will see the server-side rendered version. Angular is not loaded because of step 5 above. The content of the lazy component is present in the DOM as expected:

image

  1. Run bootstrap() in the dev tools console

You'll notice the dev tools has paused execution in the lazy.component. The page will be blank at this point. You'll see that that the app-lazy element has been removed:

image

The fact that the server-side rendered content of the router-outlet is removed while the lazy.component's constructor is still being executed proves to be problematic. The severity of flickering may increase depending on what happens on the page. In my application, the blank page is very noticeable and annoying.

My last attempt to solve this was to detach change detection, as I figured the component was being rendered in a blank state before data was read. Since the old HTML is already gone when the constructor is executed, tweaking the change detection is no use. I would expect the old HTML to be replaced only when the new component renders the first time (ie. after init lifecycle hook)

@hiepxanh
Copy link
Contributor

hiepxanh commented Mar 8, 2022

@webberig did you try initialNavigation: 'enabledBlocking', I tried on angular 13 and it working good.

@webberig
Copy link

webberig commented Mar 8, 2022

@webberig did you try initialNavigation: 'enabledBlocking', I tried on angular 13 and it working good.

Yes, this is already added automatically when you add universal.
https://github.com/webberig/universal-rehydration-problem/blob/main/src/app/app-routing.module.ts

If you add a breakpoint as described in my previous comment, you will see that the server-side html is gone.

Your app may just be small and fast enough to not notice a flicker, but it's there.

I got much improvement after adding preboot, though.

@alan-agius4
Copy link
Collaborator

This should be fixed with the current hydration efforts. Let's keep tracking such issues in angular/angular#23427

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
need: repro steps We cannot reproduce the issue with the information given
Projects
None yet
Development

No branches or pull requests