Cara Aman Menggabungkan Kode Antarmuka Buatan Claude Code ke Proyek yang Sudah Ada
TuBrief 편집팀
2026년 9월 12일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Menyalakan Claude Code di terminal dan mengetik /design akan memunculkan draf antarmuka dalam hitungan detik. Bagi pengembang solo, ini sangat praktis karena menghemat tenaga. Masalahnya dimulai tepat saat Anda membuka kode tersebut.
Komponen shadcn/ui yang sudah disetel di dalam proyek sama sekali tidak dihiraukan, dan ia malah merancang tag <button> mentah yang baru. Alih-alih menggunakan warna semantik yang terdaftar di palet, ia menyebar kode heksadesimal acak seperti bg-[#1e293b] di setiap file. Setiap kali membuat satu halaman, Anda membuang waktu 40 menit hanya untuk memperbaiki jalur impor yang berantakan dan menghapus gaya inline.
Ini bukan sekadar perasaan Anda saja. Berdasarkan studi GitClear tahun 2024 yang menganalisis 211 juta baris commit, rasio kode yang sepenuhnya ditinggalkan atau ditulis ulang dalam waktu 2 minggu setelah adopsi alat AI melonjak hampir dua kali lipat dari 3.1% menjadi 5.7%. Rasio refaktorisasi merosot drastis dari 25% ke bawah 10%. Utang teknis menumpuk secepat pertumbuhan kodenya. Alih-alih berharap model akan menjaga sistem desain yang ada dengan sendirinya, Anda harus mengikat tangan dan kakinya di tingkat sistem.
Alasan Claude Code mengabaikan kode yang sudah ada sangatlah sederhana. Ia memindai file yang diperlukan secara sempit demi menghemat window konteks. Tanpa batasan apa pun, model akan menggambar antarmuka dengan mengombinasikan tag HTML paling primitif.
Berkat caching prompt, file konfigurasi di root sesi dipertahankan pada tingkat sekitar 10% dari biaya input dasar. Konfigurasi ini tidak hilang meskipun percakapan diinisialisasi ulang atau dikompresi. Menanamkan aturan penggunaan komponen di sini dapat mencegah model membuat tag mentah semaunya.
Tulis jalur komponen umum dan aturan gaya di file CLAUDE.md pada root proyek.
`markdown
`
Anda tidak perlu terus menerus meneruskan file CSS global berukuran ribuan baris secara utuh ke dalam prompt. Cukup pilih nama token dari konfigurasi Tailwind dan ekstrak ke dalam 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)
);
`
Pasang skrip ini pada postinstall dan predev di package.json.
`json
{
"scripts": {
"postinstall": "node scripts/extract-tokens.mjs",
"predev": "node scripts/extract-tokens.mjs"
}
}
`
Daftar kelas yang tersedia akan diperbarui setiap kali Anda melakukan build. Waktu manual yang dihabiskan untuk memperbaiki kelas gaya yang salah ketik akan hilang.
Sebanyak apa pun peringatan yang ditulis dalam prompt, model terkadang tetap memberikan nilai yang melenceng. Jika Anda memberikan satu tangkapan layar dan meminta pembuatan antarmuka, ia akan memasang lebar tetap seperti w-[380px] yang disesuaikan dengan rasio gambar. Inilah pelaku utama munculnya scroll horizontal pada layar seluler.
Untuk memenuhi standar WCAG 2.1 AA, Anda harus menentukan kriteria berdasarkan resolusi dan mewajibkan pemeriksaan linter segera setelah file dibuat.
| Area Verifikasi | Kriteria Target | Kelas Tailwind Wajib | Kondisi Pemblokiran |
|---|---|---|---|
| Seluler | 390px (base) | flex-col, w-full, grid-cols-1 |
Scroll horizontal karena penggunaan lebar tetap w-[...px] |
| Tablet | 768px (md:) |
md:flex-row, md:grid-cols-2, md:p-6 |
Struktur 1 kolom seluler tetap dipertahankan pada layar lebar |
| Desktop | 1440px (xl:) |
xl:max-w-7xl, mx-auto, xl:grid-cols-4 |
Wadah layout melebar tanpa batas pada resolusi tinggi |
| Mode Gelap | Selektor .dark |
bg-background, text-foreground |
Kelas dasar seperti bg-white, text-black dibiarkan sendiri |
| Aksesibilitas | WCAG 2.1 AA | aria-label, <main>, focus-visible:ring-2 |
Tombol ikon kekurangan teks alternatif untuk screen reader |
Saat mengendalikan model yang sulit diatur, menggunakan hook siklus hidup adalah cara yang paling pasti. Dengan memanfaatkan hook PostToolUse dari Claude Code, skrip akan langsung dieksekusi begitu file ditulis ke disk.
Daftarkan perintah hook di .claude/settings.json.
`json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/ast-lint-guard.mjs",
"timeout": 30
}
]
}
]
}
}
`
Sekarang buat skrip pemeriksa. Tangkap kode heksadesimal umum dengan regex lalu ubah menjadi token proyek, serta paksa aturan menggunakan plugin 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] Pelanggaran Aturan Gaya:\n${failureLog});
process.exit(1);
}
});
`
Instal plugin linter ke dalam proyek.
`bash
npm install -D eslint-plugin-tailwindcss
`
Jika skrip mengeluarkan kode keluar (exit code) 1, Claude Code akan membaca log error dan menulis ulang kode menggunakan kelas semantik pada giliran berikutnya. Hal ini mengurangi waktu bergelut dengan tampilan antarmuka yang rusak pada tahap QA.
Jika Anda menyuruh Claude Code merancang antarmuka, ia cenderung mencampur fungsi fetch, objek data palsu yang besar, dan JSX ke dalam satu file. Nantinya, jika Anda ingin menghubungkan API nyata, Anda harus membongkar seluruh kode perenderaannya.
Kode antarmuka tidak perlu tahu cara mengubah status. Lebih aman untuk memecah satu direktori fitur menjadi 4 file dan menetapkan skema data terlebih dahulu.
Definisikan spesifikasi data terlebih dahulu.
`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;
}
`
Selanjutnya, saat memanggil Claude Code di terminal, pastikan untuk menegaskan agar tidak menggunakan status internal sama sekali.
`bash
claude "Buatlah komponen UI murni src/components/features/dashboard-card/dashboard-card-view.tsx yang mengimplementasikan DashboardCardViewProps dari src/components/features/dashboard-card/schema.ts. Jangan pernah menggunakan useState, useEffect, atau fetch di dalam, dan buatlah secara responsif hanya menggunakan Props yang diteruskan serta elemen @/components/ui."
`
Saat menghubungkan data, bungkus dengan hook. Buat data mock dan fungsi panggilan API asli dengan struktur yang sama.
`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();
},
});
};
`
Gabungkan keduanya di komponen container.
`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)}
/>
);
}
`
Sebelum backend tersedia, sempurnakan tampilan dengan useMock={true}. Setelah API selesai, cukup hapus flag tersebut. Tidak ada alasan untuk menyentuh kode tampilan bahkan satu baris pun.
Saat mengobrol panjang dengan model di terminal dan mengutak-atik UI, file konfigurasi global yang tadinya normal sering kali ikut termodifikasi atau file-file sementara yang tidak terpakai bermunculan di berbagai sudut direktori. Lebih nyaman untuk membuat direktori khusus eksperimen tanpa mengganggu ruang kerja utama.
Dengan menggunakan Git worktree, Anda dapat menjalankan Claude Code di folder yang sepenuhnya terisolasi.
`bash
git worktree add ../saas-ui-sandbox -b experiment/ai-dashboard-ui
cd ../saas-ui-sandbox
claude
`
Jika eksperimen rusak, Anda bisa langsung menghapus foldernya tanpa perlu pusing.
`bash
cd ../saas-platform
git worktree remove ../saas-ui-sandbox --force
git branch -D experiment/ai-dashboard-ui
`
Jika hasilnya sudah sesuai, jangan gabungkan (merge) seluruh cabang sekaligus; gunakan mode interaktif untuk mengambil cuplikan kode file tampilan saja.
`bash
git checkout main
git checkout -p experiment/ai-dashboard-ui -- src/components/features/dashboard-card/dashboard-card-view.tsx
`
Sambil melihat potongan kode yang muncul di terminal, tekan y hanya untuk bagian yang Anda sukai, dan ketik n untuk menolak modifikasi yang aneh.
Jika sesi melewati 5 giliran, konteksnya akan mulai kabur dan model mulai melantur. Anda harus membersihkan sesi setiap kali hal itu terjadi.
npx tsc --noEmit setelah membuat satu komponen. Lanjutkan ke prompt berikutnya hanya jika error tipe berjumlah 0./compact tanpa ragu untuk mengurangi pemborosan token./clear. Jika terjadi kekacauan, gunakan /rewind untuk kembali ke titik pemeriksaan sebelumnya.Kemampuan pembuatan model yang luar biasa dan kelayakan kode tersebut untuk masuk ke dalam produksi adalah dua hal yang sama sekali berbeda. Dengan mempersempit saluran input melalui CLAUDE.md, memverifikasi kode output dengan hook siklus hidup, dan memisahkan ruang kerja menggunakan worktree, Anda dapat menghindari begadang semalaman hanya untuk membereskan kode yang dimuntahkan oleh AI.