Code for single url on page seo checker in NODE JS output in HTML file
Wednesday, 9 August 2023 -'use strict';
const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const puppeteer = require('puppeteer');
const { parse } = require('schema-inspector'); // Add schema-inspector package
async function checkMobileFriendly(url) {
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.goto(url);
const viewportMeta = await page.evaluate(() =>
Array.from(document.querySelectorAll('meta[name="viewport"]'), element => element.content)
);
// More detailed mobile usability checks
const mobileIssues = await page.evaluate(() => {
const issues = [];
// Check for small font size
const smallFontElements = Array.from(document.querySelectorAll('body *')).filter(element => {
const style = window.getComputedStyle(element);
const fontSize = parseFloat(style.fontSize);
return fontSize < 12;
});
if (smallFontElements.length > 0) {
issues.push(`Found ${smallFontElements.length} elements with small font size.`);
}
// Check for clickable elements too close together
const clickableElements = Array.from(document.querySelectorAll('a, button, [role="button"], input[type="button"], input[type="submit"], [tabindex]'));
const closeElements = clickableElements.filter((element, index) => {
const rect1 = element.getBoundingClientRect();
return clickableElements.some((otherElement, otherIndex) => {
if (index === otherIndex) return false;
const rect2 = otherElement.getBoundingClientRect();
return Math.abs(rect1.left - rect2.left) < 48 && Math.abs(rect1.top - rect2.top) < 48;
});
});
if (closeElements.length > 0) {
issues.push(`Found ${closeElements.length} sets of clickable elements too close to each other.`);
}
// Check for elements not fitting within the viewport
const outsideViewportElements = Array.from(document.querySelectorAll('body *')).filter(element => {
const rect = element.getBoundingClientRect();
return rect.right > window.innerWidth || rect.bottom > window.innerHeight;
});
if (outsideViewportElements.length > 0) {
issues.push(`Found ${outsideViewportElements.length} elements that don't fit within the viewport.`);
}
return issues;
});
await browser.close();
return {
isMobileFriendly: viewportMeta.includes('width=device-width, initial-scale=1') && mobileIssues.length === 0,
issues: mobileIssues,
};
}
// URL Structure Optimization
function optimizeURLStructure(url) {
const urlObject = new URL(url);
let optimizedURL = urlObject.origin + urlObject.pathname;
// Replace underscores (_) with hyphens (-) in the URL
optimizedURL = optimizedURL.replace(/_/g, '-');
return optimizedURL;
}
async function analyzePage(url, page) {
// Fetch the page
const response = await axios.get(url);
// Load the page into cheerio
const $ = cheerio.load(response.data);
// Check for meta tags
const title = $('head > title').text() || '';
const metaDescription = $('head > meta[name=description]').attr('content') || '';
const metaKeywords = $('head > meta[name=keywords]').attr('content') || '';
// Colorize the meta title and description based on their length
const titleColor = title.length > 60 ? 'red' : 'green';
const descriptionColor = metaDescription.length > 160 ? 'red' : 'green';
// Check for correct HTML tags usage
let headersData = {};
for (let i = 1; i <= 6; i++) {
let headers = $(`h${i}`).map((_, el) => $(el).text()).get();
let headerColor = i === 1 && headers.length > 1 ? 'red' : 'green';
headersData[`h${i}`] = {
count: headers.length,
color: headerColor,
headers: headers
};
}
let headersContent;
if (headersData['h1'].count === 1) {
headersContent = '<span style="color: green">Good</span>';
} else {
headersContent = Object.keys(headersData).map(header => `<span style="color: ${headersData[header].color}">${header}: ${headersData[header].count}, ${headersData[header].headers.join(', ')}</span>`).join('<br/>');
}
// Server Response Code
const serverResponseCode = response.status;
const serverResponseText = response.statusText;
let serverResponseColor;
if (serverResponseCode >= 200 && serverResponseCode < 300) {
serverResponseColor = 'green';
} else if (serverResponseCode >= 300 && serverResponseCode < 400) {
serverResponseColor = 'yellow';
} else {
serverResponseColor = 'red';
}
// Check for alt text on images
const imagesWithoutAlt = $('img:not([alt])');
const imagesWithoutAltCount = imagesWithoutAlt.length;
const imagesWithoutAltColor = imagesWithoutAltCount > 0 ? 'red' : 'green';
let imagesWithoutAltSrcList = '';
imagesWithoutAlt.each((i, img) => {
imagesWithoutAltSrcList += `<li>${$(img).attr('src')}</li>`;
});
// Check for canonical URL
const canonicalUrls = $('head > link[rel=canonical]').map((i, link) => $(link).attr('href')).get();
const uniqueCanonicalUrls = [...new Set(canonicalUrls)];
const canonicalUrlCount = uniqueCanonicalUrls.length;
const duplicateCanonicalUrls = canonicalUrls.length > canonicalUrlCount;
const canonicalColor = duplicateCanonicalUrls ? 'red' : 'green';
let canonicalUrlsList = uniqueCanonicalUrls.map(url => `<li><span style="color: ${canonicalColor}">${url}</span></li>`).join('');
// Calculate Text to HTML ratio
const textContent = $('body').text();
const htmlContentLength = response.data.length;
const textContentLength = textContent.length;
const textToHtmlRatio = ((textContentLength / htmlContentLength) * 100).toFixed(2);
// Check SEO-friendly URL
function checkURL(url) {
const urlObject = new URL(url);
const urlPath = urlObject.pathname;
if (urlPath.length > 100) return { friendly: false, reason: 'URL is too long (more than 100 characters)' };
if (urlObject.search) return { friendly: false, reason: 'URL contains parameters' };
if (urlPath !== urlPath.toLowerCase()) return { friendly: false, reason: 'URL is not lowercase' };
if (!/^[\w\-/]+$/.test(urlPath)) return { friendly: false, reason: 'URL contains special characters or spaces' };
if (/\.[0-9a-z]+$/i.test(urlPath)) return { friendly: false, reason: 'URL ends with a file extension' };
if (/_/.test(urlPath)) return { friendly: false, reason: 'URL uses underscores instead of hyphens' };
if (urlPath.split('/').length > 3) return { friendly: false, reason: 'URL contains more than 2 subdirectories' };
return {
friendly: true,
reason: 'URL is short, contains no parameters or special characters, is lowercase, does not end with a file extension, uses hyphens as word separators, and contains 2 or fewer subdirectories'
};
}
// Check Social Media Meta Tags
function checkSocialMediaMetaTags($) {
const ogTags = $('head > meta[property^="og:"]');
const twitterTags = $('head > meta[name^="twitter:"]');
return ogTags.length > 0 || twitterTags.length > 0;
}
// Check Schema Markup
function checkSchemaMarkup($) {
const schemaTags = $('script[type="application/ld+json"]');
const schemaCount = schemaTags.length;
const hasSchemaMarkup = schemaCount > 0;
const schemaColor = hasSchemaMarkup ? 'green' : 'red';
const schemaMarkupText = hasSchemaMarkup ? 'Present' : 'Not Present';
let schemaMarkupContent = '';
if (hasSchemaMarkup) {
schemaTags.each((i, tag) => {
schemaMarkupContent += `<pre>${$(tag).html()}</pre>`;
});
}
return {
count: schemaCount,
color: schemaColor,
markupText: schemaMarkupText,
markupContent: schemaMarkupContent
};
}
// Inside analyzePage function:
const seoFriendlyURLCheck = checkURL(url);
const seoFriendlyURLColor = seoFriendlyURLCheck.friendly ? 'green' : 'red';
const seoFriendlyURL = `<span style="color: ${seoFriendlyURLColor}">${seoFriendlyURLCheck.reason}</span>`;
// Mobile Friendly Check
const isMobileFriendly = await checkMobileFriendly(url);
const mobileFriendlyColor = isMobileFriendly ? 'green' : 'red';
// Check Social Media Meta Tags
const hasSocialMediaMetaTags = checkSocialMediaMetaTags($);
const socialMediaTagsColor = hasSocialMediaMetaTags ? 'green' : 'red';
const socialMediaTagsText = hasSocialMediaMetaTags ? 'Used' : 'Not Used';
// Check Schema Markup
const schemaMarkup = checkSchemaMarkup($);
const schemaMarkupColor = schemaMarkup.color;
const schemaMarkupText = schemaMarkup.markupText;
const schemaMarkupContent = schemaMarkup.markupContent;
// Create HTML content
let htmlContent = `
<h1>Analysis of ${url}</h1>
<p>Title: <span style="color: ${titleColor}">${title} (${title.length} characters)</span></p>
<p>Meta description: <span style="color: ${descriptionColor}">${metaDescription} (${metaDescription.length} characters)</span></p>
<p>Headers: ${headersContent}</p>
<p>Images without alt text: <span style="color: ${imagesWithoutAltColor}">${imagesWithoutAltCount}</span></p>
<ul>${imagesWithoutAltSrcList}</ul>
<p>Canonical URL(s): <ul>${canonicalUrlsList}</ul></p>
<p>Text to HTML Ratio: ${textToHtmlRatio}%</p>
<p>SEO-friendly URL: ${seoFriendlyURL}</p>
<p>Mobile-Friendly: <span style="color: ${mobileFriendlyColor}">${isMobileFriendly ? 'Yes' : 'No'}</span></p>
<p>Server Response: <span style="color: ${serverResponseColor}">${serverResponseCode} ${serverResponseText}</span></p>
<p>Social Media Meta Tags: <span style="color: ${socialMediaTagsColor}">${socialMediaTagsText}</span></p>
<p>Schema Markup: <span style="color: ${schemaMarkupColor}">${schemaMarkupText}</span></p>
${schemaMarkupContent}
`;
// Security Analysis
const secureContent = response.data.includes('https:');
const insecureElements = [];
$('script[src], img[src], link[href]').each((i, element) => {
const src = $(element).attr('src');
const href = $(element).attr('href');
if (src && !src.startsWith('https:')) {
insecureElements.push(src);
}
if (href && !href.startsWith('https:')) {
insecureElements.push(href);
}
});
let insecureElementsContent;
if (insecureElements.length > 0) {
const insecureElementsList = insecureElements.map(element => `<li>${element}</li>`).join('');
insecureElementsContent = `<p style="color: red">Insecure Elements:</p><ul>${insecureElementsList}</ul>`;
} else {
insecureElementsContent = '<p style="color: green">No insecure elements found.</p>';
}
htmlContent += insecureElementsContent;
return htmlContent;
}
// Function to analyze responsiveness across different devices and screen sizes
async function analyzeResponsiveness(url) {
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
// Define the devices and screen sizes you want to test
const devices = [
{
name: 'Desktop',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
viewport: {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: true,
}
},
{
name: 'Tablet',
userAgent: 'Mozilla/5.0 (iPad; CPU OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/90.0.4430.212 Mobile/15E148 Safari/604.1',
viewport: {
width: 768,
height: 1024,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
isLandscape: false,
}
},
{
name: 'Mobile',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1',
viewport: {
width: 375,
height: 812,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false,
}
}
];
const results = {};
for (const device of devices) {
await page.setUserAgent(device.userAgent);
await page.setViewport(device.viewport);
await page.goto(url);
// Wait for images to be fully loaded
await page.waitForFunction(() => {
const images = Array.from(document.images);
return images.every(img => img.complete && img.naturalHeight !== 0);
});
// Perform responsiveness analysis for the device
const deviceName = device.name;
const deviceScreenshotPath = `responsiveness_${deviceName.toLowerCase()}.png`;
await page.screenshot({ path: deviceScreenshotPath });
results[deviceName] = deviceScreenshotPath;
}
await browser.close();
return results;
}
// Wrap code in IIFE to handle async
(async () => {
try {
// Replace with the URL you want to analyze
const url = 'https://www.clicky.pk/moonwalk-men-black-best-synthetic-rubber-sports-shoes-for-athletic-performance-black?id=6492d9459a567e108a95dd83';
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
const htmlContent = await analyzePage(url, page);
// Add responsiveness analysis to HTML content
const responsivenessResults = await analyzeResponsiveness(url);
const responsivenessContent = Object.keys(responsivenessResults)
.map(deviceName => `<h2>${deviceName}:</h2><img src="${responsivenessResults[deviceName]}" alt="${deviceName}" />`)
.join('');
// Combine all the analysis results
const combinedContent = htmlContent + responsivenessContent;
// Write to HTML file
fs.writeFile('output.html', combinedContent, (error) => {
if (error) {
console.error('Error writing to file:', error);
} else {
console.log('Analysis results written to output.html');
}
});
await browser.close();
} catch (error) {
console.error('Error analyzing page:', error.message);
}
})();