When we launched our SaaS in September, the domain had zero referring domains. Every launch checklist says the same thing: submit to directories. G2, Capterra, the AI tool lists, the launch sites.
Before spending weeks on forms and review queues, I wanted to know what each one actually gives back. So between August and October we measured 64 of them. The short version: four gave us a followed link on a free plan, and only three without a caveat. The full table is in the write-up on our blog.
This post is about the other half of the work: how to check a listing yourself, and why the first answer you get is often wrong.
The check
For each directory, look at a real vendor's listing while logged out, and ask four questions:
- Is there an
<a>pointing to the vendor's domain at all? - What does its
relsay? - Is the listing page indexable (
meta robots,X-Robots-Tag), and is its canonical itself? - If the link goes through an internal redirect, is that path blocked in
robots.txt?
The first three fit in a few lines of Node (18+, no dependencies):
// check-listing.mjs // usage: node check-listing.mjs <listing-url> <your-domain> const [url, domain] = process.argv.slice(2); const res = await fetch(url, { headers: { 'user-agent': 'Mozilla/5.0' } }); const html = await res.text(); console.log('status ', res.status); if (res.status !== 200) { console.log('not a 200: you measured a block page, open it in a browser'); process.exit(1); } console.log('x-robots ', res.headers.get('x-robots-tag') ?? '(none)'); console.log('robots ', html.match(/<meta[^>]+name=["']robots["'][^>]*>/i)?.[0] ?? '(none)'); console.log('canonical ', html.match(/<link[^>]+rel=["']canonical["'][^>]*>/i)?.[0] ?? '(none)'); const host = (href) => { try { return new URL(href, url).hostname; } catch { return ''; } }; const links = [...html.matchAll(/<a\b[^>]*href=["']([^"']+)["'][^>]*>/gi)] .filter(([, href]) => host(href) === domain || host(href).endsWith('.' + domain)); if (!links.length) console.log(`no <a> to ${domain} in the server HTML`); for (const [tag, href] of links) { const rel = tag.match(/rel=["']([^"']*)["']/i)?.[1]; console.log((rel || '(no rel)').padEnd(28), href); } Regex on HTML is crude, and that's fine here: you're not parsing a page, you're asking yes/no questions about a handful of tags. What matters more is knowing when the output lies.
Trap 1: the script gets a 403
Capterra, G2, Trustpilot, Product Hunt and AlternativeTo answer a script with a 403 or a bot challenge, even with full browser headers. AlternativeTo answered curl on one day and put a Cloudflare challenge in front of it the next. If the status isn't 200, you've measured the block page. Open it in a real browser.
Trap 2: "no nofollow" doesn't mean "followed"
On G2 and Capterra, "Visit website" is a <button> with no href. The vendor URL sits inside a JSON blob. SaaSworthy uses a <span onclick>. A check that only counts rel="nofollow" reports nothing wrong, because there's no link to carry the attribute.
That's why the script looks for the <a> first and says so when it's missing.
Trap 3: a followed link that goes nowhere
Future Tools and SourceForge send the click through their own redirect path, and that path is disallowed in their robots.txt. The rel says followed; a crawler never gets through.
StackShare's links have no nofollow, but the listing pages declare the homepage as canonical. If the script prints no <a> to your domain but the page clearly links out, look for the redirect and check it against robots.txt.
Trap 4: the attribute changes when you claim the listing
On StackShare, before we claimed our listing, the desktop button was nofollow ugc. After the claim, both were gone. On SaaSHub the main button was nofollow on another vendor's listing in September and followed on ours after verification. Measuring someone else's listing tells you what an unclaimed one gets.
Trap 5: it changes again later
DevHunt linked to us with no rel on September 28. On October 24 the same link was nofollow noopener. No email, no changelog. A link you counted a month ago needs counting again, which is the real reason to keep the script around.
Trap 6: server HTML vs. rendered DOM
On several of these sites the HTML the server sends and the DOM the browser builds are not the same thing. The script only sees the first. When a listing looks wrong, open DevTools and compare.
What it added up to
Out of 64: 16 nofollow, ugc or noindex; 13 dead or with a broken submit form; 13 with a followed link only on a paid plan; 10 offering a free followed link on paper; 9 with no real link at all; 3 that want a badge on your site in exchange. Of the 10 free ones, four were live on our listings at the end of October. The rest were still in queues that ran from three weeks to five months.
None of this says a nofollow listing is worthless. A Crunchbase or AlternativeTo page can still rank for your product name, and AI answers do cite them. It does say that if the link is the reason you're filling in the form, check first. It takes two minutes per directory, and the review queue takes months.
Written with AI assistance and edited by me. The measurements are ours, done by hand and re-checked in a browser.
