Building SEO tools often sounds straightforward until you hit the two walls of modern web scraping: Cross-Origin Resource Sharing (CORS) and the messiness of parsing arbitrary HTML.
Recently, I built a Meta Tag Analyzer to help developers debug their Open Graph and Twitter Card tags. The goal was to take a URL, fetch the source code, and visualize exactly how social platforms see the page.
Here is the technical breakdown of how I handled the data fetching architecture and, more importantly, how to parse HTML safely in the browser without using heavy libraries like Cheerio or JSDOM.
The Problem: CORS and The "Regex for HTML" Trap
There are two main hurdles when building a client-side SEO analyzer:
The CORS Block: You cannot simply make a fetch('https://example.com') request from your browser. The browser’s security policy will block the request because the target domain does not send the Access-Control-Allow-Origin header for your site.
Parsing Strategy: Once you get the HTML (usually via a proxy), you have a massive string of text. Beginners often try to use Regex to extract tags. As the famous StackOverflow post suggests, parsing HTML with Regex is a bad idea. It breaks easily on unclosed tags, comments, or unexpected line breaks.
The Solution: A Proxy + DOMParser Architecture
To solve this, I used a two-step architecture:
Serverless Proxy: A lightweight serverless function acts as a tunnel. It accepts a target URL, fetches the content server-side (where CORS doesn't exist), and returns the raw HTML string to my frontend.
Native DOMParser: On the client side, rather than importing a heavy parsing library, I utilized the browser's native DOMParser API. This allows us to convert a string of HTML into a manipulatable DOM document without executing scripts or loading external resources (like images).
The Code: Parsing HTML Strings safely
Here is the core logic used in the frontend. This function takes the raw HTML string returned from the proxy and extracts the standard SEO tags, Open Graph (OG) tags, and Twitter Cards.
We use parser.parseFromString(html, "text/html") to create a virtual document.
/**
* Extracts meta tags from a raw HTML string using the DOMParser API.
* * @param {string} rawHtml - The HTML string fetched from the proxy.
* @returns {object} - An object containing standard, OG, and Twitter metadata.
*/
const extractMetaData = (rawHtml) => {
// 1. Initialize the DOMParser
const parser = new DOMParser();
// 2. Parse the string into a Document.
// 'text/html' ensures it parses as HTML, forgiving syntax errors.
const doc = parser.parseFromString(rawHtml, "text/html");
// Helper to safely get content from a selector
const getMeta = (selector, attribute = "content") => {
const element = doc.querySelector(selector);
return element ? element.getAttribute(attribute) : null;
};
// 3. Extract Data
// Note: We use querySelector to handle fallback logic efficiently
const data = {
title: doc.title || getMeta('meta[property="og:title"]'),
description:
getMeta('meta[name="description"]') ||
getMeta('meta[property="og:description"]'),
// Open Graph Specifics
og: {
image: getMeta('meta[property="og:image"]'),
url: getMeta('meta[property="og:url"]'),
type: getMeta('meta[property="og:type"]'),
},
// Twitter Card Specifics
twitter: {
card: getMeta('meta[name="twitter:card"]'),
creator: getMeta('meta[name="twitter:creator"]'),
},
// Technical SEO
robots: getMeta('meta[name="robots"]'),
viewport: getMeta('meta[name="viewport"]'),
canonical: getMeta('link[rel="canonical"]', "href")
};
return data;
};
Why this approach works well:
Security: DOMParser creates a document context that is inert. Scripts found inside rawHtml are marked as non-executable by the parser, preventing XSS attacks during the analysis phase.
Performance: It parses only what is needed. Because we aren't rendering the page (just parsing the text), we avoid network requests for images, CSS, or fonts referenced in the target URL.
Resilience: Browsers are excellent at parsing "bad" HTML. If the target site has missing closing tags, the DOMParser will handle it just like a browser would, ensuring our scraper doesn't crash on malformed web pages.
Live Demo
You can see this parser in action, along with the visualization logic that previews how the link looks on social media, at the link below.
Live Tool: NasajTools Meta Tag Analyzer
Enter any URL (e.g., github.com) to see the DOMParser extraction in real-time.
Performance Considerations
When building this, I encountered an issue with massive HTML pages (some legacy sites have 2MB+ HTML files).
To optimize the "Time to Interactive" for the user:
Request Abort: On the proxy side, I set a strict timeout. If the HTML takes longer than 3 seconds to generate, we abort. SEO bots rarely wait longer than that, so it's a realistic metric.
Content-Length Check: I limit the string length processed by the DOMParser. Meta tags are almost always in the
. If the HTML string is huge, I slice the string to the first 100kb before parsing. This ensures the main thread doesn't lock up while parsing a massive that we don't even need.// Optimization: Only parse the head if the file is massive
const MAX_SIZE = 100000; // 100kb
if (rawHtml.length > MAX_SIZE) {
// Try to cut off after the closing head tag to keep it valid
const headEnd = rawHtml.indexOf('</head>');
if (headEnd !== -1) {
rawHtml = rawHtml.substring(0, headEnd + 7);
}
}
This simple truncation strategy reduced the processing time on low-end mobile devices significantly during my testing
Hopefully, this helps you if you are looking to build client-side scrapers or analyzers!
https://nasajtools.com/tools/seo/meta-tag-analyzer.html
Top comments (0)