
A public dataset is not a product. A CSV with 9,000 rows is a file. A product is the question that file cannot answer until you turn rows into pages.
I have done this twice in public.
First, AEDLocator.sg takes SCDF's public-access AEDs and answers "is there a cabinet in this building, and can I reach it after 6pm?".
Second, AgentLookup.sg takes the CEA salesperson register and answers "is this agent registered, and what does this HDB town look like?" This page is the loop I would run a third time.
01
Source
A public file with a stable ID and a question a person already asks.
02
Clean
Normalize names, hours, and joins. Do not invent fields the file lacks.
03
Pages
One URL per answer, then links to the next related answer.
04
Ship
JSON in the repo, ISR, sitemaps. Monetize after the graph exists.
A dump is not a directory
data.gov.sg is full of files that look valuable in a spreadsheet and fail in a browser.
Researchers can handle a 6.9MB GeoJSON. Someone asking "AED at Junction 8" or "is this CEA number real" cannot.
A directory is a graph of pages. Each URL answers one question, then points at the next related question. Search is a tool. The shareable object is the page.
That is different from a map demo, a dashboard, or a chatbot over the CSV. Those can be features. They are weak products on their own. A map pin does not tell you the cabinet is on B2 next to pillar H. A table of 38,000 names does not verify the person on a name card.
Pick a dataset that already has a human question
Do not start with "what files are on data.gov.sg." Start with a sentence a person already types, texts, or asks a spouse.
AEDLocator's sentence is reachability, not "open data about defibrillators." AgentLookup's sentence is verification, then town research. If you cannot write the sentence, you will generate thin URLs.
The file has to support that sentence:
- A stable ID you can put in a URL (
AED_ID, CEA registration number). Without it, you cannot update a page next week. - Fields that answer the question (hours and floor for AEDs, agency and licence for agents).
- A licence that lets you republish. Read the dataset terms. Credit the source on the page. Link back.
I skipped SCDF's thinner three-column AED file because it had no ID, hours, floor, or coordinates. It looked simpler. It could not be a product. That is in the AEDLocator write-up because I almost used it.
If the question needs a field the file does not have, stop. Do not invent hours, ratings, or "top producer" labels. AgentLookup will not rank agents by deal value. The CEA transaction file has no dollars and no building names. A confident number with no source is worse than a missing number.
Clean until you have an as-of date
Ingest is the product's trust layer.
Normalize names, hours strings, postal codes, and join keys. Count missing building names (AEDLocator: about 55%). Count duplicate IDs (last AED ingest: 0). Publish the sync date on the site. AgentLookup's last snapshot in that post was 38,232 profiles across 1,015 agencies, synced 12 August 2026.
Keep datasets in their lanes. AgentLookup uses CEA for who is registered and HDB/URA files for town medians. Those facts do not live on the same page as if they were one story. A town median is not that salesperson's deal.
I ship processed JSON in the repo. 9,644 AEDs fit in memory. 38k agent profiles fit in a MiniSearch index in the browser. A runtime database as the read path would make deploys harder to reproduce and would not make search feel faster.
Weekly ingest plus a deploy is honest. It is not live. AEDLocator says that out loud: myResponder is the emergency source. AgentLookup says the register can change between syncs. If you cannot say when the file is from, do not ship the page.
Pages that point at other pages
The information architecture is the product. I keep relearning this.
AEDLocator: device → building → postal → area → mall/MRT. AgentLookup: agent → agency roster → letter index → town → tools. A lookup that dumps you on a 404 after one fact is a demo.
Rules I reuse:
- One URL per answer.
/aed/{id},/agent/{cea-slug}. Not a query string you cannot share. - The next click is obvious. Other AEDs in the building. The rest of the agency roster. The HDB town you actually live in.
- Search stays
noindex. The profile or location page is what Google and WhatsApp should see. - Thin slices stay unpublished. AgentLookup does not mint a median on three deals. AEDLocator does not invent a building name for the 55% of rows that lack one.
Programmatic SEO is a side effect of useful pages. It is not the brief. If you generate 38,000 URLs to "get traffic," you will get a quality problem and a sitemap you are ashamed of.
Share cards matter because the next user is often a spouse. AgentLookup's Open Graph images are town, count, median, window. No "market is hot" badge. A pretty lie travels farther than a table.
Ship JSON, ISR, and segmented sitemaps
The stack is boring on purpose.
- Next.js 16 App Router
- Processed JSON in the repo, not a live database on the read path
- ISR: 24 hours for AED pages, 7 days for agent profiles
- Prebuild a slice, resolve the rest on demand. 38,000 HTML files on first paint slow deploys for no user benefit
- Segmented XML sitemaps so Google is not asked to discover thousands of URLs by accident. I wrote the App Router patterns in the sitemap guide
You do not need a SaaS boilerplate for this. There is no Stripe. There is no team invite. There is a file, a page template, and a sitemap. The reusable stack is in how I ship micro-tools with Next.js, Supabase, and Vercel.
Maps are an island. AEDLocator loads MapLibre when that island mounts. Nearby is haversine, labelled as straight-line distance. I will not guess a walk time from a pin.
Monetize after the graph exists
I did not start these two with a paywall. I started with a page someone can send.
Directories can make money later: ads, affiliates, sponsored listings, a data API, or a sale. I sold OpenGraph.xyz for $20,000 with traffic and no MRR. That is one path. It is not a promise that an AED directory will do the same.
What I will not do is mix inventing fields with "monetization." Ranking agents from incomplete activity counts to sell leads would be a good quarter and a bad product. If the business model needs a field the government file does not contain, the business model is wrong for this source.
CareAcross and ClinicGeek sit in the same "useful local directory" family. I am not pretending they share one ingest pipeline with SCDF GeoJSON. The playbook is the question, the ID, the pages, and the as-of date.
The vertical changes. The loop does not.
What I would repeat, and what I would not
Repeat
- Start from the sentence, then pick the file
- Require a stable ID and an as-of date
- JSON in the repo until the data no longer fits
- ISR plus a partial prebuild
- Related-page links as the IA
- Credit the source, link to the official register, show last sync
- Segmented sitemaps from day one
Would not do again
- The thin source file because the columns were fewer
- Map-only MVP as the product
- Prebuilding every entity HTML file on first paint
- Joining datasets that share a key but not a job (HDB dollars on a salesperson page)
- Leaderboards from counts the source cannot support
- Treating Google as the user of search-result URLs
The two long posts are the receipts. This page is so I do not have to reverse-engineer my own decisions from memory the next time data.gov.sg publishes something awkward and useful.
FAQ
How do you turn a public dataset into a website?
Find a question people already ask. Confirm the file has a stable ID and the fields that answer it. Clean the file, write one page per answer, link related pages, ship JSON plus ISR, add sitemaps, show the sync date.
What public data did AEDLocator and AgentLookup use?
AEDLocator uses SCDF public-access AEDs on data.gov.sg. AgentLookup uses the CEA registered salesperson list, plus HDB and URA market files on separate pages. Details are in the AEDLocator and AgentLookup posts.
Do I need a database for a niche directory?
Not at 10k or even 40k rows if the read path is a processed JSON snapshot. I use a database when writes, auth, or multiplayer editing show up. These directories are snapshots of public files.
How do you monetize a public-data directory?
After the graph is useful: ads, affiliates, B2B access, or a sale. I would not monetize by inventing rankings the source cannot support. OpenGraph sold on traffic. These two are still in the "make it trustworthy" phase.
Is programmatic SEO enough to pick a dataset?
No. If the page would not help someone standing in a mall or holding a name card, do not generate it. SEO follows useful URLs. It does not excuse empty ones.
What to do next
Open the dataset you keep meaning to ship. Write the human sentence. If the file cannot support it, pick another file.
If you want the two worked examples, start with how I built AEDLocator.sg, then how I built AgentLookup.sg. For the stack those sites share, read micro-tools architecture. When traffic is high enough for ads, read DR vs traffic vs revenue and the ad network comparison. If you are on the same loop, I am on Twitter.


