こんにちは、@YushiYamamotoです!
Next.js 15の登場で開発環境が大幅に進化しましたが、同時に新たなTypeScriptのエラーに悩まされる開発者も増えています。特にアップグレードしたプロジェクトでは「昨日まで動いていたコードが突然エラーに...」という経験をされた方も多いのではないでしょうか?
私も複数のプロジェクトで遭遇した TypeScript エラーとその解決策を、実際のコード例とともに解説します。特に動的ルートのパラメータ処理や非同期関数の型定義の変更は、多くの開発者が頭を抱える原因となっています。
この記事を読めば、Next.js 15 の型システムを理解し、スムーズな開発体験を取り戻せるはずです。それでは早速見ていきましょう!
📋 目次
- Next.js 15で変わったこと
- 動的ルートパラメータの型エラー
- サーバーコンポーネントの非同期処理
- APIルートの型定義
- フォーム処理での型エラー
- クライアントコンポーネントでの型チェック
- ESLint関連の型エラー
- まとめ:プロジェクト全体の型安全性を高める方法
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
として扱われるようになったのに、同期的なオブジェクトとして定義しているために発生します。
💡 解決方法
// 以前の書き方(エラーになる)
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 (
{/* 他のフォーム要素 */}
送信
);
}
⚠️ 注意点
フォーム要素の型を指定する際、次のような共通のエラーがあります:
-
セレクトボックスの型不足:
HTMLSelectElement
を含めない - 複数の要素タイプの混在: 異なる要素タイプを使う場合はユニオン型で対応
- カスタム要素の型: サードパーティ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での型エラー解決は、単に個々のエラーを直すだけでなく、プロジェクト全体の型安全性を高める良い機会です。
推奨アプローチ
-
tsconfig.jsonの最適化: 厳格なチェックを有効にする
{ "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true } }
-
型定義ファイルの集約: プロジェクト固有の型を集約管理
// 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; };
-
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リクエストなど };
-
型安全な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制作を依頼したい」という方は、お気軽にご相談ください。一緒にビジネスの成長を目指しましょう!
👉 ポートフォリオ
🌳 らくらくサイト