"use client"; import { useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { FiCheckCircle, FiAlertTriangle, FiInfo, FiDownload, FiExternalLink, } from "react-icons/fi"; const SEOChecker = () => { const searchParams = useSearchParams(); const site = searchParams.get("site"); const [url, setUrl] = useState(site); const [seoData, setSeoData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState("summary"); const submitRef = useRef(); useEffect(() => { if (site) { submitRef.current.click(); } }, [site]) const checkSEO = async () => { if (!url) { setError("Please enter a URL"); return; } setLoading(true); setError(null); setSeoData(null); try { const response = await fetch("/api/seo-check", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ url }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to analyze URL"); } const data = await response.json(); setSeoData(data); setActiveTab("summary"); } catch (err) { setError(err.message || "Failed to check SEO"); console.error("SEO check error:", err); } finally { setLoading(false); } }; const calculateScores = (data) => { if (!data) return { score: 0, deductions: [], categoryScores: {} }; let score = 100; const deductions = []; // Title (15 points) if (!data.title.exists) { score -= 15; deductions.push({ item: "Title Tag", points: -15, message: "Missing completely", }); } else { if (data.title.text.length < 30) { score -= 7; deductions.push({ item: "Title Length", points: -7, message: "Too short (under 30 chars)", }); } else if (data.title.text.length > 60) { score -= 5; deductions.push({ item: "Title Length", points: -5, message: "Too long (over 60 chars)", }); } } // Meta Description (10 points) if (!data.meta.description.exists) { score -= 10; deductions.push({ item: "Meta Description", points: -10, message: "Missing completely", }); } else { if (data.meta.description.text.length < 50) { score -= 5; deductions.push({ item: "Meta Description", points: -5, message: "Too short (under 50 chars)", }); } else if (data.meta.description.text.length > 160) { score -= 3; deductions.push({ item: "Meta Description", points: -3, message: "Too long (over 160 chars)", }); } } // Headings (15 points) if (data.headings.h1.count === 0) { score -= 10; deductions.push({ item: "H1 Heading", points: -10, message: "Missing H1 tag", }); } else if (data.headings.h1.count > 1) { score -= 7; deductions.push({ item: "H1 Heading", points: -7, message: "Multiple H1 tags", }); } if (data.headings.h2.count < 2) { score -= 3; deductions.push({ item: "H2 Headings", points: -3, message: "Not enough H2 tags", }); } // Images (10 points) if (data.images?.withoutAlt > 0) { const deduction = Math.min(10, data.images.withoutAlt * 2); score -= deduction; deductions.push({ item: "Image Alt Tags", points: -deduction, message: `${data.images.withoutAlt} images missing alt text`, }); } // Links (10 points) if (data.links?.internal < 5) { score -= 5; deductions.push({ item: "Internal Links", points: -5, message: "Few internal links", }); } if (data.links?.external === 0) { score -= 2; deductions.push({ item: "External Links", points: -2, message: "No external links", }); } // Mobile (10 points) if (!data.meta?.viewport?.mobileFriendly) { score -= 10; deductions.push({ item: "Mobile Friendly", points: -10, message: "No responsive viewport", }); } // Performance (10 points) if (data.pageLoadTime > 3) { const deduction = Math.min(10, Math.floor(data.pageLoadTime - 2) * 2); score -= deduction; deductions.push({ item: "Page Speed", points: -deduction, message: `Slow load time (${data.pageLoadTime}s)`, }); } // Technical SEO (20 points) if (!data.technical?.canonical?.exists) { score -= 5; deductions.push({ item: "Canonical URL", points: -5, message: "Missing canonical tag", }); } if (!data.security?.https) { score -= 5; deductions.push({ item: "HTTPS", points: -5, message: "Not using HTTPS", }); } if (data.technical?.schemaMarkup?.count === 0) { score -= 5; deductions.push({ item: "Schema Markup", points: -5, message: "Missing structured data", }); } if (!data.meta?.robots?.content) { score -= 5; deductions.push({ item: "Robots Meta", points: -5, message: "Missing robots meta tag", }); } // Calculate category scores const categoryScores = { onPage: calculateOnPageScore(data), technical: calculateTechnicalScore(data), content: calculateContentScore(data), mobile: calculateMobileScore(data), }; return { score: Math.max(0, Math.round(score)), deductions, categoryScores, }; }; const calculateOnPageScore = (data) => { let score = 30; if (!data.title.exists) score -= 15; if (!data.meta.description.exists) score -= 10; if (data.headings.h1.count !== 1) score -= 5; return Math.max(0, score); }; const calculateTechnicalScore = (data) => { let score = 30; if (!data.technical.canonical.exists) score -= 5; if (!data.security.https) score -= 5; if (data.technical.schemaMarkup.count === 0) score -= 5; if (!data.meta.robots.content) score -= 5; return Math.max(0, score); }; const calculateContentScore = (data) => { let score = 20; if (data.content.wordCount < 300) score -= 5; if (data.content.readability < 60) score -= 5; if (data.images.withoutAlt > 0) score -= 5; return Math.max(0, score); }; const calculateMobileScore = (data) => { let score = 20; if (!data.meta.viewport.mobileFriendly) score -= 10; if (data.pageLoadTime > 3) score -= 5; return Math.max(0, score); }; const renderReportSection = () => { if (!seoData) return null; const { score, deductions, categoryScores } = calculateScores(seoData); switch (activeTab) { case "summary": return (

Key Recommendations

); case "details": return (
= 2 ? "good" : "bad" } goodText={`1 H1 and ${seoData.headings.h2.count} H2 headings`} badText={ seoData.headings.h1.count === 0 ? "No H1 heading" : seoData.headings.h1.count > 1 ? "Multiple H1 headings" : "Not enough H2 headings" } recommendation="Use one H1 and multiple H2s for proper structure" />
0 ? "good" : "bad" } goodText={`${seoData.technical.schemaMarkup.count} schema items found`} badText="Missing" recommendation="Implement structured data for rich snippets" />
= 300 ? "good" : "bad"} goodText={`${seoData.content.wordCount} words (good length)`} badText={`${seoData.content.wordCount} words (too short)`} recommendation="Aim for at least 300 words of quality content" /> = 60 ? "good" : "bad"} goodText={`Score: ${seoData.content.readability}/100 (good)`} badText={`Score: ${seoData.content.readability}/100 (needs improvement)`} recommendation="Simplify complex sentences and reduce jargon" />
); case "full-report": return (

Comprehensive SEO Report

Generated for {seoData.url} on{" "} {new Date(seoData.analyzedAt).toLocaleString()}

Executive Summary

Overall SEO Score: {score}/100 -{" "} {score >= 80 ? "Excellent" : score >= 60 ? "Good" : score >= 40 ? "Needs Improvement" : "Poor"}

{score >= 80 ? "Your site has strong SEO fundamentals." : score >= 60 ? "Your site has decent SEO but could use some improvements." : score >= 40 ? "Your site needs significant SEO improvements." : "Your site requires urgent SEO attention."}

Detailed Findings

On-Page SEO

Technical SEO

Contents

); default: return null; } }; return (
setUrl(e.target.value)} placeholder="Enter URL (e.g., https://example.com)" className="flex-1 p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" onKeyDown={(e) => e.key === "Enter" && checkSEO()} />
{error && (

{error}

)}
{loading && (

Running comprehensive SEO analysis...

This may take 10-15 seconds

)} {seoData && ( <>
{renderReportSection()} )}
); }; // Helper Components const ScoreCard = ({ title, score, max, description }) => { const percentage = (score / max) * 100; const getColor = () => { if (percentage >= 80) return "bg-green-500"; if (percentage >= 50) return "bg-yellow-500"; return "bg-red-500"; }; return (

{title}

{score} / {max}

{description}

); }; const Section = ({ title, children }) => (

{title}

{children}
); const CheckItem = ({ label, status, goodText, badText, recommendation }) => (
{status === "good" ? ( ) : ( )}

{label}

{status === "good" ? goodText : badText}

{status !== "good" && recommendation && (

Recommendation: {recommendation}

)}
); export default SEOChecker;