2026-10-12 訂正:Promise・generic・イベント型・JSXの欠落、入力全体のDB保存、JSON不正の扱い、lint回避を訂正しました。複数プロジェクトでの本人経験は確認できていないため撤回し、仕様確認と独立した入力検証例として記載します。
Nextの変更とTypeScriptの既存規則を分ける
Next.js 15更新ガイドが示すAsync Request APIsにはparams/searchParams等があります。これは単なる型チェック厳格化ではなくAPIの変更です。15の一時的な同期アクセス互換を完成した修正と扱わず、対象APIと呼び出し側を移行します。
一方、引数が暗黙anyになる指摘はTypeScriptのnoImplicitAnyという既存の設定に由来します。すべての型エラーをNext.js 15が新しく導入した問題とは説明できません。採用するNext/React/TypeScriptと型package・lintの互換を確認します。実行時TypeErrorとコンパイル時の型エラーも区別します。
paramsとmetadataに型引数を戻す
以下はapp/posts/[slug]/page.tsx用の構文例です。実API取得を装わず、slugを表示するだけにしています。JSXを含むファイルは.tsxです。
import type { Metadata } from 'next'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
return { title: `記事 ${slug}` }
}
export default async function PostPage({ params }: Props) {
const { slug } = await params
return <main><h1>記事 {slug}</h1></main>
}
旧Promiseだけの注釈には型引数が欠けていました。組織ルートならPromise<{ orgId: string }>のようにURLの形へ合わせます。実データ取得では取得関数の戻り値を定義し、識別子・認可・不在と障害を扱います。例外メッセージだけで原因を決めず、stackとAPI変更、取得結果を確認します。awaitが必要な値を確認し、無関係な同期処理まですべて非同期化しません。
API型とJSONは別の境界
success時のdataとfailure時のerrorを必須にするdiscriminated unionの例です。Tを宣言し、Promiseへ具体的な型を指定します。
export type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string }
export type UserDto = {
id: string
name: string
createdAt: string
}
export async function readJson(response: Response): Promise<unknown> {
if (!response.ok) throw new Error('HTTP応答が失敗しました')
const value: unknown = await response.json()
return value
}
createdAtをJSONで送るなら通常はISO文字列です。DBのDate型とDTOを同一視せず、変換と有効性を検証します。readJsonはHTTP失敗を扱いますがUserDtoの形はまだ保証しません。呼出側はschema等でunknownを検証してから扱います。as UserDtoや戻り値の注釈だけではAPI応答のruntime検証になりません。
POSTは検証済みの項目だけを保存する
旧db.users.create({ data: body })はrole等の未許可項目を保存し得るため撤回しました。必須項目がtruthyかだけでは型・長さ・email等を確認できません。不正JSONをserver障害と混ぜず、処理段階ごとに判断します。
次はZod 3.25.76を使って確認した独立例です。アプリではimport先を実際の依存へ合わせます。APIの認証・DB処理を実装済みとは扱いません。
const { z } = require('zod');
const createUserSchema = z.object({
name: z.string().trim().min(1).max(100),
email: z.string().trim().email().max(254)
}).strict();
function parseUserInput(raw) {
let value;
try { value = JSON.parse(raw); }
catch { return { ok: false, status: 400 }; }
const result = createUserSchema.safeParse(value);
if (!result.success) return { ok: false, status: 422 };
return { ok: true, data: { name: result.data.name, email: result.data.email } };
}
12項目で正常入力・trim・元入力保持、不正JSON、null/配列/欠落、role追加、空白/非string/長すぎるname、不正/長すぎるemailを確認しました。Zodのemail検査はアドレスの所有や到達性の証明ではありません。
Route Handlerへ接続する時は、認証・対象tenantの認可を先に設計し、bodyの容量制限・rate limit・採用するcookie認証のCSRF対策を確認します。上の例は不正JSONを400、schema不適合を422に分ける方針です。保存は検証したname/emailとserverが決めるrole/tenantだけへ限定し、DBのunique制約と競合、再送・結果不明、transactionを扱います。保存失敗は入力不正へ変換せず、秘密情報を応答へ出しません。
GETも認可済みtenant/権限の範囲へ制限します。queryの長さ、limitの上限、pagination、取得項目、個人情報を定義し、無制限のusers一覧を公開しません。旧dbはimport/定義も省略されたplaceholderでした。今回、実DBや認証を操作していません。
フォームとButtonの型・JSX
以下は入力イベントの型を絞る例です。currentTargetを使い、HTMLInputElementのvalueへアクセスできます。
'use client'
import { useState } from 'react'
import type { ChangeEvent, ReactNode } from 'react'
export function NameField() {
const [name, setName] = useState('')
function handleChange(event: ChangeEvent<HTMLInputElement>) {
setName(event.currentTarget.value)
}
return <label>名前<input name="name" value={name} onChange={handleChange} /></label>
}
type ButtonProps = {
children: ReactNode
onClick: () => void
disabled?: boolean
}
export function Button({ children, onClick, disabled = false }: ButtonProps) {
return <button type="button" onClick={onClick} disabled={disabled}>{children}</button>
}
textarea/selectと共有するならChangeEvent等、実際の対象を含めます。nameから任意のstate keyを書き換える設計なら許可されたkeyを検証します。フォーム送信はFormEvent等を使い、応答の受付結果を確認します。HTTP成功だけで業務完了とせず、失敗確定・結果不明を分け、入力保持・重複防止を扱います。
旧Cardのimage &&だけの式やtags.mapの戻り値なし、タグが欠落した例は撤回しました。必要なら実際のimg/Imageとalt、mapのreturnとkey、callbackを接続します。型定義を足すだけで表示・interactionが実装されるわけではありません。
Zodから型を抽出する場合はtype ContactFormData = z.inferのようにschemaを指定します。旧z.inferだけの記載を訂正しました。schemaとUI型をそろえてもserver側の再検証は必要です。
lintを止めることは型修正ではない
未使用importを削除し、引数・props・generic・nullable値を直してから再検査します。no-unused-varsをwarn/offへ変える、ignoreDuringBuildsを使うという旧方法は検査ゲートの変更であり、型問題そのものの解決として推奨しません。
Next.js 16更新ガイドではnext lintとNext設定のeslint optionが削除され、next buildはlintを実行しません。15の設定を16へコピーせず、lintを別コマンドとしてCIで実行します。ESLintのflat configとTypeScriptの検査は別で、互換性のあるeslint-config-next等へ合わせます。
strictやnoUncheckedIndexedAccess等は意図した設定として導入し、発見された主要分岐を修正します。既存tsconfigを置き換えてNextが必要とする設定を失ったり、型assertionで実データの問題を隠したりしません。
今回確認したのは公式の版別変更、掲載コードの構文、独立DTO例のstrict型検査、Zod入力12項目です。Next/React全体の型検査・build・lint、browserフォーム操作、認証・DB・実APIは未実行です。
この記事を書いた人✏️@YushiYamamoto
ITPRODX.com代表 / AIアーキテクト
Next.js / TypeScript / n8nを活用した自律型アーキテクチャ設計を専門としています。
日々の自動化の検証結果や、ビジネス側の視点(ROI等)に関するより深い考察は、以下の公式サイトおよびnoteで発信しています。