1

I have a Vue/Nuxt web app where pages are dynamically generated from lots of components that have child components.

The trouble is the header and footer are rendered first, then the child components that have the actual content. This looks terrible on first load and Lighthouse doesn't like it. It's an Avoid large layout shifts failure. For context it's only an issue when client side rendering, SSR would eliminate this issue while intoducing others.

What I could do is edit every single component in my project and add an event on mounted. That could then be used to decide when to show the layout. The problem is it would be a major hassle and would cause bugs when new components are added and this bit is forgotten.

I'm not able to find any general solution to this in Vue and/or Nuxt. I'd love to have a new lifetime hook of allMounted which would only fire when child components are also mounted but it doesn't exist. Even that would be a bit hacky. An even more general render when all components are mounted option would be awesome.

rinu
  • 989
  • 1
  • 7
  • 14
  • `asyncData` is that kind of hook, no? https://nuxtjs.org/docs/2.x/features/data-fetching/#async-data Also, it comes down more to CSS and how your organize your skeletons than how you load your content. Do you have a visual example or some code to show the shifting? Otherwise, `fetch()` is a nice hook too, that you can use a loader until it's done fetching all the data. – kissu May 14 '21 at 09:52
  • It's a problem of async component loading not data or content. All content is loaded in middleware before any component loading or rendering. Components are then imported dynamically. For a visual example you can see www.credy.es – rinu May 14 '21 at 10:10
  • 2
    Why do you import them dynamically per page if you want them to be there at first? You should keep them there if you don't want them to glitch. Otherwise, they will be imported once you are on the page. Nuxt already does route code splitting so it's okay if you have your components loading directly. It's not like a modal that may be needed, your components need to be present everytime, so import them in a **non**-lazy way. Pretty sure this should fix paths like `/como-pedir-prestamo` pretty well. – kissu May 14 '21 at 10:20
  • I can't hard-code which components to use for a page. This information is requested from a server using middleware. I don't think it's possible to load components dynamically and at the same time in a non-lazy way. – rinu May 14 '21 at 11:27
  • I find a solution with combining loading screen and loading skeletons a good thing here. As this is more of a layout issue then loading. – Riad ZT May 14 '21 at 14:29
  • The issue is with my component loader. I was using import which is async while require is sync. – rinu May 15 '21 at 14:18
  • I would accept an answer of `render when all components are mounted` is already the default when components are not loaded async. Unfortunately I can't point to any part of your answer that is helpful. Having a discussion here in the comments did spark the idea of loading components in a synchronous way to solve the problem, thank you for that. The problem is solved and deployed live. – rinu May 19 '21 at 07:13

1 Answers1

2

I'm not sure that a dynamic component can help in your case, but I guess that your company's website will not really benefit from this. Indeed, the problem of the content jumping will still be present IMO.

<component :is="currentTabComponent"></component>

I still think that you content is highly static IMO and that you could even switch to full static to have the best performance benefits rather than having to wait for a long time (TTFB) while SPA is loading all the content. It may be a bit more challenging to have everything look nice of course (before/after the hydration).

Also, you should have an idea of the approximate size of your containers. In that case, you could use some skeletons and a maybe even a prototyping font to visually populate the blocks.


In case you do not agree or think that this is not doable, you still have this solution to your disposal

<child-component @hook:mounted="makeSomeStuff"></child-component>

With this you may be able to display a full-sized loader until your content is done loading. You could add a mixin with the longer mounted syntax in each component to avoid too much boilerplate but this one is deprecated and do have various issues.

But IMO, the issue is more in your way of fetching the data (asyncData and fetch hooks are nice) and the way that everything is full dynamic when there is no specific need. If it's more important to keep the dynamic part, I guess that you can be serious on code reviews or plug some git hooks or alike to kinda scan the code and see if the required mounted emits are in place.

There is no ideal solution in your case but keep in mind that Lighthouse will always prefer some SSR content with the less amount of JS. Here is my personal bible to anything performance related, you could probably grasp some nice tips in this really in-depth article.


Update for Vue3

The syntax has changed for Vue3: https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html#_2-x-syntax

tony19
  • 125,647
  • 18
  • 229
  • 307
kissu
  • 40,416
  • 14
  • 65
  • 133