It’s a Friday afternoon, about a week ago. I’m buried in RIA compliance firm websites, one tab after another, and I’m losing the afternoon to it.
Here’s why I was there. I’m building a tool that checks marketing compliance for advisory firms, so I spend a lot of time in the world these firms live in. That afternoon I wanted something simple: a clear picture of the compliance firms themselves — who they are, what they do, who they actually serve.
There’s no serious directory for that. Nothing that lets you compare firms in one place. So I did it the slow way. Open a site, read the homepage, hunt for the services page, try to work out whether they’d even fit the kind of firm I cared about, move on. Tab after tab after tab.
Twenty minutes in, the thought every builder eventually has: there must be a better way.
There was.
BestRIACompliance.com is the independent directory that came out of that afternoon — a place where registered investment advisers (RIAs) can research the firms that keep them compliant, without opening forty tabs.
The directory looks like a set of cards with filters. But here’s what made it different from other directories I’ve built: this one started with the data, not the design. I built the research pipeline first, and that one decision is the whole story.
And if you’ve ever built a directory — or been tempted to spin up a batch of programmatic pages — you know the trap waiting at the far end: hundreds of thin, near-identical, AI-written pages that no human trusts and Google quietly buries. Dodging that was the entire project.
The visible site is almost boring: a directory of cards, some filters, firm profiles, three resource articles, a methodology page, an about page. Building the shell was not the hard part.
The hard part sat underneath it. Deciding which firms even belonged. Untangling aliases, rebrands, and redirects. Separating the RIA-relevant work from the ten other financial-services lines a big firm sells. Not copying a word of anyone’s marketing. And deciding which firms had enough real information to deserve a full page at all.
I reviewed 57 source records to publish 54 listings. The 3 that fell away didn’t fail a quality bar so much as fail to be distinct — duplicates and aliases collapsing into one canonical entry.
This was a research operation with a website attached, not a website with some research pasted in.
The temptation was to install Astro (my absolute favorite framework for content-driven websites) and start laying out profile templates. I’m glad I didn’t.
You can’t design the schema until you’ve seen what the market actually publishes, and no two RIA compliance firms publish it the same way. Some have crisp service pages. Some have a homepage, a phone number, and a vibe. Some bury RIA compliance inside a dozen other services.
So the first two files I made weren’t components. They were a canonical roster — identity, domains, aliases, eligibility, disposition — and a research ledger — pages captured, review dates, what was missing, what to clean up.
Then I processed firms in batches of five. Small enough to read every source carefully. Big enough that patterns showed up by the second batch: the same service under five different names, the same missing information in the same places.
Before you design a directory schema, look at enough real entities to see what the market actually publishes. Not what you assume it does.
This was the rule I refused to break.
raw markdown.new capture
→ internal research notes
→ normalized facts
→ original firm write-up
→ editorial approval
→ public listingThe captures were evidence. Never copy. Nothing a firm wrote about itself reached the site in its own words. Testimonials, award badges, broken visitor counters, “industry-leading” superlatives, the unrelated broker-dealer services — all discarded. When two pages on the same site contradicted each other, I either dropped the fact or turned it into a question the reader should ask the firm directly.
Every public summary was written from one seat: an independent reviewer answering a single question. Should an RIA actually pick up the phone and call this firm? I wrote that standard down — it’s the published methodology — so the judgment stays consistent and visible.
I spent more time reading bad “About” pages than writing components. Not the ratio I expected going in.
AI did a lot here, and I want to be precise about what.
It helped me inspect repetitive source material fast. It normalized fifty spellings of the same service into one. It drafted and revised summaries from facts I’d already locked. It flagged inconsistent fields, copied-sounding phrasing, missing sections, structural slips.
What it did not do was decide what was true. The source evidence and my judgment stayed authoritative the whole way through.
AI was the processing layer. The evidence was the source of truth. Blend those two jobs and you get confident, well-written, wrong.
It’s a rule I already live by on my other properties: if you can’t verify the output, don’t trust it. A directory people lean on for a real hiring decision is the last place to let a model freelance the facts.
Programmatic SEO tempts you to mint one URL per record. Fifty-four firms, fifty-four pages, ship it. That’s exactly how directories fill with thin, useless URLs.
I gave each firm one of two tiers. full — enough real information to justify a substantive profile. card-only — a relevant firm, but not enough public information to say anything useful at length.
A firm earned a full profile only if the research could answer most of these:
Is it clearly RIA-relevant?
Are there identifiable services?
Is the client type clear?
Is the delivery model clear?
Is there a real differentiator?
Is the regulatory scope clear?
52 firms cleared that bar. 2 didn’t — and instead of manufacturing a padded page, I gave them a card that links straight to their site. Every full profile follows the same six beats: overview; who it may suit; RIA compliance services; how the engagement works; what stands out; what to clarify before contacting.
Programmatic SEO doesn’t mean a page for every record. Sometimes the sharpest SEO decision is refusing to publish a thin URL.
Once the rules were right, the build almost fell out of them. Every firm is a single Markdown file with structured frontmatter.
---
name: "Example Compliance Firm"
slug: "example-compliance-firm"
website: "website_url"
positioning: "Outsourced compliance"
status: "approved"
profileTier: "full"
cardTags: ["Outsourced CCO", "Annual Reviews", "Exam Support"]
services: ["Ongoing Compliance Support", "Outsourced CCO", "Regulatory Examination Support"]
clientTypes: ["Emerging RIAs", "SEC-Registered Advisers"]
reviewedAt: 2026-07-20
---
The real schema carries more — summaries, best-fit lines, engagement models, things to clarify, metadata. The part that saved me was controlled vocabularies. “Outsourced CCO” has exactly one approved spelling. So does “Advertising and Marketing Review,” and every other service. That one discipline is why the filters work, and why I don’t have ten near-identical tags splitting the same category.
Astro’s job was almost mechanical, which is the compliment. Content collections load the Markdown as typed records. Zod validates the frontmatter, so a malformed firm file fails the build instead of shipping broken. getStaticPaths() generates every firm and article page. Components render to HTML at build time. Almost nothing reaches the browser.
export async function getStaticPaths() {
const firms = (await getCollection("firms")).filter(isFullProfile);
return firms.map((firm) => ({
params: { slug: firm.data.slug },
props: { firm },
}));
}The payoff: adding a firm doesn’t mean editing a page, a card array, a filter list, or the sitemap. I drop in one valid Markdown record and the system builds the rest. No database, no CMS, no backend. Just files on Astro 7, pushed through GitHub to Netlify.
The filters are the one interactive piece, and they don’t need a framework. Every approved card renders into the initial HTML with its services, positioning, and client types tucked into data attributes. A small vanilla-JavaScript component hides the non-matching cards and updates the count.
Turn JavaScript off and the whole directory is still there. That matters for a page you want crawled.
One deliberate choice: filter state never touches the URL. I didn’t want Google discovering ten thousand crawlable filter-combination URLs. Bootstrap 5 handles the grid, custom CSS carries the identity, and the fonts are bundled locally instead of pulled from a third party.
Every rule I cared about, I taught the build to check. A validation script runs on every build and fails it when something’s off:
firm and page counts don’t match what I expect
a slug and its filename disagree
a full profile’s six headings fall out of order
a card-only firm somehow grew a detail body
a page has more than one H1, or a description outside 140–150 characters
an internal link breaks, or the sitemap drops a page
an internal note leaks into public copy
If an editorial rule matters more than once, stop enforcing it by hand. Put it in the build.
None of this is specific to compliance. Swap the subject and the same spine holds:
Research the real entities before you design anything
Separate evidence from published copy
Write from normalized facts
Gate page depth on whether you actually have something to say
Let the build enforce the standards you’d otherwise babysit by hand
The ranking? Still early, but promising. Very promising. One first-place result, for one query, with ads sitting above it, on a new domain one week old.
I added GA4 and Search Console after launch, and the honest next step is watching whether that placement turns into traffic that does anything. I’ll write that up when the data’s real, not before.
If you want to see what came out the other end, it’s here: BestRIACompliance.com. Go poke at it, and tell me what you’d have built differently.
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.