Nuxt Nation カンファレンスが近づいています。11月12~13日にご参加ください。

vitalizer
nuxt-vitalizer

Google Lighthouse での LCP スコアを即座に改善

Nuxt Vitalizer module

Nuxt Vitalizer

Google Lighthouse と Google PageSpeed Insights での Largest Contentful Paint (LCP) を最適化するための、単一のタスクをうまくこなす Nuxt モジュールとして、回避策をまとめたものです。

このモジュールは、以下の Nuxt の問題(およびその他の問題)に対する解決策を提供します。

特徴

  • 🚀 設定不要で LCP を改善
  • 🫸 レンダリングをブロックする CSS を削除
  • 🔥 ブロッキング時間メトリクスを削減するための DelayHydration コンポーネント
  • 💨 初回レンダリングで SSR コンテンツを保持する SkipHydration コンポーネント

セットアップ

npx nuxi@latest module add nuxt-vitalizer

使い方

Nuxt Vitalizer を Nuxt の設定に追加すれば準備完了です

// `nuxt.config.ts`
export default defineNuxtConfig({
  modules: ['nuxt-vitalizer']
})

モジュールをカスタマイズするには、Nuxt の設定で vitalizer オプションを設定します

// `nuxt.config.ts`
export default defineNuxtConfig({
  modules: ['nuxt-vitalizer'],

  vitalizer: {
    // Remove the render-blocking entry CSS
    disableStylesheets: 'entry'
  }
})

LCP 最適化機能

このモジュールの最適化機能を適用すると、より高い Lighthouse パフォーマンススコアを達成できます

Lighthouse SEO performance score when using the module

!注意 この機能はデフォルトで有効になっています。

Nuxt の大規模なアプリケーションでは、HTML に <link rel="prefetch"> タグが蓄積されるため、Lighthouse と Google PageSpeed Insights でのパフォーマンススコアが低下する可能性があります。

非同期コンポーネントや画像などの他のアセットなど、動的インポートごとに prefetch リンクがレンダリングされます。これにより、ブラウザは現在のページで必要ない場合でも、これらのチャンクをプリフェッチします。これはアプリケーション全体のパフォーマンスにとっては素晴らしいことですが、プリフェッチリクエストの数が多くなり、Largest Contentful Paint スコアに悪影響を与える可能性があります。

このモジュールは、動的インポートの prefetch リンクのレンダリングを無効にすることで、LCP スコアを最適化するために Nuxt ビルドプロセスにフックします。

!注意 この機能は手動で有効にする必要があります。

プリロードリンクは、現在のページに必要なクリティカルなリソースをプリロードするために使用されます。一般的に、ウェブサイトのパフォーマンスを最適化する上でその役割がありますが、正しく使用しないと、リクエスト数が多くなる可能性もあります。プリロードリンクを削除すると、特にネットワーク環境が遅い場合、FCP(First Contentful Paint)スコアを改善するのに役立ちます。

プリロードビルドリソースを削除するには、disablePrefetchLinks オプションを true に設定します

// `nuxt.config.ts`
export default defineNuxtConfig({
  modules: ['nuxt-vitalizer'],

  vitalizer: {
    disablePrefetchLinks: true
  }
})

レンダリングをブロックする CSS を停止

!注意 この機能は手動で有効にする必要があります。使用するには、Nuxt の inlineStyles 機能を有効にする必要があります。このオプションを有効にした後、アプリケーションをテストしてください。

CSS スタイルシートはレンダリングをブロックするリソースです。つまり、ブラウザはページをレンダリングする前に CSS をダウンロードして解析する必要があります。スタイルシートをロードする代わりにインラインスタイルを使用すると、ブラウザはページをより速くレンダリングでき、LCP スコアを改善できます。

最新の Nuxt バージョンでは SSR レンダリング中にスタイルがインライン化されますが、entry.<hash>.css スタイルシートは HTML でレンダリングされたままです。これにより、レンダリングをブロックする CSS が発生し、Largest Contentful Paint スコアに悪影響を与える可能性があります。

なぜそうなるのでしょうか?Nuxt コアチームメンバーの @danielroe によって説明されているように

これは現在のインラインスタイル実装の制限だと思います。

アプリ全体で すべての場所で 使用されているスタイルは、CSS ソースから完全に安全に削除できます。ただし、1 つのコンポーネントまたはページでのみ使用される CSS は、インライン化されるだけでなく、CSS ファイルにも配置する必要があります。

現時点では、vite がクライアント側の CSS のロードをすべて担当しています。つまり、すでにロードされている CSS を追跡したとしても、vite が重複した CSS を含む CSS ファイルをロードするのを止めることはできません。

これはぜひ修正したい点です。

まず、app.vue ファイルにメインアプリケーションのスタイルをインポートしてみてください。Nuxt がビルドされると、entry CSS ファイルとして保存されます

// `app.vue`
import '~/assets/css/main.css'

次に、disableStylesheets オプションを entry に設定して、entry.<hash>.css スタイルシートが HTML でレンダリングされないようにします

// `nuxt.config.ts`
export default defineNuxtConfig({
  modules: ['nuxt-vitalizer'],

  vitalizer: {
    disableStylesheets: 'entry'
  }
})

コンポーネント

DelayHydration

!警告 コンポーネントのハイドレーションを遅らせることは、ページが実際よりも早くインタラクティブであると Lighthouse に認識させるためのハックです。実際のパフォーマンスの向上には繋がらない場合があり、慎重に使用する必要があります。

ハイドレーションを遅らせることは、ページが実際よりも早くインタラクティブであると Lighthouse にヒントを与えるためのテクニックです。これにより、Lighthouse と Google PageSpeed Insights の「ブロッキング時間」メトリクスを改善できます。

DelayHydration コンポーネントは、コンポーネントをハイドレーションする前に一定時間待機するシンプルなコンポーネントです。これは、多くのネットワークリクエストが発生していて、ネットワークリクエストが完了するまでコンポーネントのハイドレーションを遅らせたい場合に役立ちます。

コンポーネントの使用法

Vue コンポーネントで DelayHydration コンポーネントを使用します

<template>
  <div>
    <DelayHydration>
      <!-- Ensure to lazy load the component -->
      <LazyMyExpensiveComponent />
    </DelayHydration>
  </div>
</template>

設定

vitalizer モジュールオプションで DelayHydration コンポーネントを設定できます

// `nuxt.config.ts`
export default defineNuxtConfig({
  modules: ['nuxt-vitalizer'],

  vitalizer: {
    delayHydration: {
      hydrateOnEvents: ['mousemove', 'scroll', 'keydown', 'click', 'touchstart', 'wheel'],
      idleCallbackTimeout: 8000,
      postIdleTimeout: 4000
    }
  }
})
  • hydrateOnEvents オプションは、ハイドレーションをトリガーするイベントを指定します。デフォルトでは、ユーザーがマウスを移動したり、スクロールしたり、キーを押したり、クリックしたり、画面に触れたり、マウスホイールをスクロールしたりすると、コンポーネントはすぐにハイドレーションされます。
  • idleCallbackTimeout オプションは、アイドルコールバックを待機する際の最大待ち時間(ミリ秒単位)を指定します。これは、多くのネットワークリクエストが発生している場合に役立ちます。
  • postIdleTimeout オプションは、アイドルコールバック後にコンポーネントをハイドレーションする前に待機する時間(ミリ秒単位)を指定します。

SkipHydration

SkipHydration コンポーネントは、初期レンダリング時のクライアントでの子要素のハイドレーションを単純に防ぎます。言い換えれば、コンポーネントがアンマウントされない限り、SSR コンテンツは保持されます。これにより、特定のページに初めてアクセスしたときに特定のコンポーネントをハイドレーションする必要がない場合に、コンポーネントチャンクのロードを節約するのに役立ちます。

新しいページに移動すると、SkipHydration コンポーネントはクライアントで子要素をマウントし、通常の Vue コンポーネントのように動作します。

コンポーネントの使用法

Vue コンポーネントで SkipHydration コンポーネントを使用します

<template>
  <div>
    <SkipHydration>
      <!-- Ensure to lazy load the component -->
      <LazyMyExpensiveComponent />
    </SkipHydration>
  </div>
</template>

モジュールオプション

interface ModuleOptions {
  /**
   * Whether to remove prefetch links from the HTML. If set to `dynamicImports`, only dynamic imports will be removed. To disable all prefetching, such as images, set to `true`.
   *
   * @remarks
   * This will prevent the browser from downloading chunks that may not be needed yet. This can be useful for improving the LCP (Largest Contentful Paint) score.
   *
   * @default 'dynamicImports'
   */
  disablePrefetchLinks?: boolean | 'dynamicImports'

  /**
   * Whether to remove preload links from the HTML. This can be useful for improving the FCP (First Contentful Paint) score, especially when emulating slow network conditions.
   *
   * @default false
   */
  disablePreloadLinks?: boolean

  /**
   * Whether to remove the render-blocking stylesheets from the HTML. This only makes sense if styles are inlined during SSR rendering. To only prevent the `entry.<hash>.css` stylesheet from being rendered, set to `entry`. If set to `true`, all stylesheet links will not be rendered.
   *
   * @remarks
   * This requires to have the Nuxt `inlineStyles` feature enabled. Make sure to test your application after enabling this option.
   *
   * @default false
   */
  disableStylesheets?: boolean | 'entry'

  /**
   * Options for the `DelayHydration` component.
   */
  delayHydration?: {
    /**
     * Specify the events that should trigger hydration.
     *
     * @default ['mousemove', 'scroll', 'keydown', 'click', 'touchstart', 'wheel']
     */
    hydrateOnEvents?: (keyof WindowEventMap)[]
    /**
     * The maximum amount of time to wait in milliseconds when waiting for an idle callback. This is useful when there are a lot of network requests happening.
     *
     * @default 8000
     */
    idleCallbackTimeout?: number
    /**
     * Time to wait in milliseconds after the idle callback before hydrating the component.
     *
     * @default 4000
     */
    postIdleTimeout?: number
  }
}

💻 開発

  1. このリポジトリをクローンします
  2. corepack enable を使用して Corepack を有効にします
  3. pnpm install を使用して依存関係をインストールします
  4. pnpm run dev:prepare を実行します
  5. pnpm run dev を使用して開発サーバーを開始します

クレジット

  • このモジュールにインスピレーションを与えてくれた、Nuxt GitHub の問題に関するすべての議論と貢献。
  • @harlan-zw の素晴らしい nuxt-delay-hydration モジュール。

ライセンス

MIT ライセンス © 2024-現在 Johann Schopplich