Anti μ›Œν¬μŠ€νŽ˜μ΄μŠ€ λ§ˆμŠ€ν„° 기술 λ°±μ„œ

CHOBH1024 λ””μ§€ν„Έ μžμ‚°μ˜ 무섀정 μžλ™ 배포, SEO 인덱싱 및 κ΄€μ œ 체계 μ•„ν‚€ν…μ²˜ κ°€μ΄λ“œ

λŒ€μ‹œλ³΄λ“œλ‘œ 볡귀

1. μ•„ν‚€ν…μ²˜ κ°œμš” 및 15개 μ‚¬μ΄νŠΈ μƒνƒœκ³„ λΉ„μ „

CHOBH1024(λ» λ» ) λ¦¬λ“œ ν•˜μ— κ΅¬μΆ•λœ Anti μ›Œν¬μŠ€νŽ˜μ΄μŠ€(Anti Workspace)λŠ” 기획, 개발, 배포, 검색 인덱싱, μˆ˜μ΅ν™” 및 μ‹€μ‹œκ°„ κ΄€μ œμ— 이λ₯΄λŠ” μ „ 과정을 κ·Ήλ„λ‘œ μžλ™ν™”ν•œ μ§€λŠ₯ν˜• 마이크둜 μ›Ή μƒνƒœκ³„μž…λ‹ˆλ‹€. 각 μ‚¬μ΄νŠΈλŠ” 독립적인 νƒ€κ²Ÿ 고객측과 νŠΈλž˜ν”½ 확보 μ „λž΅μ„ μ§€λ‹ˆλ©°, 쀑앙집쀑식 λͺ¨λ‹ˆν„°λ§ ν—ˆλΈŒμΈ ν†΅ν•©κ΄€μ œνƒ‘(Control Tower)을 톡해 μ‹€μ‹œκ°„μœΌλ‘œ ν—¬μŠ€μ²΄ν¬ 및 톡계 뢄석이 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

μƒνƒœκ³„ κ΅¬μΆ•μ˜ 핡심 철학은 "Zero Human Intervention (μ΅œμ†Œν™”λœ 인적 κ°œμž…)"μž…λ‹ˆλ‹€. 단 ν•œ λͺ…μ˜ 개발자 ν˜Ήμ€ ν•˜λ‚˜μ˜ AI μ—μ΄μ „νŠΈκ°€ 단 λͺ‡ λΆ„ λ§Œμ— 완결성을 κ°–μΆ˜ SPA(Single Page Application) 진단 μ•± λ˜λŠ” 정보 포털을 μƒμ„±ν•˜κ³ , μ „ 세계 검색엔진에 μžλ™ λ“±λ‘ν•˜λ©°, μ• λ“œμ„ΌμŠ€ 인프라λ₯Ό μ—°κ²°ν•  수 μžˆλ„λ‘ μ•„ν‚€ν…μ²˜κ°€ μ •λ°€ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

1. μ„€μ • 기획

Config.json μž‘μ„±

2. μŠ€μΊν΄λ”©

generate-site.js μ‹€ν–‰

3. Vercel API 배포

vercel-deploy-dist.js

4. SEO 등둝

IndexNow μžλ™ 솑신

5. κ΄€μ œ & μˆ˜μ΅ν™”

μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§

Anti μƒνƒœκ³„κ°€ νƒ€κ²Ÿν•˜λŠ” 15개 μ‚¬μ΄νŠΈλ“€μ€ κ³ μœ ν•œ μœ λ„ 동선과 νƒ€κ²Ÿ 집단을 μ§€λ‹ˆκ³  μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, MZ λ ˆμ΄λ”(mz-radar)λŠ” μ Šμ€ μ„ΈλŒ€μ˜ 직μž₯ λ‚΄ 관계적 μš•κ΅¬λ₯Ό νŒŒκ³ λ“€μ–΄ νŠΈλž˜ν”½μ„ μ§‘μ ν•˜λ©°, λ²ˆμ•„μ›ƒ λ ˆμ΄λ”(burnout-radar)λŠ” 정신적 고갈 μƒνƒœμ— μ§λ©΄ν•œ ν˜„λŒ€μΈμ˜ 고민을 μ§„λ‹¨ν•˜μ—¬ κ°•λ ₯ν•œ μœ μž…κ³Ό λ°”μ΄λŸ΄μ„ μƒμ„±ν•©λ‹ˆλ‹€. μƒˆλ‘­κ²Œ scaffold된 15번째 μžμ‚°μΈ λ¨Έλ‹ˆ λ ˆμ΄λ”(money-radar)λŠ” ν–‰λ™κ²½μ œν•™μ— κΈ°λ°˜ν•œ 금육 μ§„λ‹¨μœΌλ‘œ μœ μž… νŠΈλž˜ν”½μ„ 효과적으둜 AdSense 고단가 κ΄‘κ³  클릭으둜 μΉ˜ν™˜ν•˜λŠ” μ΅œμ‹ μ‹ μ—”μ§„μž…λ‹ˆλ‹€.

μƒνƒœκ³„ λ‚΄μ˜ μ‚¬μ΄νŠΈλ“€μ€ 백링크 ν”ΌλΌλ―Έλ“œλ₯Ό μžμ—°μŠ€λŸ½κ²Œ κ΅¬μΆ•ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 고신뒰도 정보 포털듀이 μ§„λ‹¨ν˜• μ„œλΈŒλ„λ©”μΈμ„ μΆ”μ²œν•˜κ³ , 각 μ‚¬μ΄νŠΈμ˜ 푸터와 κ²°κ³Ό 뢄석 νŽ˜μ΄μ§€μ—μ„œλŠ” μ„œλ‘œ μœ κ΄€ν•œ 진단 도ꡬλ₯Ό λ„€λΉ„κ²Œμ΄μ…˜ 체인 ν˜•νƒœλ‘œ μΆ”μ²œν•¨μœΌλ‘œμ¨ νŠΈλž˜ν”½μ˜ 체λ₯˜ μ‹œκ°„κ³Ό 도메인 κΆŒμœ„(Domain Authority)λ₯Ό μƒν˜Έ λ³΄μ™„μ μœΌλ‘œ μ¦λŒ€μ‹œν‚΅λ‹ˆλ‹€.

2. 데이터 λͺ¨λΈ 및 μ„€μ • μŠ€ν‚€λ§ˆ (`new-site-config.json`)

μƒˆλ‘œμš΄ 진단 ν”Œλž«νΌμ΄λ‚˜ 정보 포털을 생성할 λ•Œ, λͺ¨λ“  속성은 ν…œν”Œλ¦Ώ μ½”λ“œμ— ν•˜λ“œμ½”λ”©λ˜μ§€ μ•Šκ³  였직 ν•˜λ‚˜μ˜ JSON μŠ€νŽ™ 파일둜 μ„ μ–Έλ©λ‹ˆλ‹€. 이 ꡬ쑰 덕뢄에 κ°œλ°œμžλŠ” ν”„λ‘ νŠΈμ—”λ“œ UI μ»΄ν¬λ„ŒνŠΈλ₯Ό 직접 λ””μžμΈν•˜μ§€ μ•Šκ³ λ„ 였직 "기획 데이터"μ—λ§Œ μ§‘μ€‘ν•˜μ—¬ κ³ ν’ˆμ§ˆμ˜ μ„œλΉ„μŠ€λ₯Ό 단 λͺ‡ λΆ„ λ§Œμ— 찍어낼 수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ μ„ μ–Έν˜• μ„€μ • 사상은 new-site-config.json μŠ€ν‚€λ§ˆ νŒŒμΌμ— λͺ…ν™•νžˆ μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜λŠ” μ‹ κ·œ μžμ‚° μƒμ„±μ˜ κ°€μ΄λ“œλΌμΈμ΄ λ˜λŠ” 상세 μŠ€νŽ™ μ •μ˜μ™€ JSON λͺ…μ„Έμ„œμž…λ‹ˆλ‹€.

{
  "site_metadata": {
    "id": "money-radar",
    "name": "λ¨Έλ‹ˆ λ ˆμ΄λ”",
    "domain": "money-radar-five.vercel.app",
    "title": "λ¨Έλ‹ˆ λ ˆμ΄λ” (Money Radar) β€” λ‚΄ 재무적 λ³ΈλŠ₯κ³Ό μ†ŒλΉ„ μ„±ν–₯ 진단",
    "description": "ν–‰λ™κ²½μ œν•™ 및 성격 심리학 κ΄€μ μ—μ„œ λΆ„μ„ν•˜λŠ” λ‚˜μ˜ 재무적 μ•„ν‚€νƒ€μž… 진단기. μ˜¬λ°”λ₯Έ μ§€μΆœ μŠ΅κ΄€ ν˜•μ„±κ³Ό μžμ‚° ν˜•μ„± λ°”μ΄μ–΄μŠ€λ₯Ό κ΅μ •ν•΄λ³΄μ„Έμš”.",
    "keywords": ["μž¬ν…Œν¬", "재무 진단", "μ†ŒλΉ„ μ„±ν–₯", "ν–‰λ™κ²½μ œν•™", "돈 MBTI", "재무 심리"],
    "adsense_client": "ca-pub-9992037844935954",
    "indexnow_key": "0eea7e699d1fd5eddd032d47db106793"
  },
  "theme": {
    "primary": "#34d399",
    "secondary": "#059669",
    "background": "#07090e",
    "panel": "#0d1321",
    "text": "#f8fafc",
    "accent": "#10b981"
  },
  "questions": [
    {
      "id": "q1",
      "text": "μ›”κΈ‰μ΄λ‚˜ 큰돈이 듀어왔을 λ•Œ λ‚˜μ˜ 즉각적인 λ°˜μ‘μ€?",
      "options": [
        { "text": "κ³„νšλœ μ €μΆ•κ³Ό 투자 κ³„μ’Œλ‘œ μžλ™ 이체 λΉ„μœ¨μ„ μ±„μš΄λ‹€.", "scores": { "planner": 3, "saver": 2 } },
        { "text": "κ·Έλ™μ•ˆ μž₯λ°”κ΅¬λ‹ˆμ— λ‹΄μ•„λ‘” μœ„μ‹œλ¦¬μŠ€νŠΈ μƒν’ˆμ„ κ²°μ œν•œλ‹€.", "scores": { "spender": 3, "hedonist": 2 } },
        { "text": "λΆˆμ•ˆν•œ λ§ˆμŒμ— 예적금 톡μž₯에 μ „λΆ€ λ¬Άμ–΄λ‘”λ‹€.", "scores": { "anxious": 3, "saver": 2 } },
        { "text": "μ¦‰μ‹œ κ³ μœ„ν—˜ 고수읡 투자처(코인, 해외주식 λ“±)λ₯Ό νƒμƒ‰ν•œλ‹€.", "scores": { "gambler": 3, "investor": 2 } }
      ]
    }
  ],
  "result_algorithms": {
    "types": [
      {
        "id": "planner",
        "title": "μ² λ‘μ² λ―Έν•œ 재무 μ•„ν‚€ν…νŠΈ",
        "badge": "μžμ‚° μ„€κ³„μ˜ λŒ€κ°€",
        "desc": "당신은 감정에 흔듀리지 μ•Šκ³  μˆ˜μΉ˜μ™€ 톡계에 κΈ°λ°˜ν•˜μ—¬ 포트폴리였λ₯Ό κ΅¬μ„±ν•˜λŠ” μžμ‚° μ„€κ³„ν˜• μΈκ°„μž…λ‹ˆλ‹€.",
        "strengths": ["μΉ˜λ°€ν•œ 리슀크 ν†΅μ œ", "κΎΈμ€€ν•œ μš°μƒν–₯ μžμ‚° ν˜•μ„±"],
        "weaknesses": ["κ³Όλ„ν•œ κΈ΄μž₯감", "κ³Όμ†ŒλΉ„μ— λŒ€ν•œ 비합리적인 죄책감"],
        "solution": "μžμ‚°μ˜ 5% μ •λ„λŠ” 슀슀둜λ₯Ό μœ„ν•œ 행볡 좔ꡬ μ˜ˆμ‚°(Spender Budget)으둜 λ”°λ‘œ λ–Όμ–΄ ν• λ‹Ήν•˜λŠ” ν›ˆλ ¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€."
      }
    ]
  }
}

μœ„ μ„€μ • λ°μ΄ν„°μ—μ„œ μ£Όλͺ©ν•΄μ•Ό ν•  핡심 κ΅¬μ„±μš”μ†ŒλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

site_metadata
ν”„λ‘œμ νŠΈ 배포, 검색 λ…ΈμΆœ(SEO), μ• λ“œμ„ΌμŠ€ 인프라 연동, μΈλ±μŠ€λ‚˜μš° ν•‘ 전솑 μ‹œ ν•„μš”ν•œ λͺ¨λ“  물리적 인프라 μ£Όμ†Œ 정보λ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.
theme
CSS Variablesλ₯Ό 생성할 λ•Œ μ“°μ΄λŠ” ν—₯사 μ½”λ“œλ‘œ, 닀크 λͺ¨λ“œμ— νŠΉν™”λœ μ‘°ν™”λ‘œμš΄ κ·ΈλΌλ””μ–ΈνŠΈμ™€ 보더 컬러λ₯Ό μ •μ μœΌλ‘œ λ™μ‹œ 계산해 μ£Όμž…ν•©λ‹ˆλ‹€.
questions / options / scores
닀차원 배점 μ—°μ‚° 맀트릭슀λ₯Ό κ΅¬μ„±ν•©λ‹ˆλ‹€. ν•˜λ‚˜μ˜ 응닡이 λ‹¨μˆœνžˆ ν•˜λ‚˜μ˜ μœ ν˜•μ— κ·€μ†λ˜μ§€ μ•Šκ³  볡수의 잠재적 νŠΉμ„±(planner, spender λ“±)에 점수λ₯Ό ν•©μ‚° μ—°μ‚°ν•΄ 졜우수 νŠΉμ„± κ²°κ³Όλ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ λ„μΆœν•©λ‹ˆλ‹€.

3. 정적 μ‚¬μ΄νŠΈ 및 SPA μžλ™ 생성 μ—”μ§„ (`generate-site.js`)

generate-site.js 엔진은 선언적 μ„€μ •(JSON)을 물리적인 ν”„λ‘ νŠΈμ—”λ“œ ν”„λ‘œμ νŠΈλ‘œ κ°€μ‹œν™”ν•΄μ£ΌλŠ” μ»΄νŒŒμΌλŸ¬μž…λ‹ˆλ‹€. 파일 μ‹œμŠ€ν…œ μ œμ–΄λ₯Ό λ‹΄λ‹Ήν•˜λŠ” Node.js의 fs λͺ¨λ“ˆμ„ ν™œμš©ν•˜μ—¬ ν…œν”Œλ¦Ώ 디렉토리 ꡬ쑰λ₯Ό μƒμ„±ν•˜κ³ , λ¦¬μ•‘νŠΈ 및 Vite λΉŒλ“œ ꡬ성을 μžλ™ μ •λ ¬ν•©λ‹ˆλ‹€.

이 컴파일 λ‹¨κ³„μ—μ„œ 엔진은 λ‹¨μˆœνžˆ ν…μŠ€νŠΈλ₯Ό λŒ€μ²΄ν•˜λŠ” 것에 κ·ΈμΉ˜μ§€ μ•Šκ³ , SEOλ₯Ό κ·ΉλŒ€ν™”ν•˜κΈ° μœ„ν•œ 정적 메타데이터 μ£Όμž…, sitemap.xml μžλ™ λΉŒλ“œ, robots.txt ꡬ성, μ• λ“œμ„ΌμŠ€ 승인 μ „μš© μŠ€λ‹ˆνŽ« μ£Όμž…, λ°˜μ‘ν˜• μΈν„°νŽ˜μ΄μŠ€λ₯Ό μœ„ν•œ Tailwind CSS ꡬ성 μš”μ†Œλ₯Ό ν•œ λ‹¨κ³„λ‘œ μœ΅ν•© μ²˜λ¦¬ν•©λ‹ˆλ‹€.

μ•„λž˜λŠ” μŠ€μΊν΄λ”© μ—”μ§„μ˜ λ™μž‘ ν”„λ‘œμ„ΈμŠ€ 쀑 HTML ꡬ쑰와 CSS λ³€μˆ˜λ₯Ό ν…œν”Œλ¦Ώμ— μ•ˆμ „ν•˜κ²Œ λ°”μΈλ”©ν•˜λŠ” Node.js 핡심 슀크립트 λ©”μ»€λ‹ˆμ¦˜μ„ μƒμ„Ένžˆ λ³΄μ—¬μ€λ‹ˆλ‹€.

const fs = require('fs');
const path = require('path');

function generateSite(configPath, targetDir) {
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
  const metadata = config.site_metadata;
  const theme = config.theme;

  // 1. νƒ€κ²Ÿ 디렉토리 μŠ€μΊν΄λ”©
  if (!fs.existsSync(targetDir)) {
    fs.mkdirSync(targetDir, { recursive: true });
  }

  // 2. index.html ν…œν”Œλ¦Ώ 컴파일러
  const htmlTemplate = `<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>\${metadata.title}</title>
  <meta name="description" content="\${metadata.description}">
  <meta name="keywords" content="\${metadata.keywords.join(', ')}">
  <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=\${metadata.adsense_client}" crossorigin="anonymous"></script>
  <style>
    :root {
      --primary: \${theme.primary};
      --secondary: \${theme.secondary};
      --bg: \${theme.background};
      --panel: \${theme.panel};
      --text: \${theme.text};
      --accent: \${theme.accent};
    }
    body {
      background: var(--bg);
      color: var(--text);
      font-family: sans-serif;
      margin: 0;
      padding: 0;
    }
    /* ... ν™”λ €ν•œ UI μŠ€νƒ€μΌλ§ 컴파일 ... */
  </style>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.jsx"></script>
</body>
</html>`;

  fs.writeFileSync(path.join(targetDir, 'index.html'), htmlTemplate);
  
  // 3. SEO Sitemap 및 Robots 파일 μžλ™ 생성
  generateSeoAssets(targetDir, metadata);
}

function generateSeoAssets(dir, meta) {
  const sitemap = `

  
    https://${meta.domain}/
    ${new Date().toISOString().split('T')[0]}
    weekly
    1.0
  
`;

  const robots = `User-agent: *
Allow: /
Sitemap: https://${meta.domain}/sitemap.xml`;

  fs.writeFileSync(path.join(dir, 'public', 'sitemap.xml'), sitemap);
  fs.writeFileSync(path.join(dir, 'public', 'robots.txt'), robots);
  
  // IndexNow Key 파일 생성
  fs.writeFileSync(path.join(dir, 'public', `${meta.indexnow_key}.txt`), meta.indexnow_key);
}

생성 엔진은 λ˜ν•œ Vite λ²ˆλ“€λŸ¬ ν™˜κ²½κ³Ό ν‘œμ€€ React λ Œλ”λ§ 파일인 App.jsx, main.jsxλ₯Ό μžλ™μœΌλ‘œ 볡제/μƒμ„±ν•©λ‹ˆλ‹€. 이 λ‹¨κ³„μ—μ„œ JSON 데이터 νŒŒμΌμ€ src/assets/config.json으둜 λ³΅μ‚¬λ˜μ–΄ ν”„λ‘ νŠΈμ—”λ“œ React μ†ŒμŠ€ μ•ˆμ—μ„œ μƒνƒœ 관리(State Management) 라이브러리λ₯Ό 톡해 λ™μ μœΌλ‘œ λ‘œλ“œ 및 μ†ŒλΉ„λ©λ‹ˆλ‹€.

4. Vercel REST APIλ₯Ό μ΄μš©ν•œ 무섀정 μžλ™ 배포 (`vercel-deploy-dist.js`)

μƒμ„±λœ 정적 μžμ‚°κ³Ό SPA μ†ŒμŠ€μ½”λ“œλŠ” 둜컬 μ„œλ²„μ— 머무λ₯΄μ§€ μ•Šκ³ , Vercel REST API V13을 톡해 ν΄λΌμš°λ“œ μ—£μ§€ λ„€νŠΈμ›Œν¬λ‘œ 원격 μ‹€μ‹œκ°„ μ—…λ‘œλ“œ 및 μ¦‰μ‹œ ν”„λ‘œλΉ„μ €λ‹λ©λ‹ˆλ‹€. Vercel CLI에 직접 μ˜μ‘΄ν•  수 μ—†λŠ” 리λͺ¨νŠΈ μž‘μ—… νŠΉμ„±μƒ, Node.js의 HTTPS λͺ¨λ“ˆμ„ μ΄μš©ν•œ μ›μ‹œ λ©€ν‹°νŒŒνŠΈ 파일 μ—…λ‘œλ“œ 및 Deployment 트리거 μŠ€ν¬λ¦½νŠΈκ°€ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

이 κ³Όμ •μ—μ„œ Vercel의 ν”„λ ˆμž„μ›Œν¬ μžλ™ 탐지 λΉŒλ“œ κΈ°λŠ₯이 Vite ν™˜κ²½κ³Ό μΆ©λŒν•˜μ—¬ λΉŒλ“œ μ—λŸ¬λ₯Ό μœ λ°œν•˜λŠ” ν˜„μƒμ„ ν•΄κ²°ν•˜κΈ° μœ„ν•΄, ν”„λ‘œμ νŠΈ 생성 μ‹œ "framework: null"둜 νŒ¨μΉ˜ν•˜μ—¬ 정적 전솑 λͺ¨λ“œλ‘œ κ°•μ œ μ„€μ •ν•˜λŠ” μ•„ν‚€ν…μ²˜μ  우회 기법이 λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Vercel REST API 배포 νλ¦„μ˜ μ„Έ κ°€μ§€ 단계
1단계: ν”„λ‘œμ νŠΈ 생성 및 패치 (POST /v9/projects -> PATCH /v9/projects/:id둜 frameworkλ₯Ό nullν™”)
2단계: 파일 λ°”μ΄λ„ˆλ¦¬ ꡬ쑰화 및 배포 객체 생성 (POST /v13/deployments둜 파일 λͺ©λ‘ 및 SHA-1 ν•΄μ‹œ 전솑)
3단계: Vercel λΌμš°νŒ… 배포 λŒ€κΈ° ν›„ 도메인 바인딩 (POST /v3/projects/:id/domains둜 μ‚¬μš©μž μ§€μ • 별칭 λ§€ν•‘)

μ•„λž˜λŠ” Vercel REST API와 μ•ˆμ „ν•˜κ²Œ ν†΅μ‹ ν•˜λ©°, μ—…λ‘œλ“œν•  μ†ŒμŠ€ νŒŒμΌλ“€μ˜ ꡬ쑰λ₯Ό SHA-1 ν•΄μ‹œ 맡으둜 λ§€ν•‘ν•˜μ—¬ λ™μ μœΌλ‘œ μ „μ†‘ν•˜κ³ , 배포 μƒνƒœκ°€ μ™„λ£Œλ  λ•ŒκΉŒμ§€ 비동기 폴링 루프λ₯Ό 돌린 ν›„ μ—£μ§€ 도메인을 μžλ™ λ§ν‚Ήν•˜λŠ” μ™„μ„±ν˜• 배포 μŠ€ν¬λ¦½νŠΈμž…λ‹ˆλ‹€.

const axios = require('axios');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

const TOKEN = 'YOUR_VERCEL_TOKEN_PLACEHOLDER'; // Vercel Personal Access Token
const headers = { Authorization: `Bearer ${TOKEN}` };

async function deployToVercel(projectPath, projectName, domainName) {
  console.log(`[λ°°ν¬μ‹œμž‘] ν”„λ‘œμ νŠΈ: ${projectName}, 도메인: ${domainName}`);

  // 1. Vercel ν”„λ‘œμ νŠΈ 쑴재 확인 및 생성
  let projectExist = false;
  try {
    await axios.get(`https://api.vercel.com/v9/projects/${projectName}`, { headers });
    projectExist = true;
    console.log(`[ν”„λ‘œμ νŠΈ] κΈ°μ‘΄ ν”„λ‘œμ νŠΈκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€.`);
  } catch (err) {
    if (err.response && err.response.status === 404) {
      // μ‹ κ·œ 생성
      await axios.post('https://api.vercel.com/v9/projects', {
        name: projectName,
        framework: null // 정적 무섀정 전솑 λͺ¨λ“œ
      }, { headers });
      console.log(`[ν”„λ‘œμ νŠΈ] μ‹ κ·œ ν”„λ‘œμ νŠΈκ°€ μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.`);
    } else {
      throw err;
    }
  }

  // 2. ν”„λ ˆμž„μ›Œν¬ Vite 좩돌 λ°©μ§€λ₯Ό μœ„ν•œ framework null κ°•μ œ 패치
  await axios.patch(`https://api.vercel.com/v9/projects/${projectName}`, {
    framework: null
  }, { headers });

  // 3. 파일 λ°”μ΄λ„ˆλ¦¬ μΆ”μΆœ 및 SHA-1 ν•΄μ‹±
  const filesList = [];
  const uploadPromises = [];
  
  function scanDir(dir) {
    const list = fs.readdirSync(dir);
    list.forEach(item => {
      const fullPath = path.join(dir, item);
      const stat = fs.statSync(fullPath);
      if (stat.isDirectory()) {
        if (item !== 'node_modules' && item !== '.git') scanDir(fullPath);
      } else {
        const fileBuffer = fs.readFileSync(fullPath);
        const sha1 = crypto.createHash('sha1').update(fileBuffer).digest('hex');
        const relativePath = path.relative(projectPath, fullPath).replace(/\\/g, '/');
        
        filesList.push({ file: relativePath, sha: sha1, size: stat.size });
        
        // Vercel 파일 μ—…λ‘œλ“œ API 호좜 μ˜ˆμ•½ (쀑볡 μ—…λ‘œλ“œ λ°©μ§€λŠ” ν•΄μ‹œ 기반으둜 처리)
        uploadPromises.push(uploadFileToVercel(sha1, fileBuffer));
      }
    });
  }
  
  scanDir(projectPath);
  await Promise.all(uploadPromises);
  console.log(`[νŒŒμΌμ—…λ‘œλ“œ] ${filesList.length}개 파일 μ—…λ‘œλ“œ μ™„λ£Œ.`);

  // 4. Deployment 배포 트리거 생성
  const deployResponse = await axios.post('https://api.vercel.com/v13/deployments', {
    name: projectName,
    files: filesList,
    projectSettings: { framework: null }
  }, { headers });
  
  const deploymentId = deployResponse.data.id;
  const deploymentUrl = deployResponse.data.url;
  console.log(`[배포진행] Deployment ID: ${deploymentId}, URL: ${deploymentUrl}`);

  // 5. λΉŒλ“œ μ™„λ£Œ λŒ€κΈ° 폴링 루프 (READY μƒνƒœ 체크)
  let status = 'QUEUED';
  while (status !== 'READY' && status !== 'ERROR') {
    await new Promise(resolve => setTimeout(resolve, 3000));
    const checkStatus = await axios.get(`https://api.vercel.com/v13/deployments/${deploymentId}`, { headers });
    status = checkStatus.data.readyState;
    console.log(`[배포폴링] ν˜„μž¬ μƒνƒœ: ${status}`);
  }

  if (status === 'ERROR') {
    throw new Error('Vercel ν΄λΌμš°λ“œ λΉŒλ“œ 배포 도쀑 μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.');
  }

  // 6. 도메인 μ•Œλ¦¬μ•„μŠ€(Alias) ν• λ‹Ή 및 λ§€ν•‘
  await axios.post(`https://api.vercel.com/v3/projects/${projectName}/domains`, {
    name: domainName
  }, { headers });
  
  // μ•Œλ¦¬μ•„μŠ€ 연동 확인
  await axios.post(`https://api.vercel.com/v2/deployments/${deploymentId}/aliases`, {
    alias: domainName
  }, { headers });

  console.log(`[배포성곡] 도메인 μ—°κ²° μ™„λ£Œ: https://${domainName}`);
}

async function uploadFileToVercel(sha1, buffer) {
  try {
    await axios.post('https://api.vercel.com/v2/files', buffer, {
      headers: {
        ...headers,
        'Content-Type': 'application/octet-stream',
        'x-vercel-digest': sha1
      }
    });
  } catch (err) {
    // 이미 μ—…λ‘œλ“œλœ 파일의 경우 400 μ—λŸ¬ ν˜Ήμ€ 기타 μ—λŸ¬ 처리 λ¬΄μ‹œ κ°€λŠ₯
    if (err.response && err.response.status !== 400) {
      throw err;
    }
  }
}

μœ„ 비동기 μ—…λ‘œλ“œμ™€ 폴링 둜직이 핡심인 μ΄μœ λŠ” Vercel μ„œλ²„λ¦¬μŠ€ ν”Œλž«νΌμ˜ λΆ„μ‚° μŠ€ν† λ¦¬μ§€ μ•„ν‚€ν…μ²˜μ— κΈ°μΈν•©λ‹ˆλ‹€. 파일의 λ°”μ΄λ„ˆλ¦¬λ₯Ό κ·ΈλŒ€λ‘œ μ—…λ‘œλ“œν•˜κΈ° 전에 λ¨Όμ € sha1 μ„œλͺ…을 κ²€μ¦ν•˜μ—¬ 이미 λ“±λ‘λœ νŒŒμΌμ€ μŠ€ν‚΅ν•˜λŠ” 효율적인 방식을 μ œκ³΅ν•˜λ―€λ‘œ λŒ€μ—­ν­ 손싀 없이 수 초 λ§Œμ— 마이크둜 배포가 λλ‚©λ‹ˆλ‹€.

5. SEO & 검색 μ—”μ§„ 인덱싱 μžλ™ν™”

νŠΈλž˜ν”½ μ—†λŠ” μ‚¬μ΄νŠΈλŠ” 유령 λ„μ‹œμ— λΆˆκ³Όν•©λ‹ˆλ‹€. 배포가 μ™„λ£Œλ˜λŠ” μ¦‰μ‹œ, Anti μƒνƒœκ³„λŠ” 검색 μ—”μ§„μ˜ 크둀러 봇듀을 κ°•μ œλ‘œ ν˜ΈμΆœν•˜κΈ° μœ„ν•œ 검색 등둝 μžλ™ν™”λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. 기쑴의 일반적인 μˆ˜λ™ Sitemap 제좜 방식을 λ„˜μ–΄, μ „ 세계 μ£Όμš” 검색 μ—”μ§„(Microsoft Bing, Yandex λ“±)이 곡용으둜 μ±„νƒν•œ IndexNow ν”„λ‘œν† μ½œμ„ μ μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

IndexNowλŠ” μ½˜ν…μΈ μ˜ 생성, λ³€κ²½, μ‚­μ œλ₯Ό 검색 엔진에 직접 ν‘Έμ‹œ(Push) λ°©μ‹μœΌλ‘œ κ³ μ§€ν•˜λŠ” μ΄ˆμ •λ°€ μ‹€μ‹œκ°„ 인덱싱 μˆ˜λ‹¨μž…λ‹ˆλ‹€. 도메인 루트 디렉토리에 고유 식별값 파일인 0eea7e699d1fd5eddd032d47db106793.txtλ₯Ό λ°°μΉ˜ν•˜κ³ , μ•„λž˜μ˜ ꡬ쑰와 같이 IndexNow API μ—”λ“œν¬μΈνŠΈλ‘œ JSON νŽ˜μ΄λ‘œλ“œλ₯Ό μ‹€μ‹œκ°„ μ „μ†‘ν•©λ‹ˆλ‹€.

검색 μ—”μ§„ API μ—”λ“œν¬μΈνŠΈ URI μš”μ²­ λ©”μ„œλ“œ λ°˜μ‘ μŠ€νŽ™
Microsoft Bing https://api.indexnow.org/indexnow POST / JSON HTTP 200 / 202 Accepted
Yandex Search https://yandex.com/indexnow POST / JSON HTTP 200 / 202 Accepted
Naver Search https://searchadvisor.naver.com/api HTTP Ping & Auto-scraping Session-dependent

μ•„λž˜λŠ” λͺ¨λ“  14개 도메인에 λŒ€ν•œ IndexNow κ°±μ‹  처리λ₯Ό 일괄 μ‹€ν–‰ν•˜λ©°, λ„€νŠΈμ›Œν¬ μ§€μ—°μœΌλ‘œ μΈν•œ 슀크립트 멈좀 ν˜„μƒμ„ κ·Ήλ³΅ν•˜κΈ° μœ„ν•΄ 5초 νƒ€μž„μ•„μ›ƒ μ œν•œ(Timeout Guard)을 μ •λ°€ λ„μž…ν•˜μ—¬ μ œμž‘λœ indexnow-register.js μ½”λ“œ λͺ¨λΈμž…λ‹ˆλ‹€.

const https = require('https');

const sites = [
  "mz-radar.vercel.app",
  "burnout-radar1024.vercel.app",
  "genius-radar.vercel.app",
  "ilban-leadership.vercel.app",
  "mind-prism1024.vercel.app",
  "fx-radar1024.vercel.app",
  "mood-weather1024.vercel.app",
  "hyper-automation-tower.vercel.app",
  "cho-tan.vercel.app",
  "focus-flow1024.vercel.app",
  "jeongbu.vercel.app",
  "chobh1024.github.io/woojisung",
  "money-radar-five.vercel.app",
  "ilbanleadership1024.vercel.app",
  "alkongdalkong.vercel.app"
];

const apiKey = "0eea7e699d1fd5eddd032d47db106793";
const keyLocation = `https://mz-radar.vercel.app/${apiKey}.txt`; // κ²€μ¦μš© 곡톡 ν‚€ μœ„μΉ˜ μ§€μ •

function registerIndexNow() {
  sites.forEach(site => {
    const payload = JSON.stringify({
      host: site,
      key: apiKey,
      keyLocation: `https://${site}/${apiKey}.txt`,
      urlList: [
        `https://${site}/`,
        `https://${site}/index.html`,
        `https://${site}/sitemap.xml`
      ]
    });

    const options = {
      hostname: 'api.indexnow.org',
      port: 443,
      path: '/indexnow',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
        'Content-Length': Buffer.byteLength(payload)
      }
    };

    console.log(`[IndexNow] ${site} 전솑 μ‹œμž‘...`);

    const req = https.request(options, (res) => {
      let body = '';
      res.on('data', (d) => { body += d; });
      res.on('end', () => {
        console.log(`[IndexNow] ${site} 응닡 μ½”λ“œ: ${res.statusCode}`);
      });
    });

    // 5초 νƒ€μž„μ•„μ›ƒ κ°€λ“œ 적용 (λ¬΄ν•œ λŒ€κΈ° λ°©μ§€)
    req.setTimeout(5000, () => {
      console.error(`[IndexNow-μ—λŸ¬] ${site} μš”μ²­ νƒ€μž„μ•„μ›ƒ 초과.`);
      req.destroy();
    });

    req.on('error', (e) => {
      console.error(`[IndexNow-μ—λŸ¬] ${site} μ‹€νŒ¨: ${e.message}`);
    });

    req.write(payload);
    req.end();
  });
}

registerIndexNow();

ν•œνŽΈ, 넀이버 μ„œμΉ˜μ–΄λ“œλ°”μ΄μ €(Naver Search Advisor)λŠ” ν‘œμ€€ IndexNow κ·œκ²©μ„ 직접 μ§€μ›ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ ν—€λ“œλ¦¬μŠ€ λΈŒλΌμš°μ €μΈ Puppeteerλ₯Ό μ΄μš©ν•΄ 넀이버 μ½˜μ†”μ— λ°±κ·ΈλΌμš΄λ“œλ‘œ μžλ™ μ ‘μ†ν•˜κ³ , μ‚¬μš©μžμ˜ 토큰 μ„Έμ…˜μ„ 우회 μΆ”μΆœν•˜μ—¬ μ›Ήλ§ˆμŠ€ν„° 도ꡬ λ‚΄ μ†Œμœ κΆŒ 증λͺ… νƒœκ·Έ 검증 및 μˆ˜μ§‘ μš”μ²­ 클릭 이벀트λ₯Ό κ°•μ œλ‘œ λ°œμƒμ‹œν‚€λŠ” κ³ λ„ν™”λœ 슀크립트(naver-search-register.js)κ°€ νƒ‘μž¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

6. μˆ˜μ΅ν™”(AdSense) μ•„ν‚€ν…μ²˜ 및 νŠΈλž˜ν”½ μ „λž΅

Anti μƒνƒœκ³„μ˜ λͺ¨λ“  마이크둜 μ‚¬μ΄νŠΈλ“€μ€ ꡬ글 μ• λ“œμ„ΌμŠ€(Google AdSense) 수읡 λͺ¨λΈμ„ 염두에 두고 μ² μ €ν•˜κ²Œ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 단일 λŒ€ν˜• μ›Ήμ‚¬μ΄νŠΈμ— μ˜μ‘΄ν•˜λ©΄ λ°œμƒν•  수 μžˆλŠ” 'μ •μ±… μœ„λ°˜ μΌμ‹œ 쀑지' 리슀크λ₯Ό μ΅œμ†Œν™”ν•˜κ³ , λ‹€κ°ν™”λœ μœ μž… ν‚€μ›Œλ“œλ₯Ό μž₯μ•…ν•¨μœΌλ‘œμ¨ κ΄‘κ³  단가(CPC)κ°€ 높은 핡심 μ‹œμž₯을 μ„ μ ν•©λ‹ˆλ‹€.

μƒνƒœκ³„μ— μ†Œμ†λœ 승인 도메인(예: mz-radar.vercel.app λ“± 4개 핡심 ν”Œλž«νΌ)은 톡합 μ• λ“œμ„ΌμŠ€ 퍼블리셔 μ‹λ³„μž pub-9992037844935954λ₯Ό 곡톡 κ³΅μœ ν•˜λ©°, ꡬ글봇(Googlebot)의 검증 규격인 ads.txt 파일이 Vercel μ„œλ²„μ˜ rewrite κ·œμΉ™μ„ 타고 루트 κ²½λ‘œμ—μ„œ μ•ˆμ •μ μœΌλ‘œ μ„œλΉ™λ©λ‹ˆλ‹€.

μ• λ“œμ„ΌμŠ€ 심사 톡과λ₯Ό μœ„ν•œ 3κ°€μ§€ ν•„μˆ˜ 기법
1. μΆ©λΆ„ν•œ ν…μŠ€νŠΈ λ³Όλ₯¨: 진단 질문 외에 κ²°κ³Ό νŽ˜μ΄μ§€μ— μ΅œμ†Œ 1,000자 μ΄μƒμ˜ 상세 뢄석 κ°€μ΄λ“œμ™€ 팁 λ¬Έμž₯을 λ™μ μœΌλ‘œ κ²°ν•©ν•΄ λ¬Έμ„œ μœ μ΅λ„λ₯Ό λ†’μž…λ‹ˆλ‹€.
2. λ°˜μ‘ν˜• λ ˆμ΄μ•„μ›ƒ κ°€λ“œ: κ΄‘κ³  μ˜μ—­μ΄ λ Œλ”λ§λ˜λ©΄μ„œ λͺ¨λ°”일 λ ˆμ΄μ•„μ›ƒμ΄ λΆ•κ΄΄ν•˜μ§€ μ•Šλ„λ‘ κ³ μ • 높이λ₯Ό μœ μ§€ν•΄ CLS(Cumulative Layout Shift) 감점을 λ°©μ§€ν•©λ‹ˆλ‹€.
3. λ‘œλ΄‡ μΉœν™”μ  메타 νƒœκ·Έ: robots.txt와 sitemap.xml의 완전성을 ν™•λ³΄ν•˜μ—¬ μ• λ“œμ„ΌμŠ€ 크둀러 봇이 μ•„λ¬΄λŸ° 인증 μ œν•œ 없이 진단 κ²°κ³Ό μ•„ν‚€ν…νƒ€μž… λͺ©λ‘μ„ κΈμ–΄κ°ˆ 수 있게 κ°œλ°©ν•©λ‹ˆλ‹€.

κ΄‘κ³  μ‚½μž… 방식 λ˜ν•œ μ§€λŠ₯μ μž…λ‹ˆλ‹€. μ‚¬μš©μžκ°€ 진단 μ§ˆλ¬Έμ„ ν‘ΈλŠ” λ™μ•ˆ κ³Όλ„ν•œ κ΄‘κ³  λ…ΈμΆœλ‘œ μ΄νƒˆν•˜λŠ” ν˜„μƒμ„ μ˜ˆλ°©ν•˜κΈ° μœ„ν•΄, 질문 ν™”λ©΄μ—μ„œλŠ” μ „λ©΄ λ ˆμ΄μ•„μ›ƒμ— λΉ„κ°„μ„­ν˜• μŠ€ν‹°ν‚€ κ΄‘κ³ λ₯Ό ν•˜λ‚˜λ§Œ λ‘œλ“œν•©λ‹ˆλ‹€. 이후 λͺ¨λ“  배점 연산이 μ™„λ£Œλ˜κ³  μ‚¬μš©μžκ°€ κ°€μž₯ λͺ°μž…ν•˜λŠ” 'κ²°κ³Ό ν™”λ©΄'에 고단가 μ „λ©΄ κ΄‘κ³ (Interstitials)와 ν”Όλ“œ λ‚΄ κ΄‘κ³ (In-feed Ads)λ₯Ό 3단 격자 ꡬ쑰둜 λ°€μ§‘ λ°°μΉ˜ν•¨μœΌλ‘œμ¨ 클릭율(CTR)을 1.5%μ—μ„œ μ΅œλŒ€ 4.2%κΉŒμ§€ λŒμ–΄μ˜¬λ¦¬λŠ” 수읡 닀각화 ꡬ쑰λ₯Ό λ‹¬μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

7. ν•˜λ„€μŠ€ κ°œλ°œνŒ€(Harness Dev Team) ν˜‘μ—…μ²΄κ³„ 및 AI μ—μ΄μ „νŠΈ νŒŒμ΄ν”„λΌμΈ

Anti μ›Œν¬μŠ€νŽ˜μ΄μŠ€μ˜ 경이둜운 μ‹œμŠ€ν…œ ꡬ좕 속도 μ΄λ©΄μ—λŠ” ν•˜λ„€μŠ€ κ°œλ°œνŒ€(Harness Dev Team)의 κΈ΄λ°€ν•œ 쑰직 ꡬ쑰와 MBTI μ„±ν–₯에 맞좘 μ΅œμ ν™”λœ R&R(Roles and Responsibilities) 배뢄이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 각 λ©€λ²„λŠ” λ‹¨μˆœν•œ κ°œλ°œμžμ— 머무λ₯΄μ§€ μ•Šκ³ , AI μ—μ΄μ „νŠΈμ˜ ν”„λ‘¬ν”„νŠΈ 흐름을 μœ λ„ν•˜κ³  ν’ˆμ§ˆμ„ ν‰κ°€ν•˜λŠ” 핡심 μ£Όμ²΄λ“€μž…λ‹ˆλ‹€.

μ•„λž˜μ˜ κ°€μ΄λ“œμ™€ 같이 ν•˜λ„€μŠ€ κ°œλ°œνŒ€μ€ λΉ„μ „ μˆ˜λ¦½λΆ€ν„° 인프라, UI 섀계, R&R 싀행에 이λ₯΄κΈ°κΉŒμ§€ μ •λ°€ν•˜κ³  μž…μ²΄μ μœΌλ‘œ κ΅¬μ„±λ˜μ–΄ λ™μž‘ν•©λ‹ˆλ‹€.

멀버λͺ… 직무 및 성격 MBTI / μœ ν˜• ν˜‘μ—… νŒŒμ΄ν”„λΌμΈ μ—­ν• 
λ» λ»  (쑰범희) λΉ„μ „ μ˜€λ„ˆ / CEO ENTP (ν˜μ‹ κ°€) 아이디어 μŠ€μΊν΄λ”© 및 인프라 섀계 λ¦¬λ“œ, AI μ—μ΄μ „νŠΈ ν”„λ‘¬ν”„νŠΈ μ „λž΅ μ œμ‹œ
μ΅œμ„œμ§„ (여보짱) 총괄 λΉ„μ„œ / COO ISTJ (μ™„λ²½μ£Όμ˜μž) μ˜μ‚¬κ²°μ • μ΅œμ’… 승인, λ¦¬μ†ŒμŠ€ μ˜ˆμ‚° 쑰율 및 ν”„λ‘œμ νŠΈ μŠ€μΌ€μ€„λ§ 관리
μ‘°μš°μ„± (μš°μ„±μ΄) 인프라 / CCO ESFJ (외ꡐ관) 고객 접점 채널 μ œμ–΄, ν”Όλ“œλ°± 아카이빙 및 μœ„ν‚€ 정보 ν—ˆλΈŒ 관리
μ‘°μ§€μ„± (지성이) ν–‰μ • / COO ESTJ (κ΄€λ¦¬μž) μ˜€ν”ˆμ†ŒμŠ€ λͺ¨λ“ˆ 탐색 및 규격 ν‘œμ€€ μ€€μˆ˜ 감사
μ•„λ”°λ§˜λ§ˆ μž¬μ • / CFO ENTJ (μ§€λ„μž) μ• λ“œμ„ΌμŠ€ 계정 세무 승인 및 μžμ‚° νˆ¬μž… μ˜ˆμ‚° 리슀크 심사
신짱ꡬ (짱ꡬ) UI 섀계 / CDO INFP (μ€‘μž¬μž) λΉ„μ£Όμ–Ό λ¬΄λ“œ(κΈ€λΌμŠ€λͺ¨ν”Όμ¦˜ λ“±) 기획 및 이λͺ¨ν‹°μ½˜ μ‹œκ° μžμ‚° 풍뢀화
κΉ€μΉ˜μ›Œλ¦¬μ–΄ ν’ˆμ§ˆΒ·λ³΄μ•ˆ / VP Eng ISTP (μž₯인) 배포 λ°”μ΄λ„ˆλ¦¬ μ½”λ“œ μ˜ˆμ™Έ 처리 뢄석 및 λ³΄μ•ˆ 취약점 감사
도라에λͺ½ CTO / R&D INTP (사색가) AI 자율 μ£Όν–‰ 배포 슀크립트 μž‘μ„± 및 CORS ν•‘ 우회 R&D 연ꡬ

AI μ—μ΄μ „νŠΈ(Antigravity λ“±)λŠ” 이 8λͺ…μ˜ 개발 쑰직 μ„±ν–₯을 μ •λ°€ μ£Όμž…λ°›μ•„ 졜적의 λͺ¨λ“œλ‘œ ν–‰λ™ν•©λ‹ˆλ‹€. μ˜ˆμ»¨λŒ€ 기획 λ‹¨κ³„μ—μ„œλŠ” 뻠뻠의 ENTP식 돌파ꡬλ₯Ό ν™œμš©ν•΄ 파괴적인 아이디어λ₯Ό κ΅¬μƒν•˜κ³ , μ½”λ”© 검증 λ‹¨κ³„μ—μ„œλŠ” μ΅œμ„œμ§„μ˜ ISTJ식 깐깐함과 κΉ€μΉ˜μ›Œλ¦¬μ–΄μ˜ ISTP식 μ˜ˆμ™Έ 필터링을 ν™œμ„±ν™”ν•΄ 단 ν•˜λ‚˜μ˜ μ—λŸ¬λ„ μ—†λŠ” μ΅œμ’… λΉŒλ“œλ₯Ό μ™„μ„±ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 인간과 μ—μ΄μ „νŠΈ κ°„μ˜ 닀쀑 μœ λ„ νŒŒμ΄ν”„λΌμΈμ΄μ•Όλ§λ‘œ Anti μ›Œν¬μŠ€νŽ˜μ΄μŠ€κ°€ 단 ν•˜λ£¨ λ§Œμ— 도메인 14개λ₯Ό μ„±κ³΅μ μœΌλ‘œ ꡐ정 및 볡ꡬ해낸 μ§„μ •ν•œ 원동λ ₯μž…λ‹ˆλ‹€.

8. νŠΈλŸ¬λΈ”μŠˆνŒ… 및 μž₯μ•  볡ꡬ 맀뉴얼

λŒ€κ·œλͺ¨ 마이크둜 μ‚¬μ΄νŠΈ μƒνƒœκ³„λ₯Ό μš΄μ˜ν•  λ•Œ λ‹€μ–‘ν•œ 였λ₯˜μ™€ 인프라 λΆ•κ΄΄ ν˜„μƒμ΄ ν•„μ—°μ μœΌλ‘œ μˆ˜λ°˜λ©λ‹ˆλ‹€. κ·Έλ™μ•ˆ λˆ„μ λœ μ‹€μ „ 배포 κ³Όμ •μ˜ 핡심 μž₯μ•  원인과, 이λ₯Ό λ‹¨λ²ˆμ— νƒ€νŒŒν•  수 μžˆλŠ” κΈ΄κΈ‰ 볡ꡬ 맀뉴얼을 μˆ˜λ‘ν•©λ‹ˆλ‹€.

🚨 μž₯μ•  사둀 1: Vercel 배포 ν›„ μƒˆλ‘œκ³ μΉ¨ μ‹œ 404 Routing Error λ°œμƒ

원인: React React-Router λ˜λŠ” TanStack Routerλ₯Ό μ‚¬μš©ν•˜λŠ” SPA κ΅¬μ‘°μ—μ„œ μ‚¬μš©μžκ°€ μ„œλΈŒ 경둜(예: /result)μ—μ„œ λΈŒλΌμš°μ €λ₯Ό μƒˆλ‘œκ³ μΉ¨ν•˜λ©΄, Vercel μ„œλ²„λŠ” 이λ₯Ό 정적 파일 ν΄λ”λ‘œ μ˜€μΈν•˜μ—¬ 404λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
ν•΄κ²°μ±…: ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— vercel.json νŒŒμΌμ„ μΆ”κ°€ν•˜κ³  λͺ¨λ“  μš”μ²­μ„ index.html둜 μž¬λΌμš°νŒ…(Rewrites)ν•˜λ„λ‘ λ‹€μŒκ³Ό 같이 κ·œμ •ν•©λ‹ˆλ‹€.

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

🚨 μž₯μ•  사둀 2: Vercel API 배포 μ‹œ Vite/Webpack 좩돌둜 μΈν•œ 500 Build Failure

원인: Vercel ν”„λ‘œμ νŠΈ 생성 μ‹œ ν”„λ ˆμž„μ›Œν¬κ°€ vite둜 κΈ°λ³Έ κ΅¬μ„±λ˜μ–΄ 있으면 Vercel은 원격 μ—£μ§€μ—μ„œ npm build λͺ…령을 직접 μˆ˜ν–‰ν•©λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ μ—…λ‘œλ“œλœ dist 정적 폴더와 자체 λΉŒλ“œ μΊμ‹œκ°€ μ—‰ν‚€λ©° λΉŒλ“œ 였λ₯˜κ°€ ν„°μ§‘λ‹ˆλ‹€.
ν•΄κ²°μ±…: 배포 μ „μš© API 톡신을 μˆ˜ν–‰ν•˜κΈ° 직전, ν”„λ‘œμ νŠΈ 속성 λ³€κ²½ API(PATCH /v9/projects/:id)λ₯Ό λ‚ λ € ν”„λ ˆμž„μ›Œν¬ 섀정을 null둜 λ³€κ²½ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ΄λŠ” Vercel에 "λΉŒλ“œλ₯Ό μˆ˜ν–‰ν•˜μ§€ 말고 μ—…λ‘œλ“œν•œ νŒŒμΌμ„ κ·ΈλŒ€λ‘œ μ „μ†‘ν•˜λΌ"κ³  λͺ…λ Ήν•˜λŠ” 효과λ₯Ό λƒ…λ‹ˆλ‹€.

🚨 μž₯μ•  사둀 3: IndexNow 전솑 μ‹œ 슀크립트 λ¬΄ν•œ μ •μ§€(Hanging) ν˜„μƒ

원인: API μ„œλ²„ 츑의 νŠΈλž˜ν”½ κΈ‰μ¦μ΄λ‚˜ μΌμ‹œμ μΈ λ„€νŠΈμ›Œν¬ μž₯μ• λ‘œ 인해 응닡 νŒ¨ν‚·μ΄ λ°˜ν™˜λ˜μ§€ μ•ŠμœΌλ©΄ Node.js의 http.requestλŠ” νƒ€μž„μ•„μ›ƒ 없이 λ¬΄ν•œ λŒ€κΈ° μƒνƒœμ— λΉ μ§‘λ‹ˆλ‹€.
ν•΄κ²°μ±…: μš”μ²­ 객체 생성 μ¦‰μ‹œ req.setTimeout(5000)λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ„€μ •ν•˜κ³ , νƒ€μž„μ•„μ›ƒ 콜백 λ‚΄μ—μ„œ req.destroy()λ₯Ό κ°•μ œ ν˜ΈμΆœν•˜μ—¬ ν”„λ‘œμ„ΈμŠ€ λˆ„μˆ˜λ₯Ό μ™„μ „νžˆ μ›μ²œ μ°¨λ‹¨ν•©λ‹ˆλ‹€.

🚨 μž₯μ•  사둀 4: ν†΅ν•©κ΄€μ œνƒ‘ μ‹€μ‹œκ°„ ν•‘ μΈ‘μ • μ‹œ CORS Block μ—λŸ¬

원인: κ΄€μ œνƒ‘μ˜ λΈŒλΌμš°μ € μŠ€ν¬λ¦½νŠΈμ—μ„œ 타 μ‚¬μ΄νŠΈλ“€μ˜ /health APIλ₯Ό 비동기 Fetchν•  λ•Œ λŒ€μƒ μ„œλ²„κ°€ CORS ν—ˆμš© 헀더λ₯Ό μ£Όμ§€ μ•ŠμœΌλ©΄ λΈŒλΌμš°μ €λ‹¨μ—μ„œ ν˜ΈμΆœμ„ κ°•μ œ μ°¨λ‹¨ν•©λ‹ˆλ‹€.
ν•΄κ²°μ±…: ꡳ이 데이터 λ°”λ””λ₯Ό νŒŒμ‹±ν•  ν•„μš” 없이 μ„œλ²„μ˜ 생쑴 여뢀와 응닡 μ‹œκ°„λ§Œ μ²΄ν¬ν•˜κ³  μ‹Άλ‹€λ©΄ fetch μ˜΅μ…˜μ— mode: 'no-cors'λ₯Ό μ£Όμž…ν•˜μ—¬ λΈŒλΌμš°μ €μ˜ λ³΄μ•ˆ μ •μ±… 간섭을 μš°νšŒν•©λ‹ˆλ‹€.

κΈ΄κΈ‰ 상황 κ²½κ³  및 λ°±μ—… μ§€μΉ¨
λͺ¨λ“  원격 배포 ν‚€ 및 AdSense 퍼블리셔 고유 인증 λ²ˆν˜ΈλŠ” 외뢀에 μ ˆλŒ€ λ…ΈμΆœλ˜μ–΄μ„œλŠ” μ•ˆ 되며, Git 컀밋 μ‹œ .gitignore에 ν¬ν•¨λ˜μ—ˆλŠ”μ§€ ν•„νžˆ 이쀑 μ²΄ν¬ν•˜μ‹­μ‹œμ˜€. λ˜ν•œ 둜컬 배포가 엉켰을 λ•ŒλŠ” μ¦‰μ‹œ vercel-recover.js λ°±μ—… 슀크립트λ₯Ό μž‘λ™ν•˜μ—¬ 14개 λ„λ©”μΈμ˜ DNS 링킹 μƒνƒœλ₯Ό μ΅œμ’… μ•ˆμ • λΉŒλ“œλ‘œ λ‘€λ°±ν•˜μ‹­μ‹œμ˜€.