32blogby StudioMitsu
claude-code14 min read

Claude Code × TypeScript|型安全なコード生成のコツ

Claude CodeにTypeScriptの厳格な型安全コードを生成させるコツを全公開。strictモード設定・Zod連携・型エラーを自動修正させるループまで実践的に解説する。

claude-codeTypeScript型安全Zodstrict mode
目次

Claude Codeに「APIレスポンスをパースして」と頼んだら、こういうコードが返ってきた。僕は最初、これで型安全になっていると勘違いしていた。

typescript
const data = response.data as any
const user = data.user as any
const name = (data.user as any).name as string

as any が3回。型情報が完全に捨てられている。これでは型チェックの意味がない。

TypeScriptのコードを書かせるとき、Claude Codeはデフォルトで「とりあえず動くコード」を優先する。型安全より実行できることを選ぶ。それを変えるには、型安全を明示的に要求する設定と指示が必要だ。

この記事を読めば次のことがわかる。

  • as any が生成される根本原因と解決策
  • tsconfig.json の strict 設定を CLAUDE.md で強制する方法
  • Zod スキーマ設計を Claude Code に任せる手順
  • 型エラーを自動修正させるフィードバックループ
  • 型安全なコード生成のための CLAUDE.md テンプレート

Claude Codeがanyを乱用するのはなぜか?

Claude Codeが any を使いたがる理由は主に3つある。

1. 型情報がない状態でコードを完成させようとする

APIレスポンスの型定義がない、ライブラリの型定義が不完全、など「型がわからない」状況でClaude Codeは any で埋めようとする。型エラーを出さずにコードを完成させることを優先するからだ。

2. 指示に型安全の要件が含まれていない

「パースして」「変換して」という指示に型の要件は含まれていない。Claude Codeは指示された機能を実現することに集中して、型の厳格さは後回しになる。

3. プロジェクトの型設定が伝わっていない

tsconfig.json の設定がClaude Codeに伝わっていないと、strictモードの存在を知らずに緩い型で書いてしまう。

これらを解決するアプローチは「プロジェクト設定を先に伝える」「型を最初から定義させる」「型チェックを必須にする」の3点だ。


tsconfig.jsonのstrict設定をCLAUDE.mdで強制するには?

CLAUDE.mdに以下の内容を書くと、Claude Codeがコードを生成するたびに型安全の要件を参照するようになる。

まずはtsconfig.jsonの設定。

json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

そしてCLAUDE.mdに書くコード生成のルール。

markdown
## TypeScript設定

### コード生成のルール
1. `as any` は禁止。どんな場合も使わない
2. `as Type` のキャストは型ガードか Zod バリデーション後のみ許可
3. 関数の引数・戻り値には必ず型注釈を付ける
4. `any` の代わりに `unknown` を使い、型ガードで絞り込む
5. 型推論に頼れる場面では明示的な型注釈は不要(過剰な注釈もNG)

### 検証コマンド
コード生成後は必ず `npx tsc --noEmit` を実行する。
型エラーが出たら修正してから提出すること。

この設定がCLAUDE.mdにあると、プロンプトごとに「anyを使わないで」と言わなくて済む。tsconfig.json の内容をCLAUDE.mdに転記しておくのが特に効く。


ZodスキーマをClaude Codeに設計させるにはどうすればいいか?

外部APIのレスポンスを扱う場合、Zodスキーマを先に定義してからパースコードを生成させる流れが安全だ。

Step 1: APIのレスポンス例を渡してZodスキーマを生成させる

以下のAPIレスポンス例からZodスキーマを生成してください。

APIレスポンス例:
{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "role": "admin" | "user" | "guest",
  "createdAt": "2026-01-01T00:00:00Z",
  "profile": {
    "bio": "Developer",
    "avatarUrl": "https://example.com/avatar.jpg" | null
  }
}

要件:
- すべてのフィールドに型を付ける
- role は union 型で定義する
- createdAt は z.coerce.date() でDate型に変換する
- nullable なフィールドは .nullable() で明示する
- スキーマから型を推論して export する

生成されたスキーマ。

typescript
import { z } from 'zod'

const ProfileSchema = z.object({
  bio: z.string(),
  avatarUrl: z.string().url().nullable(),
})

const UserRoleSchema = z.enum(['admin', 'user', 'guest'])

export const UserSchema = z.object({
  id: z.number().int().positive(),
  name: z.string().min(1),
  email: z.string().email(),
  role: UserRoleSchema,
  createdAt: z.coerce.date(),
  profile: ProfileSchema,
})

export type User = z.infer<typeof UserSchema>
export type UserRole = z.infer<typeof UserRoleSchema>

Step 2: スキーマを使ったパース関数を生成させる

上記の UserSchema を使って、fetch のレスポンスをパースする関数を書いてください。

要件:
- パース失敗時は ZodError をスローする
- unknown 型で受け取って Zod でバリデーションする
- `as any` は使わない
typescript
import { UserSchema, type User } from './schemas/user'
import { ZodError } from 'zod'

export async function fetchUser(userId: number): Promise<User> {
  const response = await fetch(`/api/users/${userId}`)

  if (!response.ok) {
    throw new Error(`Failed to fetch user: ${response.status}`)
  }

  const json: unknown = await response.json()
  return UserSchema.parse(json)
}

response.json() の戻り値を unknown で受けて、ZodでバリデーションしてからTypedな値を返す。これが as any を使わずに外部データを扱う正しいパターンだ。


tsc --noEmit パスを必須条件にするにはどう書くか?

型エラーを生成コードに残させないために、CLAUDE.mdに次の一文を加える。

markdown
## コード提出前の必須チェック

コードを生成・修正したら、必ず `npx tsc --noEmit` を実行すること。
型エラーがゼロになるまでコードを修正してから回答すること。
型エラーが残ったままのコードは提出しない。

この「型エラーがゼロになるまで修正」という制約が重要だ。これがないと、Claude Codeは型エラーが出ていても「機能としては動く」という判断でコードを返してくることがある。

--noEmit フラグはJavaScriptファイルを出力せずに型チェックだけを実行するオプションだ。CIでの型チェックにも使える。

json
{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch"
  }
}

型エラーをClaudeに自動修正させるループはどう回すか?

tsc --noEmit の出力をそのままClaudeに渡して修正させるループが効率的だ。

bash
# 型エラーを取得
npx tsc --noEmit 2>&1

出力例。

src/utils/parseResponse.ts:12:5 - error TS2322: Type 'any' is not assignable to type 'User'.
src/utils/parseResponse.ts:18:3 - error TS7006: Parameter 'data' implicitly has an 'any' type.

Found 2 errors.

これをClaudeに渡す。

以下のTypeScriptエラーを修正してください。

エラー:
src/utils/parseResponse.ts:12:5 - error TS2322: Type 'any' is not assignable to type 'User'.
src/utils/parseResponse.ts:18:3 - error TS7006: Parameter 'data' implicitly has an 'any' type.

修正方針:
- `as any` を使わずに修正する
- unknown 型 + 型ガードか Zod を使う
- 修正後に `tsc --noEmit` でエラーがゼロになることを確認してから回答する

このループを回し続けると、最終的に型エラーがゼロのコードが出てくる。エラーが複数あるときは一度に渡してOKで、Claudeは全エラーをまとめて修正しようとする。


外部APIレスポンスの型付けはClaude Codeにどう任せるか?

SwaggerやOpenAPI仕様書がある場合は、それをClaudeに渡してZodスキーマに変換させるのが最速だ。

以下のOpenAPI仕様から、TypeScript + Zodのスキーマを生成してください。

仕様(抜粋):
---
components:
  schemas:
    Product:
      type: object
      required:
        - id
        - name
        - price
      properties:
        id:
          type: integer
        name:
          type: string
          maxLength: 100
        price:
          type: number
          minimum: 0
        category:
          type: string
          enum: [electronics, clothing, food]
        stock:
          type: integer
          nullable: true
---

要件:
- z.infer<typeof Schema> で型を export する
- バリデーション制約(maxLength・minimum)もZodに反映する
- nullable は .nullable() で表現する

OpenAPI仕様が複雑な場合は openapi-zod-client などのツールも選択肢になるが、Claudeに変換させる方が細かい調整がしやすい。


Prismaと連携して型推論を最大化するには?

PrismaはスキーマからTypeScriptの型を自動生成してくれる。Claude Codeにこの型を活用させる指示を書いておく。

markdown
## Prismaの型活用ルール(CLAUDE.mdに追記)

- Prismaが生成した型(`PrismaClient`・`Prisma.UserGetPayload` など)を積極的に使う
- `User` 型は `import type { User } from '@prisma/client'` から参照する
- クエリの戻り値には `Prisma.UserGetPayload<{include: {posts: true}}>` のような型を使う
- 独自の型定義でPrismaの型を上書きしない

Prismaの型を使ったコード例。

typescript
import type { Prisma } from '@prisma/client'
import { prisma } from '@/lib/prisma'

type UserWithPosts = Prisma.UserGetPayload<{
  include: { posts: { include: { tags: true } } }
}>

export async function getUserWithPosts(userId: string): Promise<UserWithPosts | null> {
  return prisma.user.findUnique({
    where: { id: userId },
    include: {
      posts: {
        include: { tags: true },
      },
    },
  })
}

Prismaが生成する型を使えば、DBのスキーマ変更がTypeScriptの型エラーとして即座に検出できる。Claude Codeに「Prismaの型を使って」と一言添えるだけで、この恩恵を受けられる。


型安全なコード生成のためのCLAUDE.mdテンプレートはどう書くか?

ここまでの内容をまとめた CLAUDE.md テンプレートを公開する。このままプロジェクトに追加して使える。

markdown
## TypeScript型安全ポリシー

### 絶対禁止
- `as any` の使用(理由を問わず禁止)
- `@ts-ignore` の使用(`@ts-expect-error` は理由コメント付きで許可)
- 型注釈なしの関数定義(引数・戻り値の型は必須)

### 外部データの型付け
- 外部API・JSONは `unknown` で受けて Zod でバリデーション
- `response.json() as SomeType` のキャストは禁止
- Zodスキーマから `z.infer<typeof Schema>` で型を export する

### tsconfig.json準拠
strict: true / noImplicitAny: true / strictNullChecks: true / noUncheckedIndexedAccess: true

### 提出前チェック(必須)
コード生成・修正後は `npx tsc --noEmit` を実行し、エラーゼロを確認してから回答する。

### Zodの使い方
- スキーマ定義は `src/schemas/` ディレクトリに集約する
- `.parse()` はバリデーション失敗時にエラーをスロー(意図的な動作)
- `.safeParse()` はエラーハンドリングが必要な場合に使う
- スキーマファイルには型も一緒に export する

まとめ

Claude Codeに型安全なTypeScriptを生成させる鍵は「型安全を暗黙の了解にしない」ことだ。

  • CLAUDE.mdにstrict設定を転記する — プロジェクト全体で一貫した型ポリシーが定着する
  • tsc --noEmit を必須チェックとして明記する — 型エラーが残ったコードが返ってこなくなる
  • 外部データはZodスキーマから始めるas any を使わない唯一の正道
  • 型エラーログをそのまま渡す — フィードバックループで自動修正が効く
  • Prismaの型を積極的に使わせる — DBスキーマとTypeScript型が自動で同期する

「anyを使わないで」と毎回言うのをやめて、CLAUDE.mdに型ポリシーを書いてしまうのが一番の近道だ。