Skip to content

Instantly share code, notes, and snippets.

@Ronkiro
Created February 5, 2026 20:58
Show Gist options
  • Select an option

  • Save Ronkiro/7cc56135433298df411da67dda21fd81 to your computer and use it in GitHub Desktop.

Select an option

Save Ronkiro/7cc56135433298df411da67dda21fd81 to your computer and use it in GitHub Desktop.
SEO Score suggestion
/**
* Calcula o SEO Score (v1) para artigos do Portal A Granja.
* * Regras baseadas no Backlog Épico B (Conteúdo Inteligente):
* - Foco na Palavra-chave (Título, Lead, Slug)
* - Volume de Conteúdo (Tamanho do texto)
* - Meta-dados (Tamanho do título)
* * @param {string} title - O título principal (H1) do artigo.
* @param {string} slug - A URL amigável do artigo.
* @param {string} content - O corpo do texto do artigo (plain text).
* @param {string} keyword - A palavra-chave foco definida pelo redator.
* @returns {Object} - Objeto contendo { score: number, checks: Array }
*/
export const calculateSeoScore = (title, slug, content, keyword) => {
// 1. Normalização de entradas
const safeTitle = (title || "").trim();
const safeSlug = (slug || "").trim().toLowerCase();
const safeContent = (content || "").trim();
const safeKeyword = (keyword || "").trim().toLowerCase();
// Variáveis de Estado
let score = 0;
let checks = [];
// --- CRITÉRIO 0: Definição da Palavra-chave (Pré-requisito) ---
// Se não houver palavra-chave, a análise fica comprometida.
if (!safeKeyword) {
return {
score: 0,
checks: [
{
label: "Defina uma palavra-chave foco para iniciar a análise",
status: "error" // Bloqueante
}
]
};
} else {
// Ganha 10 pontos apenas por definir a estratégia (Incentivo ao redator)
score += 10;
checks.push({
label: "Palavra-chave foco definida",
status: "success"
});
}
// --- CRITÉRIO A: Palavra-chave no Título (Peso: 25 pontos) ---
// O H1 é o fator on-page mais importante para o Google.
const lowerTitle = safeTitle.toLowerCase();
if (lowerTitle.includes(safeKeyword)) {
score += 25;
checks.push({
label: "Palavra-chave presente no Título (H1)",
status: "success"
});
} else {
checks.push({
label: `Inclua a palavra-chave "${safeKeyword}" no Título`,
status: "error"
});
}
// --- CRITÉRIO B: Palavra-chave no Lead/Início (Peso: 20 pontos) ---
// Aparecer nos primeiros 200 caracteres indica relevância imediata.
// Usamos 300 chars como margem de segurança para o primeiro parágrafo.
const leadText = safeContent.substring(0, 300).toLowerCase();
if (leadText.includes(safeKeyword)) {
score += 20;
checks.push({
label: "Palavra-chave presente no início do texto (Lead)",
status: "success"
});
} else {
checks.push({
label: "A palavra-chave deve aparecer no primeiro parágrafo",
status: "warning"
});
}
// --- CRITÉRIO C: Palavra-chave na URL/Slug (Peso: 15 pontos) ---
// URLs amigáveis com keywords ajudam no CTR da SERP.
if (safeSlug.includes(safeKeyword)) {
score += 15;
checks.push({
label: "Palavra-chave presente na URL (Slug)",
status: "success"
});
} else {
checks.push({
label: "A URL (Slug) deve conter a palavra-chave",
status: "warning"
});
}
// --- CRITÉRIO D: Tamanho do Título (Peso: 10 pontos) ---
// Títulos entre 30 e 60 caracteres não são cortados no Google.
if (safeTitle.length >= 30 && safeTitle.length <= 60) {
score += 10;
checks.push({
label: "Tamanho do título ideal (30-60 caracteres)",
status: "success"
});
} else if (safeTitle.length < 30) {
checks.push({
label: "Título muito curto (min: 30 caracteres)",
status: "warning"
});
} else {
checks.push({
label: "Título muito longo (max: 60 caracteres)",
status: "warning"
});
}
// --- CRITÉRIO E: Densidade/Volume de Conteúdo (Peso: 20 pontos) ---
// Artigos profundos rankeiam melhor. Meta mínima: 300 palavras.
const wordCount = safeContent.split(/\s+/).filter(w => w.length > 0).length;
const MIN_WORDS = 300;
if (wordCount >= MIN_WORDS) {
score += 20;
checks.push({
label: `Conteúdo com bom tamanho (${wordCount} palavras)`,
status: "success"
});
} else if (wordCount > 50) {
// Pontuação parcial para incentivar continuar escrevendo
const partialScore = Math.floor((wordCount / MIN_WORDS) * 10);
score += partialScore;
checks.push({
label: `Texto curto: ${wordCount}/${MIN_WORDS} palavras recomendadas`,
status: "warning"
});
} else {
checks.push({
label: "Conteúdo insuficiente (escreva mais de 300 palavras)",
status: "error"
});
}
// --- Ajuste Final (Cap) ---
// Garante que a nota nunca passe de 100 devido a arredondamentos ou bônus futuros
score = Math.min(100, Math.max(0, score));
return { score, checks };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment