Hi, I’m Luke Duff, a web designer and IT consultant from Sunderland. I build proper websites for small businesses and sort out the tech that most people don’t have the time or patience for. No agency, no account managers, no jargon: just me doing the work, and picking up the phone when you need me. Tell me what you’re after and I’ll get your business online properly.
Contact Us


Client: lukeduff.co.uk · Sector: SEO & Developer Tooling · United Kingdom
Teardown is a Chrome extension that tears any web page apart in one click and reports what is actually wrong with it. It was built for the kind of work where you are sat on a client site, a competitor's site or a staging build and need the answer immediately, not after a crawler has finished its run. Most heading tools stop at printing an H1-H6 list. Teardown opens with the question that comes before that: can this page rank at all? It reads the robots meta tag, canonical, hreflang and page language first, then works down through structure, metadata, images, content and design.
The project started life as a heading grabber, outgrew that remit, and got a new name. Luke was sole designer, developer and publisher throughout, from the initial concept through to the Chrome Web Store listing at version 1.7. There is no framework, no build step for the extension itself, no runtime dependencies, and no account required to use it. The packaged extension is 52KB across 11 files, written in around 5,500 lines of vanilla JavaScript across six source files.
Everything is local. Scan history is stored in chrome.storage.local on the user's own machine and deleted on uninstall. The only outbound requests beyond the audited page's own sitemap, robots.txt and llms.txt are an optional webhook the user points at their own endpoint. No developer-operated backend exists.
picture srcset markup and video poster attributes hide from a normal right-click inspection.| Problem | Solution Implemented |
|---|---|
Split-text animation libraries rewrite headings into character-level inline blocks with no whitespace, so reading textContent returned run-together strings like "HelpingSunderlandBusinesses". |
Heading text is now read from innerText, which returns the rendered text a reader and a crawler both see rather than the raw structural markup. |
The original keyword pass used a tag allowlist and read only direct child text nodes, so words inside links, strong elements, table cells and figcaptions were invisible. On one test page it saw two instances of a phrase that appeared six times and missed the top commercial term entirely. |
Keyword extraction now walks text nodes with a TreeWalker, because link and emphasis text is precisely where commercial keywords tend to live. |
| Whitespace handling pulled in two opposite directions. Character-split animations needed adjacent inline text concatenated raw, while ordinary inline markup needed the gap between elements preserved. Fixing either case alone broke the other. | The extractor was reworked to satisfy both cases simultaneously: split-character headings join cleanly while genuinely separate inline elements keep the space between them. |
Tokenising with \w treats every accented or non-Latin character as a word separator. "Reparation" with an accent came back as "paration", and Japanese and Arabic pages returned nothing at all. |
Tokenising moved to Unicode property escapes, with Intl.Segmenter for scripts that do not use spaces between words, and stop word lists merged in from the page's html lang attribute. |
WordPress and card-grid layouts emit one article per item. On a real client site the first was an inactive hero carousel slide holding 53 words, so scoping content to it reported a word count of zero on a page of roughly 2,000 words. |
A lone article now has to hold at least a third of the page's total text before it is trusted as the content root; otherwise the scope falls back to the wider page. |
| The stylesheet originally loaded a typeface from Google Fonts, which sent every user's IP address and user agent to Google on every popup open, while the privacy policy stated that no third party received anything. | The font is now self-hosted inside the package. A build-time test scans every shipped file for third-party hosts and fails the build if one is found, so the privacy claim is enforced rather than asserted. The landing site's three CDNs were removed for the same reason. |
| The previous layout check was "does the page scroll sideways" plus "are there console errors", which passed while a screenshot sat 671px wide inside a 737px cell, hanging 99px over its neighbour. Nothing was logged and the page never scrolled, because the overlap was contained inside the grid. | A geometry suite was added that measures elements against their containers across four pages at eight viewport widths, because a page-level check cannot see a component-level break. |