Created
February 5, 2026 20:58
-
-
Save Ronkiro/7cc56135433298df411da67dda21fd81 to your computer and use it in GitHub Desktop.
SEO Score suggestion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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