On-Page SEO Checker | FruityLOGIC Design

On-Page SEO Checker

Analisis mendalam untuk optimasi konten dan teknis website Anda. Masukkan URL dan Keyword target untuk memulai.

© 2025 FruityLOGIC Design. All rights reserved.

`;async function runAudit(e) { e.preventDefault(); let urlInput = document.getElementById('urlInput').value.trim(); let keywordInput = document.getElementById('keywordInput').value.trim(); if (!/^https?:\/\//i.test(urlInput)) urlInput = 'https://' + urlInput;// UI Reset document.getElementById('loadingState').classList.remove('hidden'); document.getElementById('resultsSection').classList.add('hidden'); document.getElementById('errorState').classList.add('hidden'); document.getElementById('demoToast').classList.add('hidden'); document.getElementById('submitBtn').disabled = true; document.getElementById('submitBtn').classList.add('opacity-50');let htmlContent = null; let isSim = false;// Fetching Strategy (Multi-proxy) try { const res1 = await fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(urlInput)}`); const data1 = await res1.json(); if(data1.contents && data1.contents.trim().startsWith('<')) htmlContent = data1.contents; else throw new Error("Proxy 1 fail"); } catch (err1) { try { const res2 = await fetch(`https://corsproxy.io/?${encodeURIComponent(urlInput)}`); if(res2.ok) htmlContent = await res2.text(); else throw new Error("Proxy 2 fail"); } catch (err2) { try { const res3 = await fetch(`https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(urlInput)}`); if(res3.ok) htmlContent = await res3.text(); else throw new Error("Proxy 3 fail"); } catch (err3) { console.warn("All proxies failed. Simulating."); htmlContent = SIMULATION_HTML; isSim = true; } } }if(isSim) document.getElementById('demoToast').classList.remove('hidden'); analyzeSEO(htmlContent, urlInput, keywordInput); }// Helper to resolve relative URLs function resolveUrl(url, base) { try { return new URL(url, base).href; } catch(e) { return url; } }function analyzeSEO(html, url, keyword) { const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); let domain = ""; try { domain = new URL(url).hostname; } catch(e) { domain = "website"; } const kw = keyword ? keyword.toLowerCase() : "";// === 1. KEYWORD OPTIMIZATION (If keyword provided) === const title = doc.querySelector('title')?.innerText || ""; const h1 = doc.querySelector('h1')?.innerText || ""; const cloneDoc = doc.cloneNode(true); cloneDoc.querySelectorAll('script, style, noscript').forEach(el => el.remove()); const bodyText = cloneDoc.body.innerText.replace(/\s+/g, ' ').trim(); const first150Words = bodyText.split(' ').slice(0, 150).join(' ').toLowerCase(); let keywordChecks = []; if (kw) { // Checklist #15: Keyword in URL const urlLower = url.toLowerCase(); const kwInUrl = urlLower.includes(kw.replace(/\s+/g, '-')) || urlLower.includes(kw.replace(/\s+/g, '')); keywordChecks.push(check("Keyword in URL", kwInUrl ? "Yes" : "No", kwInUrl, url, "URL sebaiknya mengandung kata kunci utama."));// Checklist #17: Front-Load Keyword in Title const titleLower = title.toLowerCase(); const kwInTitle = titleLower.includes(kw); const isFrontLoaded = titleLower.indexOf(kw) >= 0 && titleLower.indexOf(kw) < 15; keywordChecks.push(check("Keyword in Title", kwInTitle ? (isFrontLoaded ? "Front-Loaded" : "Included") : "Missing", kwInTitle, title, "Masukkan keyword di awal judul untuk CTR lebih baik."));// Checklist #20: Keyword in H1 const h1Lower = h1.toLowerCase(); const kwInH1 = h1Lower.includes(kw); keywordChecks.push(check("Keyword in H1", kwInH1 ? "Yes" : "No", kwInH1, h1 || "No H1 Found", "Judul utama (H1) wajib mengandung keyword."));// Checklist #19: Keyword in First 150 Words const kwInIntro = first150Words.includes(kw); keywordChecks.push(check("Keyword in Intro", kwInIntro ? "Yes" : "No", kwInIntro, "Cek 150 kata pertama.", "Sebutkan keyword di paragraf pertama agar relevansi jelas.")); document.getElementById('targetKwDisplay').innerText = keyword; } else { keywordChecks.push(check("Target Keyword", "Not Set", false, "Anda tidak memasukkan keyword.", "Masukkan keyword untuk analisis mendalam.")); document.getElementById('targetKwDisplay').innerText = "(Tidak diset)"; }// === 2. META DATA (Enhanced with Modifiers check) === const desc = doc.querySelector('meta[name="description"]')?.getAttribute('content') || ""; const canonical = doc.querySelector('link[rel="canonical"]')?.getAttribute('href') || ""; // Checklist #18: Title Modifiers (Updated with Indonesian keywords) const modifiers = [ 'terbaik', 'terpercaya', 'profesional', 'nomor 1', 'top', // Quality 'termurah', 'berkualitas', 'promo', 'diskon', 'hemat', 'terjangkau', // Price/Value 'panduan', 'cara', 'tutorial', 'lengkap', 'tips', 'strategi', 'belajar', // Edu '2024', '2025', 'terbaru', 'update', // Time 'jasa', 'jual', 'beli', 'sewa', 'review', 'ulasan' // Transactional ]; const foundModifiers = modifiers.filter(m => title.toLowerCase().includes(m)); const hasModifier = foundModifiers.length > 0; // Logic Penilaian Panjang Title & Desc let titleStatus = "Perfect"; if (!title) titleStatus = "Missing"; else if (title.length < 10) titleStatus = "Too Short"; else if (title.length > 60) titleStatus = "Too Long";let descStatus = "Perfect"; if (!desc) descStatus = "Missing"; else if (desc.length < 50) descStatus = "Too Short"; else if (desc.length > 160) descStatus = "Too Long";const metaChecks = [ check("Page Title", titleStatus, titleStatus === "Perfect", title, `Panjang: ${title.length} chars (Ideal: 10-60).`), check("Title Modifiers", hasModifier ? "Optimized" : "Standard", hasModifier, hasModifier ? `Ditemukan: "${foundModifiers.join(', ')}"` : "Tidak ada kata pemicu klik (Power Words).", "Tambahkan kata sifat/keterangan: 'Terbaik', 'Termurah', '2025', atau 'Panduan Lengkap' agar lebih menarik."), check("Meta Description", descStatus, descStatus === "Perfect", desc, `Panjang: ${desc.length} chars (Ideal: 50-160).`), check("Canonical Link", canonical ? "OK" : "Missing", !!canonical, canonical || "Tag canonical tidak ditemukan.", "Penting untuk mencegah isu duplikat konten.") ];// === 3. CONTENT & STRUCTURE (Enhanced with Schema & Video) === const wordCount = bodyText.split(' ').length; const h2Count = doc.querySelectorAll('h2').length; // Checklist: Schema Markup const schemas = doc.querySelectorAll('script[type="application/ld+json"]'); // Checklist: Multimedia (Video/Iframe) const videos = doc.querySelectorAll('video, iframe[src*="youtube"], iframe[src*="vimeo"]');const contentChecks = [ check("Word Count", wordCount > 300 ? "Good" : "Thin", wordCount > 300, `${wordCount} kata.`, "Konten di bawah 300 kata dianggap 'Thin Content'."), check("H1 Exists", h1.length > 0 ? "Yes" : "Missing", h1.length > 0, h1 || "Tidak ditemukan", "Halaman wajib memiliki H1."), check("Descriptive Subheadings", h2Count > 0 ? "Yes" : "No", h2Count > 0, `${h2Count} H2 ditemukan.`, "Gunakan H2 untuk memecah konten agar mudah dibaca."), check("Schema Markup", schemas.length > 0 ? "Detected" : "Missing", schemas.length > 0, `${schemas.length} schema scripts found.`, "Tambahkan Schema (JSON-LD) untuk Rich Snippet."), check("Multimedia Content", videos.length > 0 ? "Yes" : "No", videos.length > 0, `${videos.length} video embed found.`, "Tambahkan video untuk meningkatkan 'Time on Page'.") ];// === 4. TECHNICAL & LINKS (Enhanced with Image Filename check placeholder) === const images = doc.querySelectorAll('img'); const noAlt = Array.from(images).filter(img => !img.getAttribute('alt') || img.getAttribute('alt').trim() === "").length; const ssl = url.startsWith("https"); const links = doc.querySelectorAll('a'); let internal = 0, external = 0; links.forEach(l => { const href = l.getAttribute('href'); if(href && !href.startsWith('#') && !href.startsWith('javascript')) { if(href.includes(domain) || href.startsWith('/')) internal++; else external++; } });// Checklist #16: Short URL const urlPath = new URL(url).pathname; const isShortUrl = urlPath.length < 70; // Arbitrary simple checkconst techChecks = [ check("Short URL", isShortUrl ? "Yes" : "Long", isShortUrl, url, "URL pendek lebih disukai Google."), check("SSL / HTTPS", ssl ? "Secure" : "Insecure", ssl, ssl ? "HTTPS Aktif" : "HTTP Detected", "Wajib gunakan HTTPS."), check("Image Alt", noAlt === 0 ? "Perfect" : `${noAlt} Missing`, noAlt === 0, noAlt === 0 ? "Semua gambar aman." : `${noAlt} gambar tanpa Alt text.`, "Tambahkan atribut alt pada gambar."), check("Internal Links", internal > 0 ? `${internal} Links` : "None", internal > 0, "Tautan ke halaman sendiri.", "Internal link membantu navigasi bot."), check("External Links", `${external} Links`, true, "Tautan keluar.", "Memberikan referensi meningkatkan kepercayaan.") ];// CALCULATE SCORES const catKw = kw ? calcScore(keywordChecks) : 0; const catMeta = calcScore(metaChecks); const catCont = calcScore(contentChecks); const catTech = calcScore(techChecks);// RENDER UI if(kw) { renderCategory('list-keyword', 'score-keyword', keywordChecks, catKw); document.getElementById('list-keyword').parentElement.classList.remove('hidden'); } else { document.getElementById('list-keyword').parentElement.classList.add('hidden'); } renderCategory('list-meta', 'score-meta', metaChecks, catMeta); renderCategory('list-content', 'score-content', contentChecks, catCont); renderCategory('list-tech', 'score-tech', techChecks, catTech);// TOTAL SCORE & CHART // If keyword is missing, we exclude it from average score calculation const divisor = kw ? 4 : 3; const totalScore = Math.round(( (kw ? catKw : 0) + catMeta + catCont + catTech) / divisor); renderChart(totalScore); document.getElementById('finalScore').innerText = totalScore + '%';// GENERATE TO-DO LIST (Prioritized) let allChecks = [...metaChecks, ...contentChecks, ...techChecks]; if(kw) allChecks = [...keywordChecks, ...allChecks]; generateToDo(allChecks);// FINISH document.getElementById('loadingState').classList.add('hidden'); document.getElementById('resultsSection').classList.remove('hidden'); document.getElementById('submitBtn').disabled = false; document.getElementById('submitBtn').classList.remove('opacity-50'); }// --- HELPER FUNCTIONS ---function check(label, value, passed, desc, fix) { return { label, value, passed, desc, fix, isCritical: !passed && fix.length > 0 }; }function calcScore(checks) { const passed = checks.filter(c => c.passed).length; return Math.round((passed / checks.length) * 100); }function renderCategory(listId, scoreId, checks, score) { document.getElementById(scoreId).innerText = score + '%'; document.getElementById(scoreId).className = `text-sm font-bold ${score >= 80 ? 'text-green-600' : (score >= 50 ? 'text-yellow-600' : 'text-red-600')}`; const container = document.getElementById(listId); container.innerHTML = checks.map(item => `
${item.label}
${item.desc || "Data kosong"}
${!item.passed && item.fix ? `

${item.fix}

` : ''}
${item.value}
`).join(''); }function generateToDo(allChecks) { const container = document.getElementById('todoList'); const issues = allChecks.filter(c => !c.passed); if (issues.length === 0) { container.innerHTML = `

Hebat! Tidak ada isu kritis ditemukan.

`; return; }container.innerHTML = issues.map(item => `
${item.fix}

Masalah pada: ${item.label}

`).join(''); }function renderChart(score) { const ctx = document.getElementById('scoreChart').getContext('2d'); let color = score >= 80 ? '#10b981' : (score >= 50 ? '#f59e0b' : '#ef4444');if (chartInstance) chartInstance.destroy();chartInstance = new Chart(ctx, { type: 'doughnut', data: { labels: ['Score', 'Gap'], datasets: [{ data: [score, 100 - score], backgroundColor: [color, '#e5e7eb'], borderWidth: 0, hoverOffset: 0 }] }, options: { cutout: '85%', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { enabled: false } }, animation: { animateScale: true, animateRotate: true } } }); }