Why AI-Generated Landing Pages All Look the Same and the Solution
TuBrief 편집팀
2026년 8월 22일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
For solo developers who handle everything from planning to deployment on their own, tools like Cursor and Claude Code feel like relief pitchers. Type a few prompts and a plausible-looking webpage pops up in just 10 minutes.
The problem comes next. You hit the deploy button and look at the screen with a sense of déjà vu. Purple gradient buttons, the Inter font, a 3-column card layout smack in the middle. It reeks of a template you've seen everywhere. It looks clean on the surface, but it doesn't lead to user payments or sign-ups. People instantly spot pages that look like they were churned out of a factory and close the tab.
LLMs operate on statistical averages. Without rules, they regress to the most commonly used Tailwind default presets and Shadcn UI defaults on the web. The moment you toss out a prompt like "Create a clean and sophisticated landing page," the AI spews out unidentifiable inline pixel values like w-[320px] and top-[117px], messing up the layout.
Trying to fix this issue just by tweaking prompts gets exhausting. You need to build a pipeline that enforces brand design rules every time code is generated, and physically blocks violating code at the commit stage.
Cursor reads .cursor/rules/*.mdc, Claude Code reads CLAUDE.md, and open-source tools read AGENTS.md. If you define your brand design tokens in these files, the AI is prevented from using arbitrary styles right from the code generation stage.
If a rule file exceeds 500 lines, the context cost for reading it on every session increases, and the model's instruction-following rate drops. It needs to be written clearly within around 200 to 300 lines.
Create .cursor/rules/design-system.mdc in your project root and add the following content.
bg-[#123456] or h-[117px].pnpm lint:style to verify token compliance before completing tasks.var(--color-bg-primary) (Tailwind: bg-brand-primary)var(--color-bg-secondary) (Tailwind: bg-brand-secondary)var(--color-text-main) (Tailwind: text-brand-main)var(--color-text-muted) (Tailwind: text-brand-muted)var(--color-accent-default) (Tailwind: bg-brand-accent)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)text-brand-h1 -> Font: Inter, Weight: 700, Size: 2.5rem, Tracking: -0.02emtext-brand-body -> Font: Inter, Weight: 400, Size: 1.0rem, Leading: 1.5`
Adding these rules significantly reduces the frequency with which the AI directly injects bracketed arbitrary values or hex codes.
Prompts alone are not enough. AI often ignores rules and secretly mixes in inline pixel values. By combining Stylelint and Git pre-commit hooks, you can block hardcoded styles from entering the repository.
Install the tools and initialize Husky.
`bash
pnpm add -D husky lint-staged stylelint stylelint-declaration-strict-value
npx husky init
`
Create stylelint.config.mjs in the project root. This configuration throws a build error if any arbitrary color codes or pixel values sneak in.
`javascript
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;
`
Add lint-staged configuration to your package.json.
`json
{
"lint-staged": {
"*.{css,scss,tsx,jsx}": [
"stylelint --fix",
"eslint --max-warnings=0"
]
}
}
`
Register the following single line in the .husky/pre-commit file.
`bash
npx lint-staged
`
Now, every time you run git commit, it statically analyzes the staged code. If there is code like margin: 17px that the AI snuck in arbitrarily, the commit itself fails. You can save about 5 hours a week previously spent on style fixes.
Static analysis only checks text rules. Issues where elements overlap or navigation menus break on mobile screens require spinning up an actual browser to check. Since you can't manually shrink and stretch the viewport every time, you can automate snapshot comparisons with Playwright.
Create the playwright.config.ts file.
`typescript
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'] } },
],
});
`
Write a test script (tests/visual/landing-page.spec.ts) that validates 5 core UI locations.
`typescript
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: Desktop Hero Section Rendering', 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: Mobile Navigation Menu Viewport Rendering', 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: Pricing Card Grid Alignment', 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: Main CTA Button Hover State', async ({ page }) => {
const ctaButton = page.locator('button#primary-cta');
await ctaButton.hover();
await expect(ctaButton).toHaveScreenshot('cta-button-hover.png');
});
test('TC5: Login Modal Layout', 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');
});
});
`
Register the commands in package.json.
`json
{
"scripts": {
"test:visual": "playwright test",
"test:visual:update": "playwright test --update-snapshots"
}
}
`
By typing a single line pnpm test:visual in the terminal, you can catch broken layouts across desktop and mobile viewports in just one minute. You can eliminate the tedious task of manually adjusting screen sizes and straining your eyes during reviews.
By establishing a structure where rule files control AI input, Git hooks block the inflow of incorrect styles, and Playwright checks for broken screens, you can maintain design consistency even in solo development projects.