All Projects → harlan-zw → nuxt-delay-hydration

harlan-zw / nuxt-delay-hydration

Licence: other
Improve your Nuxt.js v2 Google Lighthouse score by delaying hydration ⚡️

Programming Languages

typescript
32286 projects
javascript
184084 projects - #8 most used programming language
Vue
7211 projects

Projects that are alternatives of or similar to nuxt-delay-hydration

nuxt-speedkit
nuxt-speedkit will help you to improve the lighthouse performance score (100/100) of your website.
Stars: ✭ 401 (+197.04%)
Mutual labels:  nuxt, hydration, lighthouse
nuxt-prune-html
🔌⚡ Nuxt module to prune html before sending it to the browser (it removes elements matching CSS selector(s)), useful for boosting performance showing a different HTML for bots/audits by removing all the scripts with dynamic rendering
Stars: ✭ 69 (-48.89%)
Mutual labels:  nuxt, lighthouse
html2md
helloworld 开发者社区开源的一个轻量级,强大的 html 一键转 md 工具,支持多平台文章一键转换,并保存下载到本地。
Stars: ✭ 332 (+145.93%)
Mutual labels:  nuxt
vite.org
The Official Website of Vite - https://vite.org
Stars: ✭ 31 (-77.04%)
Mutual labels:  nuxt
nuxtwebsite
A simple Nuxt.js setup to create websites with blog feature using Storyblok as CMS and Netlify to deploy it.
Stars: ✭ 29 (-78.52%)
Mutual labels:  nuxt
loading-screen
Loading Screen Module for Nuxt.js
Stars: ✭ 89 (-34.07%)
Mutual labels:  nuxt
open-fixture-library
A library and website for lighting technology's DMX fixture definition files.
Stars: ✭ 113 (-16.3%)
Mutual labels:  nuxt
likecoin-button
Every Like is a reward. Turn your Likes into actual income now.
Stars: ✭ 18 (-86.67%)
Mutual labels:  nuxt
react-lighthouse-viewer
A React component used for rendering Lighthouse JSON reports Demo:
Stars: ✭ 49 (-63.7%)
Mutual labels:  lighthouse
nuxt-ghost
Easy Ghost content API integration with Nuxt.js.
Stars: ✭ 27 (-80%)
Mutual labels:  nuxt
nuxtjs-sample
Nuxtjs sample! Typescript + Vuetify + Jest! (rails api server: https://github.com/walkersumida/rails-api-for-front)
Stars: ✭ 28 (-79.26%)
Mutual labels:  nuxt
nuxt-fundamentals
Source code for the Nuxt.js Fundamentals course
Stars: ✭ 40 (-70.37%)
Mutual labels:  nuxt
nuxt-supabase
A supa simple wrapper around Supabase.js to enable usage within Nuxt.
Stars: ✭ 146 (+8.15%)
Mutual labels:  nuxt
thvu-blog
My digital home on the internet.
Stars: ✭ 51 (-62.22%)
Mutual labels:  lighthouse
lighthouse-circleci-example
An example repo demonstrating Lighthouse testing in CircleCi
Stars: ✭ 21 (-84.44%)
Mutual labels:  lighthouse
plugin-lighthouse
A simple sitespeed.io lighthouse plugin to collect few performance metrics
Stars: ✭ 14 (-89.63%)
Mutual labels:  lighthouse
nuxt-vite
Nuxt + Vite!! HMR so fast it'll make your head spin! Plus all the benefits of Nuxt
Stars: ✭ 54 (-60%)
Mutual labels:  nuxt
portfolio-2018
My personal portfolio / resume online
Stars: ✭ 18 (-86.67%)
Mutual labels:  nuxt
nuxt-modules
AX2's Nuxt modules
Stars: ✭ 30 (-77.78%)
Mutual labels:  nuxt
site-audit-seo
Web service and CLI tool for SEO site audit: crawl site, lighthouse all pages, view public reports in browser. Also output to console, json, csv, xlsx, Google Drive.
Stars: ✭ 91 (-32.59%)
Mutual labels:  lighthouse

nuxt-delay-hydration

NPM Downloads

Delay Hydration for Nuxt.js! ⚡️
Improve your Nuxt.js v2 Google Lighthouse score by delaying hydration ⚡️


Status: Stable v2 , bridge , v3 ❌️
Made possible by my Sponsor Program 💖
Follow me @harlan_zw 🐦

Features

  • ⚡️ Instantly increase your Google Lighthouse score
  • 🔥 Reduce your "Blocking Time" by as much as 100%
  • 🍃 Pre-configured to minimise user experience issues
  • 🔁 (optional) Replay pre-hydration clicks

Motivation
Hydration is the process of hooking up static HTML with your Nuxt app to provide reactivity, used in SSR and SSG.

This hydration process is expensive, especially with Nuxt 2. Google Lighthouse penalises hydration with a high "Total Blocking Time" and "Time to Interactive".

While this is unavoidable in most apps, for static sites which depend on minimal interactivity, it is possible and safe to delay the hydration to avoid this penalty.

The current solution for delaying hydration is vue-lazy-hydration which works well. However, it can require a lot of tinkering, may break your HMR and not solve the performance issues.

In mount and init mode, this module delays Nuxt core code to squeeze out maximum performance, specifically around large payload executions.

Nuxt Delay Hydration aims to provide optimisations with minimal tinkering, by making certain assumptions on trade-offs. This module isn't built to replace vue-lazy-hydration, but as an abstraction layer on top of it.


How this module works
A promise is injected into your app, the location is based on the mode. The promise is resolved as soon as either of these events has fired:
  • an interaction event (mouse move, scroll, click, etc)
  • an idle callback with a fixed timeout

The idle CPU time hints to Google that these scripts are not required for your app to run.

For example:

  • if a Google bot visits the page and has no interaction, out of the box the hydration won't occur until the browser idle callback + 6 seconds
  • if a user visits the page and moves their cursor or scrolls, the hydration will be triggered immediately. The chance of interacting with the non-hydration app will be minimised

Keep in mind, this is a hacky solution. Until Google can recognise which scripts are truly blocking, we'll need to rely on this approach.


Install

yarn add nuxt-delay-hydration
# npm i nuxt-delay-hydration

Requirement: SSR, full-static (SSG) is highly recommended.

Further Requirements
Your scripts should not be required to use your website, this is what we're hinting to Google.

To do that you can ensure:

  • Full HTML served on the initial response
  • Scripts don't trigger a CLS
  • Scripts shouldn't be required for a user to interact with your site
  • Avoid using scripts to set images, will affect the LCP

Please benchmark your app before starting.

This module has been tested on documentation sites, blogs and misc content sites.


Usage

Within your nuxt.config.js add the following.

// nuxt.config.js
export default {
  buildModules: [
    'nuxt-delay-hydration',
  ],
}

Note: The module will not run in development unless you have enabled debug.

Choosing a mode

By default, no mode is selected, you will need to select how you would the module to work.

Type: init | mount| manual | false

Default: false

Type Description Use Case
false default Disable the module Testing
init Delays Nuxt app creation. All code is delayed including plugins and third-party scripts. Zero or minimal plugins/modules.
mount recommended Delays Nuxt after creation and before mounting. Plugins and some third-party scripts will work. Minimal non-critical plugins and third-party plugins.
manual Delay is provided by the DelayHydration component. Extends vue-lazy-hydration All other apps

Regardless of the mode you choose, please read further optimisations.

Init Mode

Delays hydration before the Nuxt app is created. Your entire app, including plugins, will be delayed. This will provide the biggest speed improvements however is the riskiest and may increase other metrics with delayed network requests.

Pros: Provides the biggest blocking time reduction

Cons: Risky if you have critical third party scripts

Benchmark: ~90-100% reduction

export default {
  delayHydration: {
    mode: 'init'
  }
}

Mount Mode

Delays hydration once your app is created (all plugins and vendor bundle loaded) and is about to be mounted. This delays your layout and page components.

Pros: Safer and still provides good improvements

Cons: May still break certain layouts if they are js dependent.

Benchmark: ~70% reduction

export default {
  delayHydration: {
    mode: 'mount'
  }
}

Manual Mode

Using the manual mode, you manually specify what part of your app you'd like to delay. Useful for when you need some part of the page to always hydrate immediately, such as a navigation drawer.

Pros: Safest way to optimise

Cons: Speed improvement based on usage

export default {
  delayHydration: {
    mode: 'manual'
  }
}

DelayHydration component

Once you have set the mode, you need to use the component. It's recommended you use the component within your layout file.

<template>
<div>
    <my-header />
    <delay-hydration>
        <!-- You must have a single child node as the slot -->
        <div>
            <main>
                <nuxt />
            </main>
            <my-footer />
        </div>
    </delay-hydration>
</div>
</template>

This component is a wrapper for a pre-configured vue-lazy-hydration component.

If you're using nuxt/components then no import is required. Otherwise, you can import the component as:

import { DelayHydration } from 'nuxt-delay-hydration/dist/components'

The behaviour of the component should be controlled by the advanced configuration, however props are provided for convenience.

Props

forever: boolean:false Toggle the hydration delay to last forever

debug: boolean:false Toggle the debug logging

replayClick: boolean:false Toggle the click replay

Guides

Debugging

Debug mode
It's recommended that you do thorough testing on your app with the module before deploying it into production.

To make sure the module is doing what you expect, there is a debug mode, which when enabled will log behaviour in the console.

It might be a good idea to always debug on your local environment, in that instance you could do:

export default {
    delayHydration: {
        debug: process.env.NODE_ENV === 'development',
    },
}

Delay hydration forever
Since the hydration will trigger instantly when you interact with the page, it can be useful to manually delay the hydration forever so you can test the functionality of your app in its non-hydrated state.
export default {
    delayHydration: {
        forever: true
    },
}

Visualising the hydration status
It can be unclear at times whether your app has been hydrated or not if it's quite static, this can make debugging hard.

To make things easier, there is a component HydrationStatus which will tell you what's going on.

<template>
<div>
    <my-header />
    <delay-hydration>
        <div>
            <!-- Show the hydration status, only for debugging -->
            <hydration-status />
            <main>
                <nuxt />
            </main>
            <my-footer />
        </div>
    </delay-hydration>
</div>
</template>

If you're using nuxt/components then no import is required. Otherwise, you can import the component as:

import { HydrationStatus } from 'nuxt-delay-hydration/dist/components'

Performance Auditing

Use my audit tool: https://unlighthouse.dev/

Replaying hydration click

What is this and how to enable
One of the issues with delaying hydration is that a user interaction event can occur before your scripts are loaded, leading to a user having to click on something multiple times for it to do what they expect. Think of a hamburger that is triggered using Javascript, if your app isn't hydrated then clicking it won't do anything.

The best fix for this is to write your HTML in a way that doesn't need Javascript to be interactive.

However, there are use cases where you need to use Javascript and responding to the first click is important. In those instances, you can enable the replay of the click.

export default {
    delayHydration: {
        replayClick: true
    },
}

This is an experimental configuration, you should test this option yourself before implementing it into your production app.

Further Optimisations

Load heavy components async
When you load in a heavy component synchronously, the javascript will be bundled in with the main application payload.

This will decrease all of your performance metrics. It's recommended you use async imports for these components.

Analyze your components and load the big ones async. If you're using nuxt/components, you can easily prefix them with Lazy to do so, otherwise, you can use the following syntax.

<script>
export default {
  components: {
    AdSlider: () => import('./AdSlider.vue'),
    ArticleContent: () => import('./ArticleContent.vue'),
    CommentForm: () => import('./CommentForm.vue'),
    ImageSlider: () => import('./ImageSlider.vue'),
  },
};
</script>

Advanced Configuration

Configuration should be provided on the delayHydration key within your Nuxt config.

If you're finding the lab or field data is not performing, you may want to tinker with this advanced configuration.

Event Hydration

hydrateOnEvents

  • Type: string[]
  • Default: [ 'mousemove', 'scroll', 'keydown', 'click', 'touchstart', 'wheel' ]

Controls which browser events should trigger the hydration to resume. By default, it is quite aggressive to avoid possible user experience issues.

replayClick

  • Type: boolean
  • Default: false

If the trigger for hydration was a click, you can replay it. Replaying it will re-execute the event when it is presumed your app is hydrated.

For example, if a user clicks a hamburger icon and hydration is required to open the menu, it would replay the click once hydrated.

⚠️ This is experimental, use with caution.

replayClickMaxEventAge

  • Type: number (milliseconds)
  • Default: 1000

The replay click event will not run if the hydration takes more than the limit specified.

This limit is designed to avoid possible user experience issues.

Idle Hydration

idleCallbackTimeout

  • Type: number (milliseconds)
  • Default: 7000

When waiting for an idle callback, it's possible to define a max amount of time to wait in milliseconds. This is useful when there are a lot of network requests happening.

postIdleTimeout

  • Type: { mobile: number, desktop: number } (milliseconds)
  • Default: { mobile: 6000, desktop: 5000, }

How many to wait (in milliseconds) after the idle callback before we resume the hydration. This extra timeout is required to avoid the standard "blocking", we need to provide real idle time to lighthouse.

Mobile should always be higher than desktop as the CPU capacity will generally be a lot less than a desktop.

Note: The default will likely be customised in the future based on further benchmarking.

Debugging

debug

  • Type: boolean
  • Default: false

Log details in the console on when hydration is blocked and when and why it becomes unblocked.

forever

  • Type: boolean
  • Default: false

Run the delay forever, useful for testing your app in a non-hydrated state.

Benchmarks

Live examples

Credits

Sponsors

License

MIT License © 2022 - Present Harlan Wilton

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].