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)μΌλ‘ λ°λ‘ λΌμ΄ ν λΉνλ νλ ¨μ΄ νμν©λλ€."
}
]
}
}
μ μ€μ λ°μ΄ν°μμ μ£Όλͺ©ν΄μΌ ν ν΅μ¬ ꡬμ±μμλ λ€μκ³Ό κ°μ΅λλ€:
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"λ‘ ν¨μΉνμ¬ μ μ μ μ‘ λͺ¨λλ‘ κ°μ μ€μ νλ μν€ν μ²μ μ°ν κΈ°λ²μ΄ λμ λμμ΅λλ€.
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 κ·μΉμ νκ³ λ£¨νΈ κ²½λ‘μμ μμ μ μΌλ‘ μλΉλ©λλ€.
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'λ₯Ό μ£Όμ
νμ¬ λΈλΌμ°μ μ 보μ μ μ±
κ°μμ μ°νν©λλ€.
.gitignoreμ ν¬ν¨λμλμ§ νν μ΄μ€ 체ν¬νμμμ€. λν λ‘컬 λ°°ν¬κ° μμΌ°μ λλ μ¦μ vercel-recover.js λ°±μ
μ€ν¬λ¦½νΈλ₯Ό μλνμ¬ 14κ° λλ©μΈμ DNS λ§νΉ μνλ₯Ό μ΅μ’
μμ λΉλλ‘ λ‘€λ°±νμμμ€.