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.
Sedang melakukan crawling...
Downloading HTML • Analyzing Keyword Density • Checking Schema
Mode Simulasi
Website memblokir akses bot publik. Menampilkan data contoh untuk preview.
Analisis Gagal
Tidak dapat mengakses URL tersebut.
SEO Health Score
Target Keyword: -
Priority Fixes
Top IssuesMenganalisis prioritas...
Butuh Bantuan Optimasi Lanjutan?
Beberapa faktor SEO seperti kecepatan server dan backlink membutuhkan penanganan ahli.
Konsultasi Dengan Pakar
`;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 }
}
});
}