🛠️ Next.js 15で遭遇するTypeScriptエラーの解決法 - 実践的トラブルシューティングガイド


こんにちは、@YushiYamamotoです！

Next.js 15の登場で開発環境が大幅に進化しましたが、同時に新たなTypeScriptのエラーに悩まされる開発者も増えています。特にアップグレードしたプロジェクトでは「昨日まで動いていたコードが突然エラーに...」という経験をされた方も多いのではないでしょうか？

私も複数のプロジェクトで遭遇した TypeScript エラーとその解決策を、実際のコード例とともに解説します。特に動的ルートのパラメータ処理や非同期関数の型定義の変更は、多くの開発者が頭を抱える原因となっています。

この記事を読めば、Next.js 15 の型システムを理解し、スムーズな開発体験を取り戻せるはずです。それでは早速見ていきましょう！

## 📋 目次

1. [Next.js 15で変わったこと](#next.js-15で変わったこと)
2. [動的ルートパラメータの型エラー](#動的ルートパラメータの型エラー)
3. [サーバーコンポーネントの非同期処理](#サーバーコンポーネントの非同期処理)
4. [APIルートの型定義](#apiルートの型定義)
5. [フォーム処理での型エラー](#フォーム処理での型エラー)
6. [クライアントコンポーネントでの型チェック](#クライアントコンポーネントでの型チェック)
7. [ESLint関連の型エラー](#eslint関連の型エラー)
8. [まとめ：プロジェクト全体の型安全性を高める方法](#まとめプロジェクト全体の型安全性を高める方法)

## Next.js 15で変わったこと

Next.js 15では、TypeScript 5.xへの対応と合わせて、より厳格な型チェックが導入されました。主な変更点は次のとおりです：

- **App Routerでの型強化**: より厳密な型定義によって安全性が向上
- **動的パラメータの非同期化**: パラメータがPromiseとして扱われるように
- **サーバーアクションの型改善**: FormDataやリクエスト/レスポンスの型定義強化
- **ESLint v9との統合**: 新しいフラットコンフィグ形式の採用

これらの変更は開発体験を向上させるためのものですが、既存コードと衝突して多くの型エラーを引き起こしています。

## 動的ルートパラメータの型エラー

Next.js 15の最も顕著な変更点は、動的ルートのパラメータが非同期（Promise）になったことです。

### エラー例

```
Type error: Type 'Props' does not satisfy the constraint 'PageProps'.
  Types of property 'params' are incompatible.
    Type '{ slug: string; }' is missing the following properties from type 'Promise': then, catch, finally, [Symbol.toStringTag]
```

このエラーは、`params`が`Promise`として扱われるようになったのに、同期的なオブジェクトとして定義しているために発生します。

### 💡 解決方法

```typescript
// 以前の書き方（エラーになる）
export default function PostPage({ params }: { params: { slug: string } }) {
  const post = getPostBySlug(params.slug);
  // ...
}

// 正しい書き方
export default async function PostPage({ params }: { params: Promise }) {
  const { slug } = await params;
  const post = getPostBySlug(slug);
  // ...
}
```

`generateMetadata`関数も同様に修正が必要です：

```typescript
// 以前の書き方
export async function generateMetadata({ params }: { params: { slug: string } }) {
  // ...
}

// 正しい書き方
export async function generateMetadata({ params }: { params: Promise }): Promise {
  const { slug } = await params;
  // ...
}
```

### 🔄 非同期パラメータのフロー

次の図は、動的ルートパラメータの処理フローの変化を示しています：

```
【Next.js 14までの処理フロー】
URL解析 → パラメータ抽出 → コンポーネントにパラメータ直接渡し → レンダリング

【Next.js 15の処理フロー】
URL解析 → パラメータ抽出 → Promise化 → コンポーネントでawait → 値取得 → レンダリング
```

この変更によって、パラメータの解決が非同期になり、より複雑なルーティングシナリオに対応できるようになりました。

## サーバーコンポーネントの非同期処理

Next.js 15ではサーバーコンポーネントの非同期処理の型定義も強化されています。

### エラー例

```
TypeError: Cannot read properties of undefined (reading 'slug')
```

このエラーは、Promise型のパラメータを`await`せずに使用しようとした場合に発生します。

### 💡 解決方法

サーバーコンポーネントでは、すべての非同期処理を適切に`async/await`で処理する必要があります：

```typescript
// app/dashboard/[orgId]/page.tsx
export default async function DashboardPage({
  params
}: {
  params: Promise
}) {
  // パラメータを適切にawaitする
  const { orgId } = await params;
  
  // データフェッチも非同期処理
  const orgData = await fetchOrganizationData(orgId);
  
  return (
    
      {orgData.name} ダッシュボード
      {/* コンテンツ */}
    
  );
}
```

## APIルートの型定義

APIルート（Route Handlers）でも型定義が厳格化されました。

### エラー例

```
Type error: Parameter 'request' implicitly has an 'any' type.
```

このエラーは、APIルートのハンドラー関数で型注釈が不足している場合に発生します。

### 💡 解決方法

```typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

// リクエストとレスポンスに適切な型を指定
export async function GET(request: NextRequest): Promise {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query') || '';
  
  // データ取得ロジック
  const users = await db.users.findMany({
    where: { name: { contains: query } }
  });
  
  return NextResponse.json(users);
}

export async function POST(request: NextRequest): Promise {
  try {
    const body = await request.json();
    
    // データバリデーション
    if (!body.name || !body.email) {
      return NextResponse.json(
        { error: '必須フィールドが未入力です' },
        { status: 400 }
      );
    }
    
    // データ保存ロジック
    const newUser = await db.users.create({ data: body });
    
    return NextResponse.json(newUser, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: 'サーバーエラーが発生しました' },
      { status: 500 }
    );
  }
}
```

## フォーム処理での型エラー

フォーム処理に関する型エラーも多く見られます。

### エラー例

```
Type error: Parameter 'e' implicitly has an 'any' type.
```

このエラーは、イベントハンドラーの引数に型が指定されていない場合に発生します。

### 💡 解決方法

```typescript
'use client';
import { useState, FormEvent, ChangeEvent } from 'react';

export default function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });
  
  // 適切なイベント型を指定
  const handleChange = (e: ChangeEvent) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };
  
  // フォームイベントの型も指定
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    
    try {
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData)
      });
      
      if (!response.ok) throw new Error('送信に失敗しました');
      
      // 成功処理
    } catch (error) {
      // エラー処理
    }
  };
  
  return (
    
      
      {/* 他のフォーム要素 */}
      送信
    
  );
}
```

### ⚠️ 注意点

フォーム要素の型を指定する際、次のような共通のエラーがあります：

1. **セレクトボックスの型不足**: `HTMLSelectElement`を含めない
2. **複数の要素タイプの混在**: 異なる要素タイプを使う場合はユニオン型で対応
3. **カスタム要素の型**: サードパーティUIライブラリ使用時に型定義が必要

## クライアントコンポーネントでの型チェック

クライアントコンポーネントでは、特にプロップスの型定義が重要です。

### エラー例

```
Type '{ children: string; onClick: () => void; }' is not assignable to type 'IntrinsicAttributes'.
  Property 'children' does not exist on type 'IntrinsicAttributes'.
```

このエラーは、コンポーネントのプロップスの型定義が不足している場合に発生します。

### 💡 解決方法

```typescript
'use client';

// コンポーネントのpropsに明示的な型を定義
type ButtonProps = {
  children: React.ReactNode;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
};

export default function Button({ 
  children, 
  onClick, 
  variant = 'primary', 
  disabled = false 
}: ButtonProps) {
  return (
    
      {children}
    
  );
}
```

### 📝 オプション型の活用

オプショナルプロパティには `?` を使用して、より柔軟なコンポーネントを作成できます：

```typescript
type CardProps = {
  title: string;
  description: string;
  image?: string;  // オプショナル
  tags?: string[];  // オプショナル
  onClick?: () => void;  // オプショナル
};

export default function Card({ title, description, image, tags = [], onClick }: CardProps) {
  return (
    
      {image && }
      {title}
      {description}
      {tags.length > 0 && (
        
          {tags.map(tag => {tag})}
        
      )}
      {onClick && 詳細を見る}
    
  );
}
```

## ESLint関連の型エラー

Next.js 15ではESLint v9が採用され、型チェックに関連する警告も増えています。

### エラー例

```
Failed to compile.
'Image' is defined but never used.  @typescript-eslint/no-unused-vars
'Link' is defined but never used.  @typescript-eslint/no-unused-vars
'error' is defined but never used.  @typescript-eslint/no-unused-vars
```

### 💡 解決方法

ESLint設定を新しいフラットコンフィグ形式で更新します：

```javascript
// eslint.config.mjs
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [
  ...compat.config({
    extends: ["next/core-web-vitals", "next/typescript"],
    rules: {
      "@typescript-eslint/no-unused-vars": "warn", // エラーから警告に変更
      // または特定のルールを無効化
      // "@typescript-eslint/no-unused-vars": "off"
    }
  }),
];

export default eslintConfig;
```

### 🛠️ 一時的なビルド対応

すぐに修正できない場合は、`next.config.js`で一時的にESLintチェックを無視することもできます：

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  // ...他の設定
  eslint: {
    ignoreDuringBuilds: true, // ビルド時のESLintチェックをスキップ
  },
}

module.exports = nextConfig
```

ただし、これは一時的な対処法であり、長期的には適切に型定義を修正することをお勧めします。

## まとめ：プロジェクト全体の型安全性を高める方法

Next.js 15での型エラー解決は、単に個々のエラーを直すだけでなく、プロジェクト全体の型安全性を高める良い機会です。

### 推奨アプローチ

1. **tsconfig.jsonの最適化**: 厳格なチェックを有効にする
   ```json
   {
     "compilerOptions": {
       "strict": true,
       "noImplicitAny": true,
       "strictNullChecks": true,
       "noUncheckedIndexedAccess": true,
       "exactOptionalPropertyTypes": true
     }
   }
   ```

2. **型定義ファイルの集約**: プロジェクト固有の型を集約管理
   ```typescript
   // types/index.ts
   export type User = {
     id: string;
     name: string;
     email: string;
     role: 'admin' | 'user';
     createdAt: Date;
   };
   
   export type Post = {
     id: string;
     title: string;
     content: string;
     published: boolean;
     authorId: string;
     createdAt: Date;
     updatedAt: Date;
   };
   
   // APIのレスポンス型も定義
   export type ApiResponse = {
     success: boolean;
     data?: T;
     error?: string;
   };
   ```

3. **Zodによるランタイムバリデーション**: 型安全性をランタイムにも拡張
   ```typescript
   import { z } from 'zod';
   
   // ユーザー入力のバリデーションスキーマ
   const contactFormSchema = z.object({
     name: z.string().min(1, '名前は必須です'),
     email: z.string().email('有効なメールアドレスを入力してください'),
     message: z.string().min(10, 'メッセージは10文字以上で入力してください')
   });
   
   // 型の抽出
   type ContactFormData = z.infer;
   
   // フォーム送信時の使用例
   const handleSubmit = async (formData: unknown) => {
     // ランタイムでの型チェックと検証
     const result = contactFormSchema.safeParse(formData);
     
     if (!result.success) {
       // バリデーションエラーの処理
       console.error(result.error.format());
       return;
     }
     
     // 検証済みデータの使用
     const validatedData = result.data;
     // APIリクエストなど
   };
   ```

4. **型安全なAPIクライアント**: サーバーとクライアント間の型一貫性を確保
   ```typescript
   // lib/api.ts
   import { ApiResponse, User, Post } from '@/types';
   
   export async function fetchUsers(): Promise> {
     const response = await fetch('/api/users');
     return response.json();
   }
   
   export async function fetchPost(id: string): Promise> {
     const response = await fetch(`/api/posts/${id}`);
     return response.json();
   }
   ```

Next.js 15での型定義の変更は、一時的には煩わしく感じるかもしれませんが、長期的には型安全性が向上し、堅牢なアプリケーション開発につながります。エラーを一つずつ解決しながら、より良い型システムを構築していきましょう。

TypeScriptとNext.jsの組み合わせは、開発体験とコード品質を大きく向上させる強力なツールです。型エラーと向き合うことで、より安全で保守性の高いコードが書けるようになります。ぜひ、これらのテクニックを活用して、Next.js 15の新機能を最大限に活用してください！

---

### 最後に：業務委託のご相談を承ります
私は業務委託エンジニアとしてWEB制作やシステム開発を請け負っています。最新技術を活用したレスポンシブなWebサイト制作、インタラクティブなアプリケーション開発、API連携など幅広いご要望に対応可能です。

「課題解決に向けた即戦力が欲しい」「高品質なWeb制作を依頼したい」という方は、お気軽にご相談ください。一緒にビジネスの成長を目指しましょう！

👉 [ポートフォリオ](https://yushiyamamoto.vercel.app/)

🌳 [らくらくサイト](https://rakuraku-site.prodouga.com/)