DEV Community

Cover image for What I learned adding E-E-A-T transparency pages to a programmatic directory
MORINAGA
MORINAGA

Posted on Edited on

What I learned adding E-E-A-T transparency pages to a programmatic directory

The Open Alternative To directory I launched in April has 80 programmatic pages — one per SaaS tool it covers. The first AdSense submission came back rejected. The scaled content abuse flag led to pruning down to 18 indexed pages. The re-application is a single-site attempt, and it required building E-E-A-T infrastructure I'd been treating as optional.

Here's what I built, what I think matters versus what doesn't, and what I still don't know.

What E-E-A-T actually means for a programmatic directory

E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) is Google's framework, but AdSense doesn't publish its review rubric. My interpretation after two rejections: reviewers check whether the site has demonstrable decision logic that a scraper wouldn't replicate.

For a programmatic directory, that question narrows to something specific: can a reader understand why a page shows the alternatives it does, why they're ordered the way they are, and who made that call? A page that lists "Top 5 alternatives to Datadog" with no explanation of how the list was constructed is indistinguishable from scraped content, regardless of how sophisticated the generation was.

This is the frame I used to build three pages: methodology, about, and affiliate-disclosure.

The methodology page: DRY-ing curation code into prose

The gate that decides which pages are indexed lives in curation.ts:

export const CURATION = {
  MIN_ALTERNATIVES: 4,
  MIN_TOP_STARS: 1000,
  MIN_INTRO_LEN: 80,
};

export function isCurated(s: SaasEntry): boolean {
  if (!s.intro || s.intro.length < CURATION.MIN_INTRO_LEN) return false;
  if (s.model_used && GENERIC_MODELS.has(s.model_used)) return false;
  const alts = s.alternatives ?? [];
  if (alts.length < CURATION.MIN_ALTERNATIVES) return false;
  const topStars = alts.reduce((m, a) => Math.max(m, a.stars ?? 0), 0);
  if (topStars < CURATION.MIN_TOP_STARS) return false;
  return true;
}
Enter fullscreen mode Exit fullscreen mode

The methodology page imports these constants directly:

import { CURATION, CATEGORY_MIN_CURATED } from "../lib/curation.ts";
const MIN_ALTS = CURATION.MIN_ALTERNATIVES;   // → 4
const MIN_STARS = CURATION.MIN_TOP_STARS;     // → 1,000
Enter fullscreen mode Exit fullscreen mode

If I change the threshold from 4 to 5 alternatives, the methodology page updates on the next build without touching prose. The code is the single source of truth; the page renders the current value in a sentence like "at least 4 open-source alternatives with the most-starred project above 1,000 stars." (Those were the thresholds in May 2026. They were relaxed to 3 alternatives and 800 stars on 2026-06-21, and the sentence on the page changed itself — which is the whole point of this section.)

There's no six-week horror story behind this. The curation gate and the methodology page landed the same day, and I wired the import in from the first version of the page that quoted a threshold at all. That's deliberate: about 30 minutes up front removes a whole category of maintenance drift before it has a chance to start.

The methodology page also covers the AI/human split explicitly — which models write the prose (Claude Sonnet 4.6 and Claude Haiku 4.5), what they can get wrong (licensing nuance, version numbers, recent project status), and what's deterministic (alternative card metadata is rendered straight from the GitHub REST API v3, never touched by a language model). Where those models run is worth stating plainly too: the nightly ETL calls no model at all. I pulled ANTHROPIC_API_KEY out of refresh-content.yml on May 2nd, so a new entry lands as a templated fallback and a weekly Claude Code Routine upgrades it to real prose afterwards. The three-tier content quality ladder describes how the tiers work technically; the methodology page is the human-readable summary of those same tiers.

A content farm doesn't document the boundary between machine and human output because it has no such boundary. Documenting it is differentiating by definition.

The about page: visible authorship without fabricated authority

The original about page was two paragraphs of boilerplate. The re-application version adds four things: my GitHub handle (mori7ga2222) with a contact address and a jurisdiction, links to the sister sites and what they're for, the self-hosting context that shapes my editorial lens, and the reasoning behind each curation threshold — why 4 alternatives and not 1, why 1,000 stars, why an 80-character intro floor.

What it doesn't have is a cost breakdown, and I keep going back and forth on that. Saying "this runs for about $2.25 a month — three domains at cost, everything else on free tiers since I cancelled Vercel Pro — and the monetization hypothesis is affiliate, not AdSense" feels like admitting something unflattering. It's also the kind of specificity nobody bothers to fake. Every "legitimate business" about page reads the same; a verifiable number is the cheapest way to stop reading like one. Right now the page stops at "we are not a company front for a vendor, a paid review shop, or an SEO agency pretending to be a publisher" — true, and generic.

The self-hosting context matters because it's the basis for editorial judgment on comparison pages, and it's also the easiest place on the site to overclaim. The about page currently describes that background in general terms: several years of small VPS and managed-Postgres workloads, a handful of SaaS-to-self-hosted migrations on adjacent properties, and the unglamorous failures — TLS auto-renewal that silently broke, a backup script that worked for eleven months and then didn't. It does name specific tools — managed Postgres, Pagefind for self-built site search, Cloudflare Web Analytics for traffic counts, cron plus a Git-versioned pipeline in place of a hosted ETL scheduler, and the Claude API behind the editorial polish loop. What it doesn't do is identify which of the compared alternatives I've personally run, or say where that experience runs out, and it should. Overstating authority would be worse than admitting the gaps, especially for a site where the affiliate monetization path means readers trust my recommendations.

The affiliate disclosure: per-site additions to shared boilerplate

The packages/shared/legal/ directory has an affiliate disclosure template shared across all three sites. That template covers the generic FTC language. What it doesn't cover is the site-specific disclosure:

  • Which affiliate programs are currently active on this site
  • Whether affiliate status affects which alternatives appear or how they're ranked (it doesn't — the isCurated() gate doesn't have an affiliate field)
  • How to flag a disclosure issue

The ossfind-specific section I added answers the second and third of those. It does not answer the first: it names no active programs and no categories, and the shared template still says only that we "may participate in" Amazon Associates and other relevant programs. That's the honest state of things right now, and the constraint I gave myself was not to claim compliance with something we don't yet meet. Saying "all affiliate links are marked" when the current implementation marks zero would be a false claim in a legal disclosure. An empty program list is the less impressive but accurate option; filling it in is on the list.

The editorial takes: the content that actually demonstrates judgment

Alongside the transparency pages, I added three per-alternative editorial takes — long-form assessments for the auth0, datadog, and airtable comparison pages. These live in src/content/per-alternative-takes/ as an Astro 5 content collection and render only when a file exists for that SaaS slug.

The takes are AI-drafted, not typed from scratch — all three landed in one commit as sample drafts, and claiming otherwise on a page about the AI/human boundary would be self-defeating. As of this writing they have been through an automated Codex review pass only; my own fact review is still queued. What that review pass is for is the specifics: the auth0 take turns on whether AGPL §13 actually triggers for an embedded SaaS scenario, the datadog take on whether the netdata star count I cited matches the current GitHub number, the airtable take on the gap between NocoDB's AGPL and its hosted-version license terms. Those are the claims that have to be right, and checking them is the part I'm actually responsible for.

The noindex gate pruned 80 pages to 18 curated ones. Three of those 18 — one per vertical — have editorial takes. (Those are the May 2026 numbers. The remaining 15 takes landed on 2026-05-22, and on 2026-06-21 the relaxed thresholds pushed the curated set to 32 with a take for each.) At 3-4 hours each, writing takes for all 18 would be 54-72 hours. That's not the near-term plan. Three is enough to test whether a page carrying a reviewed, tool-specific judgment reads differently to a reviewer than one built from GitHub metadata alone.

Whether that distinction reads to an AdSense reviewer as meaningful — I genuinely don't know. The application is submitted. I'll share the outcome, whatever it is, in a follow-up.

What I'd do differently

I built these pages in the wrong order: disclosure first (most formulaic), methodology second, about page last (most uncomfortable). The right order is about → methodology → disclosure. The about page forces you to articulate who you are and why you have standing to run this directory. That clarity makes the methodology easier to write — you're not describing an abstract process, you're describing why you specifically set those thresholds. The disclosure becomes easier to write honestly once you've committed to the about page's honesty about the monetization goal.

The other change: I'd write at least one editorial take before the first AdSense submission. The initial rejection included "low-value content" as a reason. Demonstrable editorial judgment might have changed that, or might not — but it would have been faster to produce than the subdomain migration that followed.

FAQ

Does the methodology page need to be long?

No. The ossfind methodology page is about 1,100 words rendered. Length doesn't signal legitimacy; specificity does. A 400-word page that explains the exact curation threshold and links the source code where that threshold lives is more credible than a 2,000-word page describing a process in vague terms.

Should the editorial takes be AI-assisted?

Mine are, and I'd rather say so than pretend otherwise. Whether an AI-assisted take can still be genuinely editorial is the real question. Editorial value comes from judgment — choosing which licensing clause matters for which use case, assessing whether a star trajectory reflects real adoption. That judgment can be framed by a model and then has to be checked by a human, and the checking is the part I can defend. What can't survive that process is a first-person experience claim I haven't earned; that's the thing to keep out of a take regardless of who drafted it.

What if AdSense rejects again?

The monetization path doesn't depend on AdSense approval for the other two sites. Affiliate is the primary hypothesis. The ossfind re-application is a parallel bet with limited downside — the transparency pages are worth building regardless, because they make the site more honest for readers.


Related: How I kept 62 of 80 pages alive while hiding them from Google | How I built a three-tier content quality ladder for programmatic ETL

Part of an ongoing 6-month experiment running three AI-curated directory sites. The technical claims here are real; this article was AI-assisted.

Top comments (0)