Claude Codeが生成した画面コードを既存プロジェクトに安全に統合する方法
TuBrief 편집팀
2026년 9월 12일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
ターミナルでClaude Codeを起動し、/designと入力すると、画面のドラフトが数秒で生成される。個人開発者としては手数が減るため便利である。問題は、そのコードを開いた瞬間から始まる。
プロジェクトにすでに設定してあるshadcn/uiコンポーネントは見向きもせず、生の状態の <button> タグを新しく作成する。パレットに登録されたセマンティックカラーの代わりに bg-[#1e293b] のような任意の16進数コードをファイルごとに散らばらせる。画面を1つ作るたびに、めちゃくちゃになったインポートパスを修正し、インラインスタイルを消す作業に40分も費やしてしまう。
気のせいではない。2億1,100万行のコミットを分析したGitClearの2024年の研究によると、AIツールの導入後2週間以内に完全に破棄されるか書き直されるコードの割合が3.1%から5.7%へと2倍近く跳ね上がった。リファクタリングの割合は25%から10%未満に急落した。コードが増えるスピードと同じだけ負債が蓄積される。モデルが勝手に既存のデザインシステムを守ってくれるという期待を捨て、システムレベルで手足を縛っておく必要がある。
Claude Codeが既存のコードを無視する理由は単純である。コンテキストウィンドウを節約するために、必要なファイルだけを狭く走査するからである。何の制約も与えなければ、モデルは最も原始的なHTMLタグを組み合わせて画面を描画する。
セッションルートの設定ファイルは、プロンプトキャッシュのおかげで基本入力コストの10%程度の水準に維持される。会話を初期化したり圧縮したりしても消えない。ここにコンポーネントの再利用ルールを書き込んでおけば、モデルが勝手気ままに生の状態のタグを作ることを防ぐことができる。
プロジェクトのルートにある CLAUDE.md ファイルに、共通コンポーネントのパスとスタイルのルールを記述する。
`markdown
`
数千行に及ぶグローバルCSSファイルを毎回プロンプトに丸ごと渡す必要はない。Tailwindの設定からトークンの名前だけを選んでJSONとして抽出しておけばよい。
`javascript
// scripts/extract-tokens.mjs
import fs from 'fs';
import resolveConfig from 'tailwindcss/resolveConfig.js';
import tailwindConfig from '../tailwind.config.js';
const fullConfig = resolveConfig(tailwindConfig);
const semanticTokens = {
colors: Object.keys(fullConfig.theme.colors || {}).filter(
(name) => !['inherit', 'current', 'transparent'].includes(name)
),
spacing: Object.keys(fullConfig.theme.spacing || {}),
borderRadius: Object.keys(fullConfig.theme.borderRadius || {}),
};
if (!fs.existsSync('.claude')) {
fs.mkdirSync('.claude');
}
fs.writeFileSync(
'.claude/design-tokens.json',
JSON.stringify(semanticTokens, null, 2)
);
`
このスクリプトを package.json の postinstall と predev に設定しておく。
`json
{
"scripts": {
"postinstall": "node scripts/extract-tokens.mjs",
"predev": "node scripts/extract-tokens.mjs"
}
}
`
ビルドするたびに利用可能なクラスの一覧が更新される。タイポしたスタイルクラスを探すために費やしていた手作業の時間がなくなる。
プロンプトにどれだけ注意書きを書いても、モデルは時々的外れな値を吐き出す。スクリーンショットを1枚渡して画面を作ってくれと頼むと、画像の比率に合わせて w-[380px] のような固定幅を埋め込んでしまう。モバイル画面で横スクロールが発生する主犯である。
WCAG 2.1 AAの基準を満たすには、解像度別の基準を明示し、ファイルが生成された瞬間にリンターで強制検査しなければならない。
| 検証領域 | 対象基準 | 必須Tailwindクラス | 遮断条件 |
|---|---|---|---|
| モバイル | 390px (base) | flex-col, w-full, grid-cols-1 |
w-[...px] の固定幅使用による水平スクロール |
| タブレット | 768px (md:) |
md:flex-row, md:grid-cols-2, md:p-6 |
モバイルの1カラム構造が広い画面で維持されるとき |
| デスクトップ | 1440px (xl:) |
xl:max-w-7xl, mx-auto, xl:grid-cols-4 |
高解像度でレイアウトコンテナが無制限に広がるとき |
| ダークモード | .dark セレクター |
bg-background, text-foreground |
bg-white, text-black などのデフォルトクラスが単体で放置されているとき |
| アクセシビリティ | WCAG 2.1 AA | aria-label, <main>, focus-visible:ring-2 |
アイコンボタンにスクリーンリーダー用の代替テキストが欠落しているとき |
言うことを聞かないモデルを制御するには、ライフサイクルフックを使う方が確実である。Claude Codeの PostToolUse フックを利用すると、ファイルをディスクに書き込むと同時にスクリプトが実行される。
.claude/settings.json にフックコマンドを登録する。
`json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/ast-lint-guard.mjs",
"timeout": 30
}
]
}
]
}
}
`
次に検査スクリプトを作成する。正規表現で一般的な16進数コードを検知してプロジェクトトークンに置き換え、Tailwind ESLintプラグインでルールを強制する。
`javascript
// .claude/hooks/ast-lint-guard.mjs
import fs from 'fs';
import readline from 'readline';
import { execSync } from 'child_process';
const rl = readline.createInterface({ input: process.stdin });
let inputBuffer = '';
rl.on('line', (line) => { inputBuffer += line; });
rl.on('close', () => {
try {
const payload = JSON.parse(inputBuffer);
const filePath = payload.tool_input?.file_path || payload.tool_input?.path;
if (!filePath || !/\.(tsx|jsx)$/.test(filePath) || !fs.existsSync(filePath)) {
process.exit(0);
}
let code = fs.readFileSync(filePath, 'utf-8');
let changed = false;
const replacementMap = {
'#ffffff': 'bg-background',
'#000000': 'text-foreground',
'#020817': 'bg-background',
'#0f172a': 'bg-card',
'#1e293b': 'bg-muted',
'#64748b': 'text-muted-foreground',
'#2563eb': 'bg-primary',
};
for (const [hex, token] of Object.entries(replacementMap)) {
const regex = new RegExp(`(bg|text|border)-\[${hex}\]`, 'gi');
if (regex.test(code)) {
code = code.replace(regex, token);
changed = true;
}
}
if (changed) {
fs.writeFileSync(filePath, code, 'utf-8');
}
execSync(`npx eslint "${filePath}" --rule "tailwindcss/no-arbitrary-value: error"`, {
stdio: 'pipe',
});
process.exit(0);
} catch (error) {
const failureLog = error.stdout?.toString() || error.stderr?.toString() || error.message;
console.error([Lint Pipeline Block] スタイル規約違反:\n${failureLog});
process.exit(1);
}
});
`
プロジェクトにリンタープラグインをインストールする。
`bash
npm install -D eslint-plugin-tailwindcss
`
スクリプトが終了コード1を返すと、Claude Codeはエラーログを読み取り、次のターンでセマンティッククラスにコードを修正して書き直す。QA段階でデザインが崩れた画面と格闘する回数を減らすことができる。
Claude Codeに画面の作成を指示すると、1つのファイルの中に fetch 関数、巨大なモックデータオブジェクト、JSXをごちゃ混ぜにして配置しがちである。後から実際のAPIを組み込もうとすると、レンダリングコードまで全て剥がす必要が出てくる。
画面コードは状態の変更方法を知る必要はない。1つの機能ディレクトリを4つのファイルに分割し、データスキーマを先に確定させておく方が安全である。
まずはデータの仕様から定義する。
`typescript
// src/components/features/dashboard-card/schema.ts
import { z } from "zod";
export const MetricItemSchema = z.object({
id: z.string(),
label: z.string(),
value: z.string(),
changePercentage: z.number(),
trend: z.enum(["up", "down", "neutral"]),
});
export const DashboardCardSchema = z.object({
title: z.string().min(1),
metrics: z.array(MetricItemSchema),
});
export type DashboardCardData = z.infer;
export interface DashboardCardViewProps {
data: DashboardCardData;
isLoading?: boolean;
onActionClick?: (metricId: string) => void;
}
`
次に、ターミナルでClaude Codeを呼び出す際、内部状態を一切使用しないよう釘を刺す。
`bash
claude "src/components/features/dashboard-card/schema.tsの DashboardCardViewProps を実装する純粋なUIコンポーネント src/components/features/dashboard-card/dashboard-card-view.tsx を作成してください。内部で useState、useEffect、fetch は絶対に使用せず、受け取ったPropsと @/components/ui の要素のみを使用してレスポンシブに構築してください。"
`
データを結合する際はフックでラップする。モックデータと実際のAPI呼び出し関数を同じ構造にしておく。
`typescript
// src/components/features/dashboard-card/use-dashboard-card.ts
import { useQuery } from "@tanstack/react-query";
import { DashboardCardData } from "./schema";
const MOCK_DATA: DashboardCardData = {
title: "月間アクティブ指標",
metrics: [
{ id: "m-1", label: "新規流入", value: "1,240名", changePercentage: 12.5, trend: "up" },
{ id: "m-2", label: "離脱率", value: "2.1%", changePercentage: -0.4, trend: "down" },
],
};
export const useDashboardCard = (cardId: string, useMock = false) => {
return useQuery({
queryKey: ["dashboard-card", cardId],
queryFn: async () => {
if (useMock) {
return MOCK_DATA;
}
const res = await fetch(/api/dashboard/${cardId});
if (!res.ok) throw new Error("データの取得に失敗しました");
return res.json();
},
});
};
`
コンテナコンポーネントで両者を組み立てる。
`typescript
// src/components/features/dashboard-card/index.tsx
"use client";
import React from "react";
import { DashboardCardView } from "./dashboard-card-view";
import { useDashboardCard } from "./use-dashboard-card";
export function DashboardCardContainer({ cardId, useMock = false }: { cardId: string; useMock?: boolean }) {
const { data, isLoading } = useDashboardCard(cardId, useMock);
if (!data) return null;
return (
<DashboardCardView
data={data}
isLoading={isLoading}
onActionClick={(id) => console.log(id)}
/>
);
}
`
バックエンドが完成する前は useMock={true} で画面をブラッシュアップする。APIが完成したらフラグを削除するだけである。ビューコードは1行たりとも変更する必要がない。
ターミナルでモデルと長めの対話をしながらUIを調整していると、問題のないグローバル設定ファイルが修正されていたり、不要な一時ファイルがディレクトリのあちこちに生成されたりする。自分の作業スペースはそのままにして、実験専用のディレクトリを切って作業する方が気楽である。
Git worktreeを使用すると、完全に分離されたフォルダでClaude Codeを動かすことができる。
`bash
git worktree add ../saas-ui-sandbox -b experiment/ai-dashboard-ui
cd ../saas-ui-sandbox
claude
`
実験が失敗した場合は、迷わずフォルダごと削除すればよい。
`bash
cd ../saas-platform
git worktree remove ../saas-ui-sandbox --force
git branch -D experiment/ai-dashboard-ui
`
望み通りの形になった場合は、ブランチをまるごとマージせず、インタラクティブモードでビューファイルのコードスニペットだけを選んで持ち帰る。
`bash
git checkout main
git checkout -p experiment/ai-dashboard-ui -- src/components/features/dashboard-card/dashboard-card-view.tsx
`
ターミナルに表示されるコードの塊を見ながら、気に入った部分だけ y を押し、おかしな修正は n で弾く。
セッションが5ターンを超えるとコンテキストが薄れ、モデルが的外れなことを言い始める。その都度セッションを整理する必要がある。
npx tsc --noEmit を実行する。型エラーが0個の時のみ次のプロンプトに進む。/compact を実行してトークンの無駄遣いを減らす。/clear でメモリを完全に空にする。混乱したときは /rewind で以前のチェックポイントに戻る。モデルの生成能力が高いことと、そのコードがプロダクションに耐えうるかは全く別の問題である。 CLAUDE.md で入力の導線を絞り、ライフサイクルフックで出力コードを検証し、worktreeで作業スペースを分離しておけば、AIが吐き出したコードの後始末で徹夜する事態は避けることができる。