💨

Next.jsパフォーマンス最適化:コンポーネント復元と高度な遅延読み込み記録

に公開3

問題の再発見

前回の最適化でナビゲーション遅延は解決しましたが、でも:

  • 削除したコンポーネント(Statistics, Certifications, ReadingList, Blog)が実際には必要
  • すべての機能を維持しながらパフォーマンスを向上させる必要な場合はどうする?

新しいアプローチ

コンポーネントの完全復元

すべてのコンポーネントを復元:

  • Statistics(統計データ表示)
  • Certifications(資格証明一覧)
  • ReadingList(読書リスト)
  • Blog(ブログ記事表示)
  • ナビゲーションメニューの全項目

インテリジェントな読み込み戦略

単純な削除ではなく、高度な読み込み最適化を実装しました。これにより、初期読み込みパフォーマンスを維持しながら、全ての機能を提供することが可能になりました。

実装したソリューション

1. インテリジェント遅延読み込みコンポーネント

方針:データ取得はサーバ(RSC)側で行い、クライアントでは水和のタイミングだけを Intersection Observer / Suspense で遅らせます(RSC/Streaming の設計に沿う)

// components/LazyLoader.tsx
'use client';

import { useEffect, useRef, useState, ReactNode } from 'react';

export default function LazyLoader({ 
  children, 
  threshold = 0.1, 
  rootMargin = '100px'
}) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { threshold, rootMargin }
    );

    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [threshold, rootMargin]);

  return (
    <div ref={ref}>
      {isVisible ? children : (
        <div className="w-full h-96 bg-gradient-to-br from-gray-100 to-gray-200 animate-pulse rounded-lg" />
      )}
    </div>
  );
}

このLazyLoaderコンポーネントは、Intersection Observer APIを使用して、コンポーネントがビューポートに表示される直前にのみ読み込みを開始します。ユーザーが実際に見る必要があるコンテンツだけを読み込むことで、初期読み込み時間を大幅に短縮します。

2. 階層化された読み込み戦略

dynamic(..., { ssr:false }) はブラウザ API 依存などSSR 不可な最小箇所に限定し、その他は SSR/RSC に参加させます(公式 “Lazy loading client components and libraries” 推奨)。

// コアコンポーネント - 即時読み込み
const Hero = dynamic(() => import('@/components/Hero'));
const Skills = dynamic(() => import('@/components/Skills'));
const ProjectsShowcase = dynamic(() => import('@/components/ProjectsShowcase'));
const Contact = dynamic(() => import('@/components/Contact'));

// セカンダリコンポーネント - 積極的遅延読み込み
const Statistics = dynamic(() => import('@/components/Statistics'));
const Certifications = dynamic(() => import('@/components/Certifications'));
const ReadingList = dynamic(() => import('@/components/ReadingList'));
const Blog = dynamic(() => import('@/components/Blog'));

このアプローチにより、ページの初期表示に不可欠なコンポーネントはすぐに読み込まれますが、スクロールしてから表示されるコンポーネントは後から読み込まれるため、初期ロードパフォーマンスが向上します。

技術的な学び

1. Intersection Observerの有効活用

ビューポート内でのみコンポーネントを読み込むことで、初期読み込み時間を大幅に改善しました。Intersection Observer APIは、ユーザーの実際のスクロール行動に合わせてリソースをロードするための理想的なツールです。

2. 段階的な読み込み戦略

すべてのコンポーネントを同じように扱うのではなく、重要性に基づいた読み込み優先度を設定することで、ユーザー体験とパフォーマンスのバランスを取ることができました。

実装のベストプラクティス

  1. コンポーネントの分類: コア機能とセカンダリ機能を明確に分離し、読み込みの優先度を設定しました。
  2. プログレッシブ表示: 読み込み中のユーザー体験を考慮したプレースホルダー表示を実装し、待ち時間を感じさせないようにしました。

結論

学生として、日々の学びを活かしながら、より良いWebアプリケーションの開発を目指していきたいと思います。

Discussion

Honey32Honey32

失礼します。一連の記事を拝見したのですが

  • 極端で場当たり的な対応なのにもかかわらず、堂々と効果を謳っている
    • あと、見出しで、動詞句ではなく名詞句を多用している
  • 記事では肝心な部分(コンポーネント間の依存関係とか、どれが Server Component でどれが Client Component とか…)が端折られていて、書かれている施策が効果的なのか判断できない

という部分からの邪推になりますが、実装の改善または方針ぎめを AI に任せていらっしゃいますか?

上記のような点から鑑みるに、このまま進みつづけると、AI の場当たり的な判断に振り回されてしまうのではと老婆心で心配になってしまいます。

もしまだであれば、

  • 何らかの方法で AI に Next.js 公式ドキュメントを、情報源として読み込ませる

などして、AI が正確な情報に基づいた判断をできるようにする必要があるのでは、と思います。

勝手な思い込みでしたらすみません🙇‍♂️

ACAneACAne

ご指摘ありがとうございます。
学生として手探りで取り組んでおり、今回は初めての Next.js 個人サイトでの試行錯誤を記録した記事です。実装は公式ドキュメントを軸に、必要に応じて AI を補助的に使用しています。ご指摘のとおり、依存関係や Server/Client の切り分け、計測指標など肝心な背景の説明が不足していました。今後は根拠や再現手順も含めて補足します。

また、今回の変更後に EN/JP の言語切り替えで遅延が生じていることも把握しており、読み込み戦略を見直して対応中です。

見出しや言い回しは英語で下書きしてから日本語に訳したため、不自然な表現があったかもしれません。以後は日本語のニュアンスにも配慮します。今後ともご指導のほど、よろしくお願いいたします。

Honey32Honey32

「英語→日本語で翻訳執筆」の可能性を考慮できておらず、失礼しました… 🙇

言語の切替機能、たしかに(僕自身経験がありませんが)厄介なことになりそうですが、頑張ってください!

もしかしたら、このページが参考になるかもしれないので、共有しておきます!

https://nextjs.org/docs/app/getting-started/fetching-data