Skip to content

重要機能

📦 Lazy Loading(遅延ロード)とCode Splitting(コード分割)

Section titled “📦 Lazy Loading(遅延ロード)とCode Splitting(コード分割)”

Lazy Loadingとは、ユーザーが実際に必要とするまで、コンポーネントやアセットのロードを遅らせる技術です。Next.jsでは、これを簡単に実装できます。Code Splittingは、アプリケーション全体のJavaScriptバンドルを、小さなチャンクに分割するプロセスです。Next.jsはデフォルトでこれを自動的に行いますが、Lazy Loadingと組み合わせることで、さらに最適化できます。

1. Dynamic Imports(動的インポート)

Section titled “1. Dynamic Imports(動的インポート)”

Next.jsでLazy Loadingを実装する最も一般的な方法は、ReactのlazyやSuspenseと組み合わせて**Dynamic Imports(動的インポート)**を使うことです。これにより、特定のコンポーネントが画面に表示されるまで、そのコンポーネントのコードをロードしないように指示できます。

実装例:インタラクティブな地図コンポーネント

Section titled “実装例:インタラクティブな地図コンポーネント”

地図コンポーネントは通常、サイズが大きく、ページ全体のパフォーマンスを低下させることがあります。これをLazy Loadingで最適化します。

components/Map.tsx
// このコンポーネントは非常に大きいと仮定
'use client';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
export default function Map() {
return (
<MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: '400px', width: '100%' }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<Marker position={[51.505, -0.09]}>
<Popup>ここにいます!</Popup>
</Marker>
</MapContainer>
);
}

動的インポートで地図コンポーネントを遅延ロード

Section titled “動的インポートで地図コンポーネントを遅延ロード”

next/dynamicを使って、この地図コンポーネントを必要なときだけロードするようにします。

app/contact/page.tsx
'use client';
import dynamic from 'next/dynamic';
import LoadingSpinner from '@/components/LoadingSpinner';
// Dynamic ImportでMapコンポーネントを遅延ロード
const DynamicMap = dynamic(() => import('@/components/Map'), {
ssr: false, // サーバーサイドではレンダリングしない
loading: () => <LoadingSpinner />,
});
export default function ContactPage() {
return (
<div>
<h1>お問い合わせ</h1>
<p>私たちのオフィスはこちらです。</p>
{/* ユーザーがこの部分にスクロールするか、表示されるまでロードを遅延 */}
<DynamicMap />
</div>
);
}
  • ssr: false: このオプションは、コンポーネントがクライアントサイドでのみレンダリングされるように指定します。地図コンポーネントはブラウザのAPIに依存するため、この設定が重要です。
  • loading: コンポーネントがロードされる間に表示する代替UI(ローディングスピナーなど)を指定できます。

Next.jsのApp Routerは、ページやコンポーネントの単位で自動的にコードを分割します。これにより、ユーザーは必要なページのJavaScriptだけをダウンロードします。

  • : ユーザーが/aboutページにアクセスした場合、Next.jsは/aboutページに必要なコードのみをロードします。/contactページのコードはロードされません。

  • 利点:

    • バンドルサイズの削減: ページのレンダリングに必要なコードだけをロードするため、初期ロードが高速になります。
    • メモリ使用量の削減: 不要なコードをメモリにロードしないため、ブラウザのメモリ使用量を抑えられます。

Next.jsのnext/imageコンポーネントも、デフォルトで画像のLazy Loadingをサポートしています。

  • 仕組み: 画面外の画像は、ユーザーがスクロールして表示領域に入るまでロードされません。これにより、ページのLCP(Largest Contentful Paint)スコアが改善され、ページの初期ロードが高速になります。

  • 使用法: <img>タグの代わりにnext/imageコンポーネントを使用するだけです。

import Image from 'next/image';
function ProductImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={500}
height={300}
priority // この画像は優先してロードする
/>
);
}

next/imageは、画像のサイズを自動的に最適化し、ブラウザがサポートする最新の形式(WebPなど)で提供する機能も備えています。

📦 Next.jsにおけるキャッシュ戦略

Section titled “📦 Next.jsにおけるキャッシュ戦略”

Next.jsのApp Routerは、デフォルトでサーバーコンポーネントのキャッシュ機能を内蔵しています。これにより、同じデータを繰り返しフェッチすることなく、高速なレンダリングが可能になります。キャッシュには主に3つの種類があり、それぞれが異なる役割を果たします。

1. Request Memoization (リクエストメモ化)

Section titled “1. Request Memoization (リクエストメモ化)”

リクエストメモ化は、同じリクエスト内で、重複するデータフェッチを自動的に排除する仕組みです。fetch()関数やReactのcache()APIがこれを実現します。

  • 仕組み: サーバーコンポーネント内で同じURLに対して複数のfetch()呼び出しがあっても、Next.jsは1回だけリクエストを送信し、その結果をキャッシュして他の呼び出しで再利用します。

  • 利点:

    • サーバーリクエストの削減: 複数のコンポーネントが同じデータを必要とする場合に、重複したAPI呼び出しを防ぎます。
    • 開発体験の向上: 開発者はデータの重複を気にせず、各コンポーネントで必要なデータを自由にフェッチできます。
app/products/[id]/page.tsx
async function getProduct(id: string) {
// 同じリクエスト内で複数回呼び出されても、このAPIは1回だけ実行される
const res = await fetch(`https://api.example.com/products/${id}`);
return res.json();
}
export default async function ProductPage({ params }: { params: { id: string } }) {
// ページのヘッダーで同じデータを取得
const headerProduct = await getProduct(params.id);
// ページのメインコンテンツで同じデータを取得
const mainProduct = await getProduct(params.id);
// ... 実際には1回のAPIリクエストしか発生しない
return (
<div>
<h1>{mainProduct.name}</h1>
{/* ... */}
</div>
);
}

2. Full Route Cache (フルルートキャッシュ)

Section titled “2. Full Route Cache (フルルートキャッシュ)”

フルルートキャッシュは、レンダリングされたサーバーコンポーネントツリー全体をキャッシュする仕組みです。

  • 仕組み: 初回リクエスト時にレンダリングされたサーバーコンポーネントのHTMLとReact Server Component Payload (RSC)をキャッシュします。同じルートに再度アクセスがあった場合、サーバーは再レンダリングをせず、キャッシュされたコンテンツをすぐに返します。

  • 利点:

    • 高速なページ表示: ユーザーは瞬時にキャッシュされたページを見ることができます。
    • サーバー負荷の軽減: 繰り返し同じページがリクエストされても、サーバーの処理が不要になります。
  • 有効化/無効化:

    • デフォルトで有効です。
    • 動的な関数(cookies()headers()など)を使用したり、**動的なレンダリング(Dynamic Rendering)**を強制すると、このキャッシュは無効になります。

データキャッシュは、fetch() APIの結果をサーバーに永続的に保存する仕組みです。これにより、同じAPIリクエストが複数回発生しても、再フェッチすることなくキャッシュされたデータを再利用できます。

  • 仕組み:
    • デフォルト: fetch()は、**再検証(revalidation)が設定されていない限り、永続的にキャッシュされます。これは静的サイト生成(SSG)**に似ています。
    • 再検証(Revalidation): 一定時間経過後(revalidateオプション)や、特定のイベント時(revalidatePathrevalidateTag)にキャッシュを無効化し、最新のデータを再フェッチできます。
app/blog/page.tsx
// 60秒ごとにキャッシュを再検証する
export async function getPosts() {
const res = await fetch('https://api.example.com/posts', { next: { revalidate: 60 } });
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
// ... 投稿リストを表示
}

これらのキャッシュ戦略は、Next.jsがパフォーマンスを最適化する上で欠かせない機能です。適切に活用することで、アプリケーションの応答速度を大幅に向上させることができます。

🌐 Server Actions (サーバーアクション)

Section titled “🌐 Server Actions (サーバーアクション)”

Server Actionsは、Next.js 13.4で導入された機能で、サーバーコンポーネント内から直接サーバー側の関数を実行できるようにするものです。これにより、APIルートを明示的に作成することなく、フォームの送信やデータベースの更新といった処理を簡潔に実装できます。

  • 特徴:
    • シンプルさ: クライアントからサーバーへデータを送信する際のボイラープレートコード(APIルートの定義、クライアントでのfetch呼び出しなど)が不要になります。
    • パフォーマンス: フォームのデータが直接サーバー関数に渡されるため、クライアントへのJavaScriptの送信量が削減されます。
    • 安全性: クライアントコンポーネントから直接サーバーのアクションを呼び出すことも可能ですが、Server Actionsは自動的にCSRF攻撃などのセキュリティリスクを軽減します。
app/contact/page.tsx
import { revalidatePath } from 'next/cache';
import { saveMessage } from '@/lib/messages'; // データベースに保存するサーバー側の関数
// Server Actionとして定義
async function createMessage(formData: FormData) {
'use server'; // Server Actionを有効化
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const message = formData.get('message') as string;
await saveMessage({ name, email, message });
// キャッシュを無効化し、ページを最新の状態に再検証
revalidatePath('/contact');
}
export default function ContactPage() {
return (
<form action={createMessage}>
<input type="text" name="name" placeholder="お名前" />
<input type="email" name="email" placeholder="メールアドレス" />
<textarea name="message" placeholder="メッセージ"></textarea>
<button type="submit">送信</button>
</form>
);
}

この例では、<form>要素のaction属性に直接サーバー側の関数createMessageを指定しています。これにより、フォームが送信されると、JavaScriptを使わずにサーバー上で直接処理が実行されます。

Next.jsのApp Routerは、ファイルシステムベースのルーティングを採用しており、ディレクトリ構造がそのままURLのパスになります。

  • 特徴:
    • シンプルさ: app/ディレクトリ内に新しいフォルダとpage.tsxファイルを作成するだけで、新しいルートが自動的に作成されます。
    • 動的ルート: [slug]のようにフォルダ名を角括弧で囲むことで、動的なURLを作成できます。
    • : app/blog/[slug]/page.tsx/blog/my-first-post のようなURLに対応します。
    • ネストされたルートとレイアウト: ルートはディレクトリとしてネストでき、layout.tsxファイルを使って共通のUI(ヘッダーやフッターなど)を複数のページで共有できます。
    • : app/dashboard/layout.tsx は、app/dashboard/settings/page.tsxapp/dashboard/profile/page.tsx の両方に適用されます。

実装例: ネストされたルートとレイアウト

Section titled “実装例: ネストされたルートとレイアウト”
app/dashboard/layout.tsx
// ダッシュボードの共通レイアウト
export default function DashboardLayout({ children }) {
return (
<div>
<nav>
<a href="/dashboard">ホーム</a>
<a href="/dashboard/settings">設定</a>
</nav>
<main>{children}</main>
</div>
);
}
// app/dashboard/settings/page.tsx
// 設定ページ
export default function SettingsPage() {
return (
<h1>設定</h1>
);
}

この構造により、開発者はコンポーネントを再利用しやすく、アプリケーションの全体的な構造を整理しやすくなります。Server Actionsとルーティングは、Next.jsが提供する強力な機能の一部であり、より効率的で現代的なウェブアプリケーション開発を可能にします。

APIルートは、Next.jsアプリケーション内にサーバーサイドのAPIエンドポイントを作成する機能です。これにより、外部APIを呼び出したり、データベースとやり取りしたりするなど、サーバーサイドのロジックを実装できます。App Routerでは、app/api/ディレクトリ内にRoute Handlersを作成します。

  • 特徴:
    • サーバーサイドのロジック: クライアントに公開したくない機密情報(APIキーなど)を安全に扱うことができます。
    • RESTful APIの構築: GET、POST、PUT、DELETEなどのHTTPメソッドに対応し、RESTfulなAPIを簡単に構築できます。
    • データベースとの連携: 認証情報を使ってデータベースを操作したり、外部サービスと連携したりできます。
app/api/user/[id]/route.ts
import { NextResponse } from 'next/server';
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
try {
const { id } = params;
// データベースからユーザー情報を取得するロジック
const user = await db.getUser(id);
if (!user) {
return NextResponse.json({ message: 'User not found' }, { status: 404 });
}
return NextResponse.json(user);
} catch (error) {
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
}
}

この例では、app/api/user/123のようなリクエストに対して、サーバーサイドでユーザーIDを取得し、データベースから情報をフェッチしてJSON形式で返します。

ミドルウェアは、リクエストが完了する前に、特定のパスでコードを実行できる機能です。これにより、リクエストヘッダーの書き換え、リダイレクト、認証、A/Bテストなど、様々な処理を一元的に管理できます。

  • 特徴:
    • グローバルなロジック: アプリケーション全体に適用されるロジックを、各ページやAPIルートごとに書くことなく実装できます。
    • リクエストの制御: ユーザーを別のページにリダイレクトしたり、特定の条件下でリクエストをブロックしたりできます。
    • 認証・認可: ログイン済みのユーザーのみが特定のページにアクセスできるようにするなどの認証チェックを簡単に行えます。
middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
const loggedIn = !!token;
const url = request.nextUrl.clone();
// 認証が必要なルート
const protectedRoutes = ['/dashboard', '/profile'];
// 認証が必要なルートにアクセスしようとしているが、ログインしていない場合
if (protectedRoutes.includes(url.pathname) && !loggedIn) {
url.pathname = '/login';
return NextResponse.redirect(url);
}
// ログイン済みだが、ログインページにアクセスしようとしている場合
if (url.pathname === '/login' && loggedIn) {
url.pathname = '/dashboard';
return NextResponse.redirect(url);
}
return NextResponse.next();
}

middleware.tsファイルは、Next.jsのプロジェクトルートに配置することで自動的に認識されます。このミドルウェアは、ユーザーが保護されたページにアクセスする前に認証トークンをチェックし、ログインページへリダイレクトする役割を果たします。

Tailwind CSSは、ユーティリティファーストのCSSフレームワークで、HTMLに直接クラスを追加することで、迅速かつ柔軟にUIを構築できます。Next.jsは、Tailwind CSSとの統合を公式にサポートしており、セットアップが非常に簡単です。

  • 特徴:
    • 開発効率の向上: m-4, flex, text-centerといったユーティリティクラスを使うことで、カスタムCSSをほとんど書く必要がなくなります。
    • 一貫したデザイン: フレームワークが提供するデザインシステム(カラーパレット、スペーシング、フォントサイズなど)により、デザインの一貫性を保ちやすくなります。
    • 不要なCSSの除去: Tailwindのビルドプロセスは、使用されていないCSSクラスを自動的に削除し、本番環境のバンドルサイズを最小限に抑えます。

create-next-appコマンドでプロジェクトを作成する際に、Would you like to use Tailwind CSS?の質問にYesと答えるだけで、必要な設定がすべて自動で行われます。

Terminal window
npx create-next-app@latest

手動でセットアップする場合は、以下の手順を実行します。

必要なパッケージをインストール:

Section titled “必要なパッケージをインストール:”
Terminal window
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

コンテンツファイル(appディレクトリなど)へのパスを設定します。

tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
};
export default config;

globals.cssにTailwindのディレクティブを追加:

Section titled “globals.cssにTailwindのディレクティブを追加:”
app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Next.jsプロジェクトの品質と信頼性を確保するために、ユニットテストは不可欠です。Next.jsの公式ドキュメントでは、テストフレームワークとしてJestとReact Testing Libraryの組み合わせが推奨されています。

  • 特徴:
    • Jest: JavaScriptのテストフレームワークで、テストの実行、アサーション、モックなどの機能を提供します。
    • React Testing Library (RTL): コンポーネントのテストに特化したライブラリで、ユーザーがコンポーネントとどのように対話するかをシミュレートすることに焦点を当てています。これにより、実装の詳細に依存しない堅牢なテストを作成できます。

必要なパッケージをインストール:

Section titled “必要なパッケージをインストール:”

create-next-appでテスト環境をセットアップしていない場合、以下をインストールします。

Terminal window
npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

jest.config.tsファイルを作成し、jest-environment-jsdom@testing-library/jest-domを使用するように設定します。

例えば、シンプルな「こんにちは」を表示するコンポーネントをテストします。

components/Greeting.tsx
export default function Greeting({ name }: { name: string }) {
return <h1>こんにちは、{name}!</h1>;
}
__tests__/Greeting.test.tsx
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import Greeting from '@/components/Greeting';
describe('Greeting', () => {
it('should render a greeting with the provided name', () => {
render(<Greeting name="太郎" />);
// 「こんにちは、太郎!」というテキストがドキュメントに存在するか確認
expect(screen.getByText('こんにちは、太郎!')).toBeInTheDocument();
});
});

このテストは、コンポーネントが正しくテキストをレンダリングしているかを確認します。RTLは、UIが正しくユーザーに表示されているかをテストする上で非常に強力です。

これらの機能は、Next.jsが提供するエコシステムの一部であり、開発者がモダンで高品質なウェブアプリケーションを効率的に構築するための基盤となります。