Skip to content
← All projects
2026Product and design. Built with Lovable and AI coding agents, then hardened and tested through its GitHub sync

PreviewProof: see your link the way the internet sees it

A link-preview checker for apps built with AI tools. It fetches a page without running JavaScript, as X, LinkedIn, Slack and WhatsApp do, and shows what each platform will display. It scores the page, and every problem comes with a copy-paste HTML fix and a prompt you can paste straight back into Lovable. It knows Lovable specifically: it recognises older client-rendered projects and explains that Lovable pre-renders those for verified crawlers but not for every unfurler.

TypeScriptReact 19TanStack StartCloudflare WorkersSSRF defencePlaywrightLighthouse CILovable
100Lighthouse accessibility, best practices and SEO, enforced in CI: the build fails below it

Architecture

Architecture of PreviewProof: see your link the way the internet sees it, as a pipeline. 1. Validate: scheme, credentials, host and port, before any network access 2. Resolve: DNS-over-HTTPS per hop; every address must be public 3. Fetch: redirect set to manual, each Location re-validated; 8 s budget, 2 MB cap 4. Parse: head-only metadata, charset-aware, entity-decoded 5. Analyse: score, prioritised fixes with HTML and a Lovable prompt, five platform previews
  1. 01

    Validate

    scheme, credentials, host and port, before any network access

  2. 02

    Resolve

    DNS-over-HTTPS per hop; every address must be public

  3. 03

    Fetch

    redirect set to manual, each Location re-validated; 8 s budget, 2 MB cap

  4. 04

    Parse

    head-only metadata, charset-aware, entity-decoded

  5. 05

    Analyse

    score, prioritised fixes with HTML and a Lovable prompt, five platform previews

The problem

Many apps built with AI tools are single-page apps whose title, description and Open Graph tags only exist after JavaScript runs. Browsers show them fine, but link-preview bots never run JavaScript, so every share becomes a bare link and the builder never finds out. Checking this means fetching arbitrary URLs that strangers paste in, from a server, which is exactly the shape of an SSRF hole.

Approach & decisions

  • The fetcher is built to be pointed at by strangers. Only http and https on ports 80 and 443; before every request the hostname is resolved over DNS-over-HTTPS and refused if any answer is private, loopback, link-local, CGNAT, documentation, benchmarking or multicast. IPv6 is checked as an allowlist (global unicast only), and IPv4 addresses embedded in IPv6 are unwrapped and checked as IPv4.
  • Redirects are followed by hand, at most five, and every hop is re-validated and re-resolved. The og:image and favicon checks go through the same guard, so a hostile page can't point them inward.
  • Hard limits that hold even when the platform doesn't cooperate: an 8-second page budget and 5 seconds per asset, enforced by racing every await against the deadline, because a Workers fetch, DNS lookup or stream cancel can ignore an abort signal. Downloads stop at 2 MB.
  • Grade the page, not the firewall. Cloudflare-style bot challenges are recognised and reported as such, rather than scored as if they were the site.
  • The parser reads only the head and ignores comments, scripts and templates. It handles charsets, byte-order marks, all HTML 4 named entities and a '>' inside attribute values, because real pages have all of them.
  • Styling follows the stack's own conventions rather than one-off classes: design tokens in Tailwind v4's theme, and every button, badge and disclosure built from shadcn primitives with cva variants. axe can't check text on gradients or glass, so a unit test checks WCAG AA contrast for every token pair in both themes, including each stop of the brand gradient. It caught the main button failing in light mode, and the fix shipped.
  • Browser tests run against a small fake internet that exists only in development, so every results path (a perfect page, a blank SPA, an old Lovable app, a bot challenge, a name that resolves privately) is exercised offline in CI. The fake network is compiled out of the production bundle.

Results

  • Unit, integration and component tests, including the SSRF bypass forms (decimal, hex and octal IPv4, IPv4-mapped IPv6, NAT64, 6to4) and deadlines that hold when fetch, DNS or a stream ignores the abort signal.
  • Playwright browser tests, each run on desktop and mobile, with axe accessibility checks on the results page in light and dark mode.
  • Key tests were mutation-checked: auto-following redirects, skipping the DNS check, dropping the IPv4-mapped check, awaiting a stuck stream cancel and running a check twice were each re-introduced, and each one failed the suite.
  • Lighthouse CI runs against the production Worker build on every push and fails it below 90 for performance or 100 for accessibility, best practices and SEO. The latest run scored 92, 100, 100 and 100 on mobile, and 100 across the board on desktop.

What this honestly is not

  • Not a pixel copy of each platform. The previews are built from your real tags and each platform's known fallbacks, and platforms change their layouts.
  • Not a closed DNS-rebinding defence. There's a small window between the DNS check and the connection that the Workers fetch API can't close. Workers can't reach private networks in the first place.
  • No per-client rate limit yet.

Stack

TypeScriptReact 19TanStack StartCloudflare WorkersTailwind CSSMotionVitestPlaywrightaxe-coreLighthouse CILovable