// Mobile SEO Check Component const { useState } = React; const MobileSeoCheck = () => { const [url, setUrl] = useState(''); const [isAnalyzing, setIsAnalyzing] = useState(false); const [results, setResults] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [error, setError] = useState(null); // Function to complete and validate URL const validateAndCompleteURL = (inputURL) => { let processedURL = inputURL.trim(); // Add protocol if missing if (!processedURL.startsWith('http://') && !processedURL.startsWith('https://')) { processedURL = 'https://' + processedURL; } // Convert http to https if (processedURL.startsWith('http://')) { processedURL = processedURL.replace('http://', 'https://'); } try { new URL(processedURL); return { isValid: true, url: processedURL }; } catch (_) { return { isValid: false, url: processedURL }; } }; // Perform real SEO analysis using web scraping const analyzeSite = async () => { if (!url || isAnalyzing) return; // Validate and complete URL const { isValid, url: processedURL } = validateAndCompleteURL(url); if (!isValid) { setError('Please enter a valid URL (e.g. example.com)'); return; } // Update URL with processed version setUrl(processedURL); // Reset states setError(null); setIsAnalyzing(true); setResults(null); setCurrentStep(0); try { // Steps for analysis const steps = [ 'Checking page title...', 'Analyzing meta description...', 'Checking heading structure...', 'Analyzing page content...', 'Checking mobile friendliness...', 'Testing page speed...', 'Analyzing image optimization...', 'Checking link structure...', 'Finalizing results...' ]; // Variable to store desktop speed score from API for later use let desktopSpeedScoreRef = 0; // Function to update step counter with detailed status messages const updateStep = (index, detailedMessage = null) => { setCurrentStep(index + 1); // Log the current step for debugging console.log(`Mobile SEO Analysis Step ${index + 1}/9: ${detailedMessage || 'Processing...'}`); }; // Fetch the website content using the fetch API updateStep(0, "Fetching website content"); // First step // Fetch website content using direct API call let htmlContent; try { // Use the direct URL approach, which uses server-side curl console.log("Fetching website content via server..."); // Include a cache buster to prevent caching const cacheBuster = new Date().getTime(); const response = await fetch(`fetch.php?url=${encodeURIComponent(processedURL)}&_=${cacheBuster}`); if (!response.ok) { throw new Error(`Server fetch failed: ${response.status}`); } // Get the HTML content directly htmlContent = await response.text(); if (!htmlContent || htmlContent.trim() === '') { throw new Error('Empty response from server'); } console.log("Successfully fetched content via server"); } catch (fetchError) { console.error("Failed to fetch website content:", fetchError.message); throw new Error(`Could not access the website content. Please try a different URL or try again later: ${fetchError.message}`); } // Create a DOM parser to analyze the HTML const parser = new DOMParser(); const doc = parser.parseFromString(htmlContent, 'text/html'); // Initialize factors array to store analysis results const factors = []; // Function to score a factor from 0-100 const scoreFactor = (value, max, min = 0) => { return Math.max(0, Math.min(100, Math.round(((value - min) / (max - min)) * 100))); }; // 1. Analyze title updateStep(1, "Analyzing page title tag"); const titleElement = doc.querySelector('title'); const title = titleElement ? titleElement.textContent.trim() : ''; const titleScore = title ? (title.length > 10 && title.length < 70 ? scoreFactor(title.length, 60, 10) : scoreFactor(title.length > 70 ? 130 - title.length : title.length, 60, 10)) : 0; const titleStatus = titleScore >= 80 ? 'pass' : (titleScore >= 50 ? 'warning' : 'fail'); factors.push({ name: 'Title Tag', score: titleScore, status: titleStatus }); // 2. Analyze meta description updateStep(2, "Analyzing meta description"); const metaDesc = doc.querySelector('meta[name="description"]'); const metaDescContent = metaDesc ? metaDesc.getAttribute('content') : ''; const metaDescScore = metaDescContent ? (metaDescContent.length > 50 && metaDescContent.length < 160 ? scoreFactor(metaDescContent.length, 150, 50) : scoreFactor(metaDescContent.length > 160 ? 320 - metaDescContent.length : metaDescContent.length, 150, 50)) : 0; const metaDescStatus = metaDescScore >= 80 ? 'pass' : (metaDescScore >= 50 ? 'warning' : 'fail'); factors.push({ name: 'Meta Description', score: metaDescScore, status: metaDescStatus }); // 3. Analyze heading structure updateStep(3, "Analyzing heading structure"); const h1Elements = doc.querySelectorAll('h1'); const h2Elements = doc.querySelectorAll('h2'); const h3Elements = doc.querySelectorAll('h3'); const h1Count = h1Elements.length; const headingStructureScore = h1Count === 1 && h2Elements.length > 0 ? (h2Elements.length + h3Elements.length > 0 ? 100 : 80) : (h1Count === 0 ? 0 : (h1Count > 1 ? 40 : 60)); const headingStructureStatus = headingStructureScore >= 80 ? 'pass' : (headingStructureScore >= 50 ? 'warning' : 'fail'); factors.push({ name: 'Heading Structure', score: headingStructureScore, status: headingStructureStatus }); // 4. Analyze content length updateStep(4, "Analyzing content length"); const bodyText = doc.body.textContent.replace(/\s+/g, ' ').trim(); const wordCount = bodyText.split(/\s+/).length; const contentLengthScore = wordCount >= 300 ? (wordCount >= 1000 ? 100 : scoreFactor(wordCount, 1000, 300)) : scoreFactor(wordCount, 300, 0); const contentLengthStatus = contentLengthScore >= 80 ? 'pass' : (contentLengthScore >= 50 ? 'warning' : 'fail'); factors.push({ name: 'Content Length', score: contentLengthScore, status: contentLengthStatus }); // 5. Check for mobile-friendly viewport updateStep(5, "Testing mobile compatibility"); const hasViewport = doc.querySelector('meta[name="viewport"]') !== null; const viewportScore = hasViewport ? 100 : 0; const viewportStatus = hasViewport ? 'pass' : 'fail'; factors.push({ name: 'Mobile Friendly', score: viewportScore, status: viewportStatus }); // 6. Check page speed using Google PageSpeed Insights API updateStep(6, "Starting Google PageSpeed analysis"); try { // Fetch results from Google PageSpeed API using server-side PHP console.log("Fetching PageSpeed data for:", processedURL); // Try both endpoint locations with detailed logging const cacheBuster = new Date().getTime(); let pageSpeedResponse; let pageSpeedData; let endpointUsed; // First try the root directory endpoint updateStep(6, "Contacting Google PageSpeed API (this may take up to 60 seconds)"); try { console.log("Trying main directory endpoint: pagespeed.php"); // Set up a timeout indicator for better user feedback let apiTimeout = setTimeout(() => { updateStep(6, "PageSpeed API request in progress (still waiting for response...)"); }, 15000); // After 15 seconds, show a waiting message pageSpeedResponse = await fetch(`pagespeed.php?url=${encodeURIComponent(processedURL)}&_=${cacheBuster}`); // Clear the timeout indicator clearTimeout(apiTimeout); if (!pageSpeedResponse.ok) { console.warn(`Main directory PageSpeed API HTTP error: ${pageSpeedResponse.status}`); updateStep(6, "First PageSpeed API endpoint failed, trying backup endpoint"); throw new Error("Main endpoint failed"); } updateStep(6, "PageSpeed API responded, processing data"); pageSpeedData = await pageSpeedResponse.json(); endpointUsed = "Main directory"; console.log("Successfully fetched PageSpeed data from main directory endpoint"); } catch (mainEndpointError) { // If the main endpoint fails, try the /core/ directory endpoint console.log("Main endpoint failed, trying API directory endpoint: core/pagespeed.php"); updateStep(6, "Trying backup PageSpeed API endpoint (this may take up to 60 seconds)"); try { // Set up another timeout indicator for the second attempt let apiBackupTimeout = setTimeout(() => { updateStep(6, "Backup PageSpeed API request in progress (still waiting for response...)"); }, 15000); // After 15 seconds, show a waiting message pageSpeedResponse = await fetch(`core/pagespeed.php?url=${encodeURIComponent(processedURL)}&_=${cacheBuster}`); // Clear the timeout indicator clearTimeout(apiBackupTimeout); if (!pageSpeedResponse.ok) { console.error(`API directory PageSpeed HTTP error: ${pageSpeedResponse.status}`); updateStep(6, "PageSpeed API connection failed"); throw new Error(`PageSpeed API HTTP error: ${pageSpeedResponse.status}`); } updateStep(6, "Backup PageSpeed API responded, processing data"); pageSpeedData = await pageSpeedResponse.json(); endpointUsed = "API directory"; console.log("Successfully fetched PageSpeed data from API directory endpoint"); } catch (apiEndpointError) { updateStep(6, "PageSpeed API connection failed on both endpoints"); console.error("Both PageSpeed endpoints failed:", mainEndpointError.message, apiEndpointError.message ); throw new Error(`PageSpeed API HTTP error: Could not access either endpoint. Please check server logs.`); } } // Check if the data is valid and successful if (!pageSpeedData) { console.error("PageSpeed API returned no data"); throw new Error(`PageSpeed API error: No data returned`); } if (!pageSpeedData.success) { console.error("PageSpeed API returned error:", pageSpeedData.error || 'Unknown error'); throw new Error(`PageSpeed API error: ${pageSpeedData.error || 'Unknown error'}`); } console.log(`Successfully fetched PageSpeed data from ${endpointUsed}:`, pageSpeedData); // Validate mobile and desktop data if (!pageSpeedData.mobile || !pageSpeedData.desktop) { console.error("PageSpeed API missing mobile or desktop data:", pageSpeedData); throw new Error(`PageSpeed API error: Incomplete data returned`); } // Extract mobile score const pageSpeedScore = pageSpeedData.mobile.score || 0; console.log("Mobile speed score:", pageSpeedScore); // Determine status based on score const pageSpeedStatus = pageSpeedScore >= 80 ? 'pass' : (pageSpeedScore >= 60 ? 'warning' : 'fail'); factors.push({ name: 'Page Speed', score: pageSpeedScore, status: pageSpeedStatus }); // Store desktop score for later use desktopSpeedScoreRef = pageSpeedData.desktop.score || Math.min(100, pageSpeedScore + 10); console.log("Desktop speed score:", desktopSpeedScoreRef); } catch (speedError) { console.error("PageSpeed API error:", speedError); // Update step to show user there was an error with PageSpeed API updateStep(6, "PageSpeed API error - continuing with other SEO factors"); // Add a neutral page speed factor instead of failing the entire analysis factors.push({ name: 'Page Speed', score: 50, // Neutral score status: 'warning' }); // Set a default desktop score desktopSpeedScoreRef = 55; // Log the error but don't throw it - this allows the analysis to continue console.error(`Could not retrieve page speed data from Google PageSpeed API: ${speedError.message}`); } // 7. Analyze image optimization updateStep(7, "Analyzing image optimization"); const imgElements = doc.querySelectorAll('img'); const imgCount = imgElements.length; let imgWithAlt = 0; let imgWithSize = 0; imgElements.forEach(img => { if (img.hasAttribute('alt') && img.getAttribute('alt').trim() !== '') { imgWithAlt++; } if (img.hasAttribute('width') && img.hasAttribute('height')) { imgWithSize++; } }); const imgAltScore = imgCount === 0 ? 100 : Math.round((imgWithAlt / imgCount) * 100); const imgSizeScore = imgCount === 0 ? 100 : Math.round((imgWithSize / imgCount) * 100); const imgOptScore = imgCount === 0 ? 100 : Math.round((imgAltScore * 0.6) + (imgSizeScore * 0.4)); const imgOptStatus = imgOptScore >= 80 ? 'pass' : (imgOptScore >= 50 ? 'warning' : 'fail'); factors.push({ name: 'Image Optimization', score: imgOptScore, status: imgOptStatus }); // 8. Analyze link structure updateStep(8, "Analyzing link structure"); const links = doc.querySelectorAll('a'); const internalLinks = Array.from(links).filter(link => { try { const href = link.getAttribute('href'); if (!href) return false; // Check if it's an internal link if (href.startsWith('/') || href.startsWith('#')) return true; const linkUrl = new URL(href); const pageUrl = new URL(processedURL); return linkUrl.hostname === pageUrl.hostname; } catch (e) { return false; } }).length; const externalLinks = links.length - internalLinks; const hasLinks = links.length > 0; const linkScore = !hasLinks ? 50 : (internalLinks > 0 && externalLinks > 0 ? 100 : (internalLinks > 0 ? 80 : 60)); const linkStatus = linkScore >= 80 ? 'pass' : (linkScore >= 50 ? 'warning' : 'fail'); factors.push({ name: 'Link Structure', score: linkScore, status: linkStatus }); // Calculate overall score (average of all factors) updateStep(9, "Calculating final scores and preparing results"); const factorScores = factors.map(factor => factor.score); const overallScore = Math.round(factorScores.reduce((sum, score) => sum + score, 0) / factorScores.length); // Determine grade based on score let grade; if (overallScore >= 90) grade = 'A'; else if (overallScore >= 80) grade = 'B'; else if (overallScore >= 70) grade = 'C'; else if (overallScore >= 60) grade = 'D'; else grade = 'F'; // Get the mobile speed score from the page speed factor const speedFactor = factors.find(factor => factor.name.includes('Page Speed')); const mobileSpeedScore = speedFactor ? speedFactor.score : 70; const desktopSpeedScore = desktopSpeedScoreRef || Math.min(100, mobileSpeedScore + 10); // Set final results const finalResults = { score: overallScore, grade: grade, mobileScore: mobileSpeedScore, desktopScore: desktopSpeedScore, factors: factors }; setResults(finalResults); setIsAnalyzing(false); } catch (error) { console.error('Analysis error:', error); setError(`Analysis failed: ${error.message}`); setIsAnalyzing(false); } }; // Helper for factor status icon const getStatusIcon = (status) => { switch(status) { case 'pass': return ; case 'warning': return ; case 'fail': return ; default: return null; } }; // Helper for score color const getScoreColor = (score) => { if (score >= 90) return '#22c55e'; if (score >= 70) return '#84cc16'; if (score >= 50) return '#f59e0b'; return '#ef4444'; }; // Get grade color const getGradeColor = (grade) => { switch(grade) { case 'A': return '#22c55e'; case 'B': return '#84cc16'; case 'C': return '#f59e0b'; default: return '#ef4444'; } }; return (
{/* URL Input */}

SEO Analysis Tool

{ setUrl(e.target.value); // Clear error when user types if (error) setError(null); }} onBlur={(e) => { // Auto-complete URL when input loses focus if (e.target.value.trim()) { const { url: processedURL } = validateAndCompleteURL(e.target.value); setUrl(processedURL); } }} placeholder="Enter URL (e.g. example.com)" style={{ flex: 1, padding: '12px', border: '1px solid #d1d5db', borderRight: 'none', borderRadius: '8px 0 0 8px', fontSize: '14px', }} />
{error && (
{error}
)}
{/* Analysis Progress */} {isAnalyzing && (

Analyzing Website...

{currentStep === 1 ? 'Fetching website content' : currentStep === 2 ? 'Analyzing page title tag' : currentStep === 3 ? 'Analyzing meta description' : currentStep === 4 ? 'Analyzing heading structure' : currentStep === 5 ? 'Checking mobile friendliness' : currentStep === 6 ? 'Running Google PageSpeed analysis' : currentStep === 7 ? 'Analyzing image optimization' : currentStep === 8 ? 'Checking link structure' : currentStep === 9 ? 'Finalizing results' : 'Initializing...'}

)} {/* Empty state */} {!isAnalyzing && !results && !url && (

Enter a URL to analyze

Get a detailed SEO analysis of any website in seconds

)} {/* Results */} {results && ( <> {/* Overall Grade */}
Overall Grade
{results.score}
{results.grade}
{/* Performance Categories */}
{/* Performance Score */}
{results.mobileScore}
Performance
Mobile
{/* Desktop Performance */}
{results.desktopScore}
Performance
Desktop
{/* Factors list */}

SEO Factors

{results.factors.map((factor, index) => (
{getStatusIcon(factor.status)} {factor.name}
{factor.score}
))}
)}
); }; // Make it globally available window.MobileSeoCheck = MobileSeoCheck;