DEV Community

Tech Believers
Tech Believers

Posted on

Building a Meta Tags Analyzer: Technical Deep-Dive

Introduction

Meta tags are the unsung heroes of SEO. They're invisible to users but critical for search engines and social platforms. In this technical guide, we'll explore how meta tags work, why they matter, and how to build your own Meta Tags Analyzer tool.

What Are Meta Tags? (The Technical Perspective)

Meta tags are HTML elements placed in the <head> section of a document. They provide metadata about the webpage—information that describes the content but isn't displayed on the page itself.

Basic Structure

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page Title Here</title>
    <meta name="description" content="Page description here">
    <meta name="keywords" content="keyword1, keyword2, keyword3">

    <!-- Open Graph -->
    <meta property="og:title" content="OG Title">
    <meta property="og:description" content="OG Description">
    <meta property="og:image" content="https://example.com/image.jpg">
    <meta property="og:url" content="https://example.com/page">

    <!-- Twitter Card -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="Twitter Title">
    <meta name="twitter:description" content="Twitter Description">

    <!-- Technical -->
    <link rel="canonical" href="https://example.com/page">
    <meta name="robots" content="index, follow">
</head>
<body>
    <!-- Page content -->
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

How Search Engines Process Meta Tags

When a search engine crawler visits your page, here's what happens:

1. HTTP Request
   - Crawler sends GET request to your URL
   - Server responds with HTML document

2. HTML Parsing
   - Crawler parses the HTML DOM
   - Extracts <head> section
   - Identifies all meta tags

3. Tag Extraction
   - Reads <title> tag
   - Reads meta name="description"
   - Reads meta property="og:*"
   - Reads link rel="canonical"
   - Reads meta name="robots"

4. Data Storage
   - Stores metadata in search index
   - Associates with page URL
   - Uses for ranking and display decisions

5. Rendering Decision
   - Determines how to display in SERPs
   - Generates search result snippet
   - Applies any robots directives
Enter fullscreen mode Exit fullscreen mode

Building a Meta Tags Analyzer: The Logic

Let's build a tool that fetches a URL, extracts meta tags, and analyzes them.

Step 1: Fetch the HTML

async function fetchHTML(url) {
    try {
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'User-Agent': 'Mozilla/5.0 (compatible; MetaTagsAnalyzer/1.0)'
            }
        });

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const html = await response.text();
        return html;
    } catch (error) {
        console.error('Error fetching URL:', error);
        throw error;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Parse the HTML

function parseHTML(html) {
    // Create a DOM parser
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');

    return doc;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Extract Meta Tags

function extractMetaTags(doc) {
    const metaTags = {
        basic: {},
        openGraph: {},
        twitter: {},
        technical: {}
    };

    // Extract title
    const titleTag = doc.querySelector('title');
    metaTags.basic.title = titleTag ? titleTag.textContent : null;
    metaTags.basic.titleLength = metaTags.basic.title ? metaTags.basic.title.length : 0;

    // Extract meta description
    const descTag = doc.querySelector('meta[name="description"]');
    metaTags.basic.description = descTag ? descTag.getAttribute('content') : null;
    metaTags.basic.descriptionLength = metaTags.basic.description ? metaTags.basic.description.length : 0;

    // Extract meta keywords (deprecated but still checked)
    const keywordsTag = doc.querySelector('meta[name="keywords"]');
    metaTags.basic.keywords = keywordsTag ? keywordsTag.getAttribute('content') : null;

    // Extract viewport
    const viewportTag = doc.querySelector('meta[name="viewport"]');
    metaTags.basic.viewport = viewportTag ? viewportTag.getAttribute('content') : null;

    // Extract Open Graph tags
    const ogTags = doc.querySelectorAll('meta[property^="og:"]');
    ogTags.forEach(tag => {
        const property = tag.getAttribute('property').replace('og:', '');
        metaTags.openGraph[property] = tag.getAttribute('content');
    });

    // Extract Twitter Card tags
    const twitterTags = doc.querySelectorAll('meta[name^="twitter:"]');
    twitterTags.forEach(tag => {
        const name = tag.getAttribute('name').replace('twitter:', '');
        metaTags.twitter[name] = tag.getAttribute('content');
    });

    // Extract canonical URL
    const canonicalTag = doc.querySelector('link[rel="canonical"]');
    metaTags.technical.canonical = canonicalTag ? canonicalTag.getAttribute('href') : null;

    // Extract robots directives
    const robotsTag = doc.querySelector('meta[name="robots"]');
    metaTags.technical.robots = robotsTag ? robotsTag.getAttribute('content') : null;

    // Extract language
    const htmlTag = doc.querySelector('html');
    metaTags.technical.lang = htmlTag ? htmlTag.getAttribute('lang') : null;

    // Extract structured data (JSON-LD)
    const structuredData = [];
    const jsonLdScripts = doc.querySelectorAll('script[type="application/ld+json"]');
    jsonLdScripts.forEach(script => {
        try {
            const data = JSON.parse(script.textContent);
            structuredData.push(data);
        } catch (e) {
            console.error('Error parsing JSON-LD:', e);
        }
    });
    metaTags.technical.structuredData = structuredData;

    return metaTags;
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Validate Meta Tags

function validateMetaTags(metaTags) {
    const issues = [];
    const warnings = [];
    const recommendations = [];

    // Validate title tag
    if (!metaTags.basic.title) {
        issues.push('Missing title tag');
    } else {
        if (metaTags.basic.titleLength < 30) {
            warnings.push('Title tag is too short (< 30 characters)');
        }
        if (metaTags.basic.titleLength > 60) {
            warnings.push('Title tag is too long (> 60 characters, will be truncated in search results)');
        }
    }

    // Validate meta description
    if (!metaTags.basic.description) {
        issues.push('Missing meta description');
    } else {
        if (metaTags.basic.descriptionLength < 70) {
            warnings.push('Meta description is too short (< 70 characters)');
        }
        if (metaTags.basic.descriptionLength > 160) {
            warnings.push('Meta description is too long (> 160 characters, will be truncated)');
        }
    }

    // Validate Open Graph
    if (!metaTags.openGraph.title) {
        warnings.push('Missing og:title (recommended for social sharing)');
    }
    if (!metaTags.openGraph.description) {
        warnings.push('Missing og:description (recommended for social sharing)');
    }
    if (!metaTags.openGraph.image) {
        warnings.push('Missing og:image (social shares will look broken)');
    }
    if (!metaTags.openGraph.url) {
        recommendations.push('Consider adding og:url for canonical social sharing');
    }

    // Validate Twitter Card
    if (!metaTags.twitter.card) {
        recommendations.push('Consider adding Twitter Card tags for better Twitter sharing');
    }

    // Validate canonical
    if (!metaTags.technical.canonical) {
        recommendations.push('Consider adding a canonical tag to avoid duplicate content issues');
    }

    // Validate viewport (mobile optimization)
    if (!metaTags.basic.viewport) {
        issues.push('Missing viewport meta tag (required for mobile optimization)');
    }

    // Validate structured data
    if (metaTags.technical.structuredData.length === 0) {
        recommendations.push('Consider adding structured data (JSON-LD) for rich snippets');
    }

    return { issues, warnings, recommendations };
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Generate SEO Score

function calculateSEOScore(metaTags, validation) {
    let score = 100;

    // Deduct points for issues
    score -= validation.issues.length * 15;

    // Deduct points for warnings
    score -= validation.warnings.length * 10;

    // Deduct points for missing recommendations
    score -= validation.recommendations.length * 5;

    // Bonus points for structured data
    if (metaTags.technical.structuredData.length > 0) {
        score += 10;
    }

    // Ensure score is between 0 and 100
    score = Math.max(0, Math.min(100, score));

    return score;
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Put It All Together

async function analyzeMetaTags(url) {
    try {
        // Fetch HTML
        console.log('Fetching URL...');
        const html = await fetchHTML(url);

        // Parse HTML
        console.log('Parsing HTML...');
        const doc = parseHTML(html);

        // Extract meta tags
        console.log('Extracting meta tags...');
        const metaTags = extractMetaTags(doc);

        // Validate
        console.log('Validating...');
        const validation = validateMetaTags(metaTags);

        // Calculate score
        const score = calculateSEOScore(metaTags, validation);

        // Return results
        return {
            url,
            metaTags,
            validation,
            score,
            timestamp: new Date().toISOString()
        };
    } catch (error) {
        console.error('Analysis failed:', error);
        throw error;
    }
}

// Usage
analyzeMetaTags('https://example.com')
    .then(results => {
        console.log('Meta Tags Analysis Results:');
        console.log('SEO Score:', results.score);
        console.log('Issues:', results.validation.issues);
        console.log('Warnings:', results.validation.warnings);
        console.log('Recommendations:', results.validation.recommendations);
    })
    .catch(error => {
        console.error('Error:', error);
    });
Enter fullscreen mode Exit fullscreen mode

Advanced Features

Image Validation

For Open Graph images, validate dimensions and format:

async function validateOGImage(imageUrl) {
    try {
        const img = new Image();
        img.src = imageUrl;

        await new Promise((resolve, reject) => {
            img.onload = resolve;
            img.onerror = reject;
        });

        const validation = {
            exists: true,
            width: img.naturalWidth,
            height: img.naturalHeight,
            aspectRatio: (img.naturalWidth / img.naturalHeight).toFixed(2)
        };

        // Validate dimensions
        if (img.naturalWidth < 1200 || img.naturalHeight < 630) {
            validation.warning = 'Image is smaller than recommended (1200x630px)';
        }

        // Validate aspect ratio (should be ~1.91:1 for og:image)
        const idealRatio = 1.91;
        const actualRatio = img.naturalWidth / img.naturalHeight;
        if (Math.abs(actualRatio - idealRatio) > 0.1) {
            validation.warning = 'Image aspect ratio is not optimal for social sharing';
        }

        return validation;
    } catch (error) {
        return {
            exists: false,
            error: 'Image could not be loaded'
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Structured Data Validation

Validate JSON-LD structured data:

function validateStructuredData(structuredData) {
    const results = [];

    structuredData.forEach((data, index) => {
        const validation = {
            index,
            type: data['@type'],
            valid: true,
            errors: []
        };

        // Check required @context
        if (!data['@context']) {
            validation.valid = false;
            validation.errors.push('Missing @context');
        }

        // Check required @type
        if (!data['@type']) {
            validation.valid = false;
            validation.errors.push('Missing @type');
        }

        // Type-specific validation
        if (data['@type'] === 'Article') {
            if (!data.headline) validation.errors.push('Missing headline');
            if (!data.author) validation.errors.push('Missing author');
            if (!data.datePublished) validation.errors.push('Missing datePublished');
        } else if (data['@type'] === 'Organization') {
            if (!data.name) validation.errors.push('Missing name');
            if (!data.url) validation.errors.push('Missing url');
        } else if (data['@type'] === 'Product') {
            if (!data.name) validation.errors.push('Missing name');
            if (!data.offers) validation.errors.push('Missing offers');
        }

        if (validation.errors.length > 0) {
            validation.valid = false;
        }

        results.push(validation);
    });

    return results;
}
Enter fullscreen mode Exit fullscreen mode

Server-Side Implementation (Node.js)

For server-side analysis, use libraries like axios and cheerio:

const axios = require('axios');
const cheerio = require('cheerio');

async function analyzeMetaTagsServer(url) {
    try {
        // Fetch HTML
        const response = await axios.get(url, {
            headers: {
                'User-Agent': 'Mozilla/5.0 (compatible; MetaTagsAnalyzer/1.0)'
            },
            timeout: 10000
        });

        // Parse with Cheerio
        const $ = cheerio.load(response.data);

        // Extract meta tags
        const metaTags = {
            basic: {
                title: $('title').text(),
                description: $('meta[name="description"]').attr('content'),
                keywords: $('meta[name="keywords"]').attr('content'),
                viewport: $('meta[name="viewport"]').attr('content')
            },
            openGraph: {},
            twitter: {},
            technical: {
                canonical: $('link[rel="canonical"]').attr('href'),
                robots: $('meta[name="robots"]').attr('content'),
                lang: $('html').attr('lang')
            }
        };

        // Extract Open Graph
        $('meta[property^="og:"]').each((i, elem) => {
            const property = $(elem).attr('property').replace('og:', '');
            metaTags.openGraph[property] = $(elem).attr('content');
        });

        // Extract Twitter
        $('meta[name^="twitter:"]').each((i, elem) => {
            const name = $(elem).attr('name').replace('twitter:', '');
            metaTags.twitter[name] = $(elem).attr('content');
        });

        // Extract structured data
        const structuredData = [];
        $('script[type="application/ld+json"]').each((i, elem) => {
            try {
                const data = JSON.parse($(elem).html());
                structuredData.push(data);
            } catch (e) {
                console.error('Error parsing JSON-LD:', e);
            }
        });
        metaTags.technical.structuredData = structuredData;

        return metaTags;
    } catch (error) {
        console.error('Error analyzing meta tags:', error);
        throw error;
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices for Meta Tags

Title Tag

<!-- Good -->
<title>Project Management Software for Remote Teams | TechBelievers</title>

<!-- Bad -->
<title>Home</title>
<title>Welcome to Our Website | The Best Place for All Your Needs | Buy Now | Free Shipping</title>
Enter fullscreen mode Exit fullscreen mode

Rules:

  • 50-60 characters optimal
  • Include primary keyword naturally
  • Front-load important words
  • Include brand name (at the end)
  • Unique for every page

Meta Description

<!-- Good -->
<meta name="description" content="Manage remote teams effectively with our project management software. Features include task tracking, team collaboration, and real-time reporting. Try free for 14 days.">

<!-- Bad -->
<meta name="description" content="Welcome to our website.">
<meta name="description" content="project management software project management tools project management platform best project management software for remote teams">
Enter fullscreen mode Exit fullscreen mode

Rules:

  • 155-160 characters optimal
  • Include primary keyword naturally
  • Compelling value proposition
  • Call-to-action when appropriate
  • Unique for every page

Open Graph Tags

<!-- Complete Open Graph implementation -->
<meta property="og:title" content="Project Management for Remote Teams">
<meta property="og:description" content="Manage distributed teams with ease. Task tracking, collaboration, and reporting in one platform.">
<meta property="og:image" content="https://example.com/images/og-image.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:url" content="https://example.com/product">
<meta property="og:type" content="website">
<meta property="og:site_name" content="TechBelievers">
Enter fullscreen mode Exit fullscreen mode

Rules:

  • Always include og:title, og:description, og:image, og:url
  • Image should be 1200x630px minimum
  • Use high-quality, relevant images
  • og:title can differ from page title (optimize for social)

Conclusion

Building a Meta Tags Analyzer requires understanding HTML parsing, validation logic, and SEO best practices. The tool we've built can:

  • Fetch and parse any URL
  • Extract all meta tags
  • Validate against best practices
  • Calculate an SEO score
  • Provide actionable recommendations

Meta tags are the foundation of technical SEO. By analyzing and optimizing them, you can significantly improve search visibility and social sharing performance.

Try it yourself: Build your own analyzer or use our free tool at TechBelievers.com/tools/meta-tags-analyzer

Top comments (0)