Оптимизация производительности через ленивый(отложенный) рендеринг/гидратацию
Async-components
Обычные асинхронные компоненты все-равно будут грузиться и на клиенте и на сервере(исправляется фиксом выше), ЕСЛИ v-if=true, в случае v-if=false грузиться они не будут. С v-show в любом значении компонент будет загружен.
Имеет смысл для элементов которые меняются между мобильным/десктопным интерфейсом, не важны для seo и отображаются по действию (модальные окна, мобильные сайдбары)
<template>
<div>
<AsyncComponent />
</div>
</template>
<script>
export default {
components: {
AsyncComponent: () => import('foo/bar'),
},
};
</script>
Преимущества и недостатки
- ✓ Отложенная загрузка если компонент находится за
v-ifили<Component :is=""/> - ✓ Работает для
seo(рендерится приssr) - × В остальных случаях порой хуже статичных импортов т.к. грузится и на клиенте и на сервере и маунтиться сразу, но получается лишний запрос
- × Возможно размазывание
long-taskпри первом рендере и увеличениеFID/INP
Область применения
- Компоненты в
<Component :is="component" /> - Компоненты за
v-if(лучше с исходным значениемfalse)
Vue-lazy-hydration
C v-if компонент грузится лишь при v-if="true".
С v-show гидрируется и грузится по триггеру (when-visible итд) (для ssr уточнение)
<template>
<div>
<AsyncComponent />
</div>
</template>
<script>
import { hydrateNever } from 'vue-lazy-hydration';
export default {
components: {
AsyncComponent: hydrateNever(() => import('foo/bar')),
},
};
</script>
Преимущества и недостатки
- ✓ Логика компонента, загрузка (для
ssrfix) и привязывание кDOMвыполняется по триггеру - ✓ Работает для
seo(рендерится приssr) - × При слишком коротких триггерах возможно размазывание
long-taskпри первом рендере и увеличениеFID/INP - × При неточных триггерах или их отсутствие, потеря интерактивности
- × Теряет смысл при
spaпереходах
Область применения
- Тяжелые компоненты НУЖНЫЕ при
ssr(SfCarousel, разнообразные списки) - Формы при правильном триггере
Lazy-component
Сочетание ClientOnly и триггеров на манер vue-lazy-hydration, при указании loadingComponent лоадер отображается при ssr и до маунта компонента.
<template>
<div>
<LazyComponent />
</div>
</template>
<script>
import { defineLazyComponentWhenObserve } from 'shared-front/lib/composition/lazy-component';
export default {
components: {
LazyComponent: defineLazyComponentWhenObserve({
loader: () => import('foo/bar'),
loadingComponent: 'SfSpinner',
}),
},
};
</script>
Преимущества и недостатки
- ✓ Клиентский код не исполняется на сервере, полезно для библиотек или если нужен
browser api - ✓ Первый рендер и загрузка откладываются до триггера
- ✓ Работает при
spaпереходах - × Не работает для
seo(приssrотображается лоадер если есть) - × При слишком коротких триггерах возможно размазывание
long-taskпри первом рендере и увеличениеFID/INP - × При неточных триггерах или их отсутствие, потеря интерактивности
Область применения
- Тяжелые компоненты НЕ нужные при
ssr(SfRichEditor) - Модальные окна и выезжающие панели которых нет на экране изначально (
MobileUserMenu)
ClientOnly
Преимущества и недостатки
- ✓ Клиентский код не исполняется на сервере, полезно для библиотек или если нужен
browser api - × Для первого рендера модифицируется
domи этим вызывает дополнительную нагрузку на браузер, которой бы не было приssr
Область применения
- Компоненты в которых нужно
browser APIи которые нужны как можно скорее (прим.SfColorThemeSwitcher,FcmSubscribed) - Страницы которые не нужны при
ssr(формыspravochnik/redactor)
async-components
Обычные асинхронные компоненты все-равно будут грузиться и на клиенте и на сервере даже с v-if (тем более с v-show)
<template>
<div>
<ClientOnly>
<AsyncComponent />
</ClientOnly>
</div>
</template>
<script>
export default {
components: {
AsyncComponent: () => import('foo/bar'),
},
};
</script>
Оборачивание vue-lazy-hydration в ClientOnly
При оборачивании vue-lazy-hydration в ClientOnly и , гидратация не откладывается т.к. её нет. Компонент сразу загружается и маунтиться.
Полностью идентично обычным/асинхронным компонентам в ClientOnly, лишено смысла.
<template>
<div>
<ClientOnly>
<AsyncComponent />
</ClientOnly>
</div>
</template>
<script>
import { hydrateNever } from 'vue-lazy-hydration';
export default {
components: {
AsyncComponent: hydrateNever(() => import('foo/bar')),
},
};
</script>
lazy-component
Внутри lazyComponent и так применяется логика ClientOnly, нет нужды в еще одной обертке
<template>
<div>
<ClientOnly>
<LazyComponent />
</ClientOnly>
</div>
</template>
<script>
import { defineLazyComponentWhenObserve } from 'shared-front/lib/composition/lazy-component';
export default {
components: {
LazyComponent: defineLazyComponentWhenObserve({
loader: () => import('foo/bar'),
loadingComponent: 'SfSpinner',
}),
},
};
</script>
Ssr and async-components
По умолчанию vue-server-renderer (vue2) используемый в nuxt2 добавляет асинхронные компоненты в <body>. Даже если фактическая их загрузка на клиенте никогда не стриггерится.
Для исправления этого, был написан хук к наксту, который вырезает <script> с определенным названием файла.
Исходно решение было взято из vuejs/vue#9847
hooks: {
render: {
// Хук после рендера, но до отправки клиенту
route: (_, page) => {
// html ответа
page.html = page.html.replace(
/<script[\w"= ]*src="(.*?)".*?><\/script>/gm,
(match, src) => {
// Удаление скриптов по src
if (src.includes('_lazy')) return '';
return match;
},
);
},
},
},
Помимо вырезания, нужно, чтобы вебпак отдавал эти названия файлов, поэтому необходимо чтобы config.build.filenames.chunk всегда содержал название чанка ([name])
build: {
filenames: {
chunk: () => '[id]-[name]-[chunkhash].js',
},
},
Для использования асинхронных чанков необходимо добавлять webpackChunkName с _lazy в названии
const ShopGuestPurchaseModal = () =>
import(
/* webpackChunkName: "shop-id_lazy" */ './-components/shop-guest-purchase-modal'
);
Итоги
- !!! Для асинхронных импортов компонентов нужно добавление
webpackChunkNameс постфиксом_lazy - Для тяжелых компонентов не нужных при первом рендере использовать
lazyComponent - Для тяжелых компонентов нужных при
ssr, но с которыми юзер не будет сразу работатьvue-lazy-hydration - Для компонентов в
<Component :is="component" />обычныеasync-componentилиvue-lazy-hydration