npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nuxt-delay-hydration-v2

v1.2.1

Published

Improve your Nuxt.js Google Lighthouse score by delaying hydration ⚡️

Downloads

5

Readme

nuxt-delay-hydration

Features

  • 🔥 Reduce your "Blocking Time" by as much as 100%, instantly increasing your Google Lighthouse score
  • 🚦 Filter it to run only on specific pages
  • 🍃 Minimal config
  • 🔁 (optional) Replay pre-hydration clicks

Delaying hydration is a technique to hint to Google that our scripts are not required for our app to function.

By delaying hydration we improve the Google Lighthouse score by reducing your "Blocking Time" metric.

Previously you may have used vue-lazy-hydration which works well. However, it is just a more verbose way of providing hints to Google, like we're doing with this module.

A progressively enhanced app is one that is designed to work without JavaScript, and then progressively enhanced with JavaScript.

Your scripts should not be required to use your website, this is what we're hinting to Google by delaying the hydration.

To do that you can ensure:

  • Full HTML served on the initial response
  • Scripts don't trigger a CLS
  • Avoid using scripts to set images, will affect the LCP
  • 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 progressive script enhancements, we'll need to rely on this approach.

Install

If you're using Nuxt 2.x, please follow the docs on the v0 branch.

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

Requirement: Progressively enhanced SSR or SSG Nuxt app.

Usage

// nuxt.config.ts
export default {
  modules: [
    'nuxt-delay-hydration',
  ],
  delayHydration: {
    // enables nuxt-delay-hydration in dev mode for testing
    debug: process.env.NODE_ENV === 'development'
  }
}

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 all scripts from loading. | Zero or minimal plugins/modules. | | mount recommended | Delays Nuxt while it's 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. | All other apps |

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

Init Mode

This mode delays all scripts from loading until the hydration promise is resolved.

It does this by hooking into the HTML rendering, removing all script tags and adding them back after the hydration promise is resolved.

This will provide the biggest speed improvements however is the riskiest.

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

This mode delays Nuxt while it's mounting. Plugins and some third-party scripts will work.

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.

<template>
  <div>
    <DelayHydration>
      <div>
        <LazyMyExpensiveComponent />
      </div>
    </DelayHydration>
  </div>
</template>

Guides

Debugging

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',
  },
}

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

<template>
  <div>
    <MyHeader />
    <DelayHydration>
      <div>
        <!-- Show the hydration status, only for debugging -->
        <HydrationStatus />
        <main>
          <nuxt />
        </main>
        <my-footer />
      </div>
    </DelayHydration>
  </div>
</template>

Performance Auditing

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

Replaying hydration click

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

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

Run nuxi analyze to find large components. When loading them, prefix them with Lazy.

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.

Filtering routes

Using the include and exclude options, you can specify which routes you'd like to delay hydration on.

// nuxt.config.ts
export default {
  delayHydration: {
    include: [
      '/blog/**',
    ],
    exclude: [
      '/admin/**'
    ],
  },
}

You can provide a glob pattern similar to route rules or a regex.

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.

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: 5000, desktop: 4000, }

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.

Benchmarks

Live examples

  • https://www.odysseytraveller.com/
  • https://massivemonster.co/

Credits

Sponsors

License

MIT License © 2022 - Present Harlan Wilton