AI가 만든 랜딩 페이지가 전부 똑같아 보이는 이유와 해결책
TuBrief 편집팀
2026년 8월 22일
0
컴퓨터/소프트웨어원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
혼자서 기획부터 배포까지 다 쳐내는 1인 개발자에게 Cursor나 Claude Code는 구원투수 같습니다. 프롬프트 몇 줄 치면 10분 만에 그럴듯한 웹페이지가 뚝딱 나오니까요.
문제는 그다음입니다. 배포 버튼을 누르고 화면을 보면 기시감이 듭니다. 보라색 그라데이션 버튼, Inter 폰트, 정중앙에 박힌 3단 카드 레이아웃. 어디서 본 듯한 템플릿 냄새가 풀풀 풍깁니다. 겉보기엔 깔끔한데 사용자 결제나 가입으로 이어지지 않습니다. 사람들은 공장에서 찍어낸 것 같은 페이지를 기막히게 알아채고 창을 닫아버립니다.
LLM은 통계적 평균으로 작동합니다. 규칙을 주지 않으면 웹상에서 가장 흔하게 쓰인 Tailwind 기본 프리셋과 Shadcn UI 기본값으로 회귀합니다. "깔끔하고 세련된 랜딩 페이지를 만들어줘"라는 프롬프트를 던지는 순간, AI는 w-[320px], top-[117px] 같은 정체불명의 인라인 픽셀값을 쏟아내며 레이아웃을 망가뜨립니다.
이 문제를 프롬프트 수정으로 잡으려 하면 지칩니다. 코드를 생성할 때마다 브랜드 규칙을 강제하고, 위반한 코드는 커밋 단계에서 물리적으로 막아내는 파이프라인을 구축해야 합니다.
Cursor는 .cursor/rules/*.mdc, Claude Code는 CLAUDE.md, 오픈소스 도구들은 AGENTS.md를 읽습니다. 이 파일에 브랜드 디자인 토큰을 정의해두면 AI가 코드 생성 단계부터 임의의 스타일을 쓰지 못합니다.
규칙 파일이 500줄을 넘어가면 세션마다 읽어오는 컨텍스트 비용이 커지고 모델의 지시 준수율도 떨어집니다. 200~300줄 안팎으로 명확하게 작성해야 합니다.
프로젝트 루트에 .cursor/rules/design-system.mdc를 만들고 아래 내용을 넣습니다.
---
description: Design System Rules and Custom Token Enforcement
globs: ["src/app/**/*.tsx", "src/components/**/*.tsx", "src/styles/**/*.css"]
alwaysApply: false
---
# Brand Design System Constraints
## Universal Rules
- MUST NOT use arbitrary Tailwind utility classes such as `bg-[#123456]` or `h-[117px]`.
- MUST use predefined semantic Design Tokens for colors, spacing, and typography.
- MUST run `pnpm lint:style` to verify token compliance before completing tasks.
## Design Token Reference Map
### Color Tokens
- Surface Background: `var(--color-bg-primary)` (Tailwind: `bg-brand-primary`)
- Surface Secondary: `var(--color-bg-secondary)` (Tailwind: `bg-brand-secondary`)
- Text Main: `var(--color-text-main)` (Tailwind: `text-brand-main`)
- Text Muted: `var(--color-text-muted)` (Tailwind: `text-brand-muted`)
- Accent Primary: `var(--color-accent-default)` (Tailwind: `bg-brand-accent`)
### Spacing Scale (8pt Grid Standard)
- `var(--space-1)`: 0.25rem (4px)
- `var(--space-2)`: 0.5rem (8px)
- `var(--space-4)`: 1.0rem (16px)
- `var(--space-6)`: 1.5rem (24px)
- `var(--space-8)`: 2.0rem (32px)
### Typography Rules
- Main Heading (H1): Class `text-brand-h1` -> Font: Inter, Weight: 700, Size: 2.5rem, Tracking: -0.02em
- Body Text: Class `text-brand-body` -> Font: Inter, Weight: 400, Size: 1.0rem, Leading: 1.5
이 규칙을 넣어두면 AI가 대괄호 임의값이나 Hex 코드를 직주입하는 빈도가 확연히 줄어듭니다.
프롬프트만으로는 부족합니다. AI는 종종 규칙을 무시하고 몰래 인라인 픽셀값을 섞어 넣습니다. Stylelint와 Git Pre-commit 훅을 엮어서 하드코딩된 스타일이 저장소에 들어오는 길목을 차단합니다.
도구를 설치하고 Husky를 초기화합니다.
pnpm add -D husky lint-staged stylelint stylelint-declaration-strict-value
npx husky init
프로젝트 루트에 stylelint.config.mjs를 생성합니다. 임의의 색상 코드나 픽셀값이 들어오면 빌드 에러를 띄우는 설정입니다.
import type { Config } from "stylelint";
export default {
plugins: ["stylelint-declaration-strict-value"],
rules: {
"scale-unlimited/declaration-strict-value": [
["/color$/", "font-size", "/margin/", "/padding/"],
{
ignoreVariables: false,
ignoreFunctions: false,
ignoreKeywords: {
"": ["transparent", "inherit", "currentColor", "auto", "0"]
},
message: "Design System Violation: Hardcoded value for '${property}' is forbidden. Use CSS Design Tokens instead."
}
]
}
} satisfies Config;
package.json에 lint-staged 설정을 추가합니다.
{
"lint-staged": {
"*.{css,scss,tsx,jsx}": [
"stylelint --fix",
"eslint --max-warnings=0"
]
}
}
.husky/pre-commit 파일에 아래 한 줄을 등록합니다.
npx lint-staged
이제 git commit을 칠 때마다 스테이징된 코드를 정적 분석합니다. AI가 멋대로 넣은 margin: 17px 같은 코드가 있으면 커밋 자체가 실패합니다. 스타일 수정에 쓰던 시간을 주당 5시간 정도 아낄 수 있습니다.
정적 분석은 텍스트 규칙만 검사합니다. 요소가 겹치거나 모바일 화면에서 메뉴가 깨지는 문제는 브라우저를 직접 띄워봐야 압니다. 매번 손으로 뷰포트를 줄였다 늘렸다 할 수는 없으니 Playwright로 스냅샷 비교를 자동화합니다.
playwright.config.ts 파일을 만듭니다.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/visual',
snapshotPixelRatioTemplate: '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{ext}',
expect: {
toHaveScreenshot: {
maxDiffPixelRatio: 0.01,
threshold: 0.2,
animations: 'disabled',
},
},
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
projects: [
{ name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 13'] } },
],
});
핵심 UI 5곳을 검증하는 테스트 스크립트(tests/visual/landing-page.spec.ts)를 작성합니다.
import { test, expect } from '@playwright/test';
test.describe('Visual Regression Guardrails', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
await page.evaluate(() => document.fonts.ready);
});
test('TC1: 데스크톱 히어로 섹션 렌더링', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
const heroSection = page.locator('section#hero');
await expect(heroSection).toBeVisible();
await expect(heroSection).toHaveScreenshot('hero-desktop.png', { maxDiffPixelRatio: 0.01 });
});
test('TC2: 모바일 내비게이션 메뉴 뷰포트 렌더링', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
const navBar = page.locator('header#main-nav');
await expect(navBar).toHaveScreenshot('nav-mobile.png');
});
test('TC3: 요금제 카드 그리드 정렬', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
const pricingGrid = page.locator('div#pricing-cards');
await expect(pricingGrid).toHaveScreenshot('pricing-grid.png', {
mask: [page.locator('.dynamic-price-timestamp')]
});
});
test('TC4: 메인 CTA 버튼 호버 상태', async ({ page }) => {
const ctaButton = page.locator('button#primary-cta');
await ctaButton.hover();
await expect(ctaButton).toHaveScreenshot('cta-button-hover.png');
});
test('TC5: 로그인 모달 레이아웃', async ({ page }) => {
await page.click('button#open-login-modal');
const modalDialog = page.locator('div[role="dialog"]');
await expect(modalDialog).toBeVisible();
await expect(modalDialog).toHaveScreenshot('login-modal.png');
});
});
package.json에 명령어를 등록합니다.
{
"scripts": {
"test:visual": "playwright test",
"test:visual:update": "playwright test --update-snapshots"
}
}
터미널에서 pnpm test:visual 한 줄만 치면 데스크톱과 모바일 뷰포트에서 깨진 레이아웃을 1분 만에 잡아냅니다. 화면 크기 일일이 조절하며 눈 빠지게 검수하던 작업을 치워버릴 수 있습니다.
규칙 파일로 AI의 입력을 통제하고, Git 훅으로 잘못된 스타일의 유입을 막고, Playwright로 화면 깨짐을 확인하는 구조를 갖추면 1인 개발 프로젝트에서도 디자인 일관성을 지킬 수 있습니다.