Wie man von Claude Code generierten UI-Code sicher in ein bestehendes Projekt integriert
TuBrief 편집팀
2026년 9월 12일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Öffnet man Claude Code im Terminal und gibt /design ein, wirft das Tool innerhalb von Sekunden einen ersten UI-Entwurf aus. Für Solo-Entwickler ist das äusserst bequem, da es Arbeit abnimmt. Das Problem beginnt jedoch in dem Moment, in dem man diesen Code öffnet.
Die im Projekt bereits eingerichteten shadcn/ui-Komponenten werden komplett ignoriert, stattdessen werden rohe <button>-Tags neu geschrieben. Anstelle der im Farbpaletten-System registrierten semantischen Farben werden willkürliche Hex-Codes wie bg-[#1e293b] über jede Datei verstreut. Für jeden neu erstellten Bildschirm verschwendet man am Ende 40 Minuten damit, fehlerhafte Import-Pfade zu korrigieren und Inline-Styles zu löschen.
Das ist keine Einbildung. Eine Studie von GitClear aus dem Jahr 2024, die 211 Millionen Codezeilen in Commits analysierte, zeigt, dass der Anteil von Code, der innerhalb von zwei Wochen nach der Einführung von KI-Tools vollständig verworfen oder umgeschrieben wird, von 3,1 % auf 5,7 % und damit fast auf das Doppelte angestiegen ist. Die Refactoring-Quote ist gleichzeitig von 25 % auf unter 10 % eingebrochen. Die technische Schuld wächst im gleichen Tempo wie die Codebasis. Man muss sich von der Erwartung verabschieden, dass das Modell von selbst das bestehende Design-System einhält – stattdessen muss man es auf Systemebene an die Kandare nehmen.
Der Grund, warum Claude Code bestehenden Code ignoriert, ist simpel: Um das Context Window zu schonen, scannt es nur die minimal notwendigen Dateien. Ohne Einschränkungen kombiniert das Modell einfach die primitivsten HTML-Tags, um die Benutzeroberfläche zu zeichnen.
Dank Prompt Caching bleiben Konfigurationsdateien im Wurzelverzeichnis der Session bei etwa 10 % der normalen Eingabekosten. Sie gehen selbst dann nicht verloren, wenn Unterhaltungen zurückgesetzt oder komprimiert werden. Wenn man hier Regeln zur Wiederverwendung von Komponenten verankert, lässt sich verhindern, dass das Modell eigenmächtig rohe Tags generiert.
Tragen Sie die gemeinsamen Komponenten-Pfade und Stilregeln in die Datei CLAUDE.md im Projektstamm ein.
`markdown
`
Tausendzeilige globale CSS-Dateien müssen nicht jedes Mal komplett an den Prompt übergeben werden. Es reicht völlig aus, die Token-Namen aus der Tailwind-Konfiguration zu extrahieren und als JSON zu speichern.
`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)
);
`
Hängen Sie dieses Skript an postinstall und predev in der package.json an.
`json
{
"scripts": {
"postinstall": "node scripts/extract-tokens.mjs",
"predev": "node scripts/extract-tokens.mjs"
}
}
`
Die Liste der verfügbaren Klassen wird bei jedem Build aktualisiert. Die manuelle Arbeit, fehlerhafte Style-Klassen zu korrigieren, entfällt komplett.
Egal wie viele Warnhinweise im Prompt stehen, das Modell spuckt hin und wieder falsche Werte aus. Wenn man ihm einfach einen Screenshot hinwirft und um eine Benutzeroberfläche bittet, fügt es oft feste Breiten wie w-[380px] passend zum Bildformat ein. Das ist der Hauptgrund für horizontales Scrollen auf Mobilgeräten.
Um die Kriterien von WCAG 2.1 AA zu erfüllen, müssen auflösungsspezifische Standards klar definiert und der erstellte Code sofort durch einen Linter überprüft werden.
| Prüfbereich | Zielstandard | Erforderliche Tailwind-Klassen | Ausschlusskriterium |
|---|---|---|---|
| Mobil | 390px (base) | flex-col, w-full, grid-cols-1 |
Horizontales Scrollen durch feste w-[...px]-Breiten |
| Tablet | 768px (md:) |
md:flex-row, md:grid-cols-2, md:p-6 |
Beibehaltung der mobilen Einspaltigkeit auf breiten Bildschirmen |
| Desktop | 1440px (xl:) |
xl:max-w-7xl, mx-auto, xl:grid-cols-4 |
Unbegrenztes Vergrößern des Layout-Containers bei hoher Auflösung |
| Dark Mode | .dark Selektor |
bg-background, text-foreground |
Unüberwachte Standardklassen wie bg-white oder text-black |
| Barrierefreiheit | WCAG 2.1 AA | aria-label, <main>, focus-visible:ring-2 |
Fehlender Alternativtext für Screenreader bei Icon-Buttons |
Wenn man ein ungezähmtes Modell kontrollieren will, sind Lifecycle Hooks der sicherste Weg. Mit dem PostToolUse-Hook von Claude Code wird ein Skript ausgeführt, sobald Dateien auf die Festplatte geschrieben werden.
Registrieren Sie den Hook-Befehl in .claude/settings.json.
`json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/ast-lint-guard.mjs",
"timeout": 30
}
]
}
]
}
}
`
Schreiben Sie nun das Validierungsskript. Es fängt gängige Hex-Codes über reguläre Ausdrücke ab, ersetzt sie durch Projekt-Tokens und erzwingt Regeln über das Tailwind ESLint Plugin.
`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] Stilregelverstoß:\n${failureLog});
process.exit(1);
}
});
`
Installieren Sie das Linter-Plugin im Projekt.
`bash
npm install -D eslint-plugin-tailwindcss
`
Gibt das Skript den Exit-Code 1 zurück, liest Claude Code das Fehlerprotokoll und korrigiert den Code im nächsten Durchlauf auf semantische Klassen. Das minimiert stundenlanges Frickeln an fehlerhaften Layouts in der QA-Phase.
Befiehlt man Claude Code, eine Benutzeroberfläche zu erstellen, wirft es häufig fetch-Funktionen, riesige Mock-Datenobjekte und JSX in einen einzigen Topf. Möchte man später eine echte API anbinden, muss man am Ende den gesamten Render-Code wieder herausreißen.
UI-Code muss nicht wissen, wie Zustandsänderungen gehandhabt werden. Es ist sicherer, ein Funktionsverzeichnis in vier Dateien zu unterteilen und das Datenschema im Voraus festzulegen.
Definieren Sie zuerst das Datenformat.
`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;
}
`
Unterbinden Sie beim Aufruf von Claude Code im Terminal jegliche Verwendung interner Zustände.
`bash
claude "Erstelle eine reine UI-Komponente src/components/features/dashboard-card/dashboard-card-view.tsx, die die DashboardCardViewProps aus src/components/features/dashboard-card/schema.ts implementiert. Verwende intern absolut kein useState, useEffect oder fetch, sondern baue sie rein responsiv unter ausschließlicher Nutzung der übergebenen Props und @/components/ui-Elemente."
`
Wickeln Sie die Datenanbindung in einen Hook ein. Halten Sie Mock-Daten und echte API-Aufrufe in derselben Struktur.
`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: "Monatliche Aktivitätsmetriken",
metrics: [
{ id: "m-1", label: "Neuzugänge", value: "1.240", changePercentage: 12.5, trend: "up" },
{ id: "m-2", label: "Absprungrate", 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("Fehler beim Abrufen der Daten");
return res.json();
},
});
};
`
Setzen Sie beide Teile in einer Container-Komponente zusammen.
`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)}
/>
);
}
`
Vor dem Release des Backends lässt sich die UI mit useMock={true} verfeinern. Steht die API bereit, wird einfach das Flag entfernt. Die View-Datei muss dabei nicht ein einziges Mal angefasst werden.
Führt man im Terminal längere Gespräche mit dem Modell und bastelt an der UI, findet man oft modifizierte globale Konfigurationsdateien oder temporäre Dateien im Verzeichnis vor. Es ist ratsam, die eigene Arbeitsumgebung unberührt zu lassen und Experimente in einem separaten Verzeichnis durchzuführen.
Mit Git Worktrees lässt sich Claude Code in einem völlig isolierten Ordner ausführen.
`bash
git worktree add ../saas-ui-sandbox -b experiment/ai-dashboard-ui
cd ../saas-ui-sandbox
claude
`
Schlägt das Experiment fehl, kann der Ordner ohne Bedenken gelöscht werden.
`bash
cd ../saas-platform
git worktree remove ../saas-ui-sandbox --force
git branch -D experiment/ai-dashboard-ui
`
Liefert das Ergebnis das gewünschte Resultat, sollte der Branch nicht blind gemergt werden; holen Sie stattdessen nur die gewünschten Code-Snippets der View-Datei im interaktiven Modus ab.
`bash
git checkout main
git checkout -p experiment/ai-dashboard-ui -- src/components/features/dashboard-card/dashboard-card-view.tsx
`
Prüfen Sie die im Terminal angezeigten Code-Blöcke und bestätigen Sie gewünschte Änderungen mit y, während Sie unerwünschte Modifikationen mit n verwerfen.
Sobald eine Session fünf Turns überschreitet, verschwimmt der Kontext und das Modell beginnt Unsinn zu reden. Die Session sollte in solchen Momenten bereinigt werden:
npx tsc --noEmit aus. Erst wenn null Typfehler vorliegen, geht es zum nächsten Prompt./compact, um Token-Verschwendung zu vermeiden./clear vollständig bereinigt. Bei Verwirrung hilft /rewind, um zum vorherigen Checkpoint zurückzukehren.Dass ein Modell hervorragend generieren kann, bedeutet noch lange nicht, dass dieser Code produktionsreif ist. Wenn man den Eingangskanal mit CLAUDE.md verengt, den ausgegebenen Code mit Lifecycle Hooks validiert und den Workspace über Worktrees trennt, bleibt einem das nächtliche Aufräumen von KI-generiertem Code erspart.