3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

こんにちは、@YushiYamamotoです!

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

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

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

📋 目次

  1. Next.js 15で変わったこと
  2. 動的ルートパラメータの型エラー
  3. サーバーコンポーネントの非同期処理
  4. APIルートの型定義
  5. フォーム処理での型エラー
  6. クライアントコンポーネントでの型チェック
  7. 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]

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

💡 解決方法

// 以前の書き方(エラーになる)
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関数も同様に修正が必要です:

// 以前の書き方
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で処理する必要があります:

// 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ルートのハンドラー関数で型注釈が不足している場合に発生します。

💡 解決方法

// 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.

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

💡 解決方法

'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'.

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

💡 解決方法

'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}
    
  );
}

📝 オプション型の活用

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

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設定を新しいフラットコンフィグ形式で更新します:

// 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チェックを無視することもできます:

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

module.exports = nextConfig

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

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

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

推奨アプローチ

  1. tsconfig.jsonの最適化: 厳格なチェックを有効にする

    {
      "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "noUncheckedIndexedAccess": true,
        "exactOptionalPropertyTypes": true
      }
    }
    
  2. 型定義ファイルの集約: プロジェクト固有の型を集約管理

    // 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によるランタイムバリデーション: 型安全性をランタイムにも拡張

    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クライアント: サーバーとクライアント間の型一貫性を確保

    // 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制作を依頼したい」という方は、お気軽にご相談ください。一緒にビジネスの成長を目指しましょう!

👉 ポートフォリオ

🌳 らくらくサイト

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?