ElMassimo / iles

🏝 The joyful site generator
https://iles.pages.dev
MIT License
1.07k stars 31 forks source link

Vue client app should be created with `createSSRApp` for efficient client hydration #149

Open maoberlehner opened 2 years ago

maoberlehner commented 2 years ago

Description 📖

In multiple locations createApp is used instead of createSSRApp (e.g., https://github.com/ElMassimo/iles/blob/af74635967d7136ce46ef16c83b3ea453a141e17/packages/hydration/vue.ts#L6). I think this prevents efficient client-side hydration.

When re-hydrating server-side rendered HTML, createSSRApp should also be used on the client to enable efficient re-hydration. See: https://vuejs.org/guide/scaling-up/ssr.html#client-hydration

Reproduction 🐞

This can be reproduced with every Vue powered iles app.

Logs 📜

Not applicable.

Screenshots 📷

Not applicable.

maoberlehner commented 2 years ago

Fixing this issue has two benefits:

  1. better hydration performance (Vue reuses the existing DOM)
  2. It allows for a nice pattern where you don't need to inline props data for rehydration.
<script setup lang="ts">
import { defineComponent, onServerPrefetch, ref } from 'vue';

const data = ref(null);

// Load data for client-side hydration
if (typeof window !== `undefined`) {
  // Top level await makes this an async component
  await new Promise((resolve) => {
    setTimeout(() => {
      data.value = { hello: 'World' };
      resolve(null);
    }, 1000);
  })
}

// Load data on the server
onServerPrefetch(async () => {
  await new Promise((resolve) => {
    setTimeout(() => {
      data.value = { hello: 'World' };
      resolve(null);
    }, 500);
  })
});
</script>

<template>
  <div>{{ data.hello }}</div>
</template>

This can be nice if you load a lot of data from an API and you don't want to inline it for rehydration. Instead, as soon as the component is hydrated, the data will be fetched again.

Currently, you get a flash of empty content for 1000ms. When using createSSRApp you always see World because the component is only re-rendered when the data is fetched. Until then, the SSR content is shown.

ElMassimo commented 2 years ago

Thanks for reporting Markus! 😃

It makes sense to use createSSRApp in both cases when in build mode.

Need to check whether it would still be desirable to use createApp during development (for example, verify if Vue logs any warnings when using createSSRApp when the HTML has not been rendered).

PRs are welcome! 😃

I'll take a look during the week.

ElMassimo commented 2 years ago

I've explored switching to createSSRApp, but it doesn't seem to be working as intended.

There are some problems when Vue components should be re-rendered which suggest that the optimizations performed by the Vue compiler don't hold with the way that the rendered HTML is inserted.

For example, running the docs site with pnpm docs, the DarkModeSwitch component is not re-rendered correctly when toggling the theme.