For Realtors & Med Spas· 13 min read

How I Built PropertySignalHQ: From 0 to Indexed in 6 Weeks

A walk-through of building a real estate intelligence platform with hundreds of indexed pages. Tech stack, content strategy, what worked, what didn't.

I built PropertySignalHQ to test a hypothesis: a single developer, with no marketing team and no SEO agency, could ship a real estate site that indexed faster and ranked deeper than most agent sites in any major metro. The thesis was that programmatic SEO done with actual care — not spun, not thin — was a market inefficiency. Most agents weren't doing it. The ones who were doing it were doing it badly.

This is the build log. The stack, the content strategy, what shipped first, what got indexed first, what didn't, and what I'd do differently next time.

Why I built it

The honest reason: I wanted to prove this works at scale before charging clients for it. Pitching programmatic SEO to a realtor is hard if your only proof point is "trust me." It's easy if you can say "I built one of these, here's the Search Console data, here's the indexed page count, here's the keywords it ranks for."

The secondary reason: there's a real product hiding in pSEO real estate sites. Once you have hundreds of pages indexed for hyper-local terms, you can layer lead capture, agent matching, or paid listings on top. The site is both a marketing demo and a viable independent business.

The stack

I picked tools I could move fast in, not tools that looked good on a resume.

  • Next.js 14 (App Router) — server components by default, easy static generation for pSEO pages, deployment to Vercel is one git push
  • Postgres on Neon — serverless Postgres, free tier covered the first few thousand rows
  • Drizzle ORM — typed queries, schema-in-code, fits the Next.js workflow
  • Tailwind CSS — page templates with a lot of conditional sections; utility-first means I'm not naming 40 classes
  • Vercel — preview deploys on every PR, fast Edge for the public pages
  • Cloudflare DNS + Vercel — separated DNS from hosting so I could swap hosts later if needed

What I deliberately didn't pick: a CMS. Sanity, Contentful, Strapi — they all assume content is created by humans. pSEO inverts that. Content comes from data. A CMS sits between the data and the page templates and adds nothing.

The data layer

The first decision was where to get data. Without a defensible data layer, the site is just a template; anyone can copy it.

I went with three sources:

MLS via an IDX feed — the heaviest lift. RESO Web API is becoming standard but most boards still hand you a non-standard XML or RETS feed. The pipeline pulls listings nightly into Postgres, normalizes them, computes derived fields (price per sqft, days on market, neighborhood centroid).

US Census API for demographics. Median household income, education level, housing unit counts, age distribution. The acs/acs5/profile endpoint with a tract or block group geography ID gets you everything you need for a neighborhood profile.

Custom-curated neighborhood metadata. This is the part you can't automate. For each neighborhood I served, I wrote 4–6 sentences of unique character: what the area is known for, what types of buyers it attracts, what the most expensive and most affordable streets are. This is the moat. Census data is free; the editorial layer on top of it isn't.

I deliberately did not use WalkScore on launch. It's good data but adds $200/yr and a dependency I didn't need for v1. I added it later for the top 30 neighborhoods.

Page templates I shipped

Four templates, in this order:

1. Neighborhood pages — the workhorse. URL: /areas/[city]/neighborhoods/[slug]/. Each page had: hero, neighborhood profile (the curated character section), median home price chart (12-month rolling), demographics summary, schools table, live listings carousel from the MLS feed, nearby neighborhoods, FAQ.

2. School pages/areas/[city]/schools/[slug]/. School name, rating, attendance zone description, current listings within the zone, demographics, nearby schools.

3. Market report pages/market-reports/[city]/[year]-[quarter]/. Quarterly. Median price, days on market, inventory, year-over-year. Updated programmatically from the MLS feed plus an editorial paragraph I wrote each quarter.

4. ZIP code pages/areas/[city]/zip/[zip]/. Came later. These rank surprisingly well for "[zip] homes for sale" — search volume isn't huge per ZIP but the cumulative tail is significant.

I considered a "schools by district" template and a "subdivision" template. I didn't ship either. Lesson: I would rather have four templates that are very good than seven that are decent.

What got indexed first

Search Console doesn't give you a per-page indexing timestamp, but you can approximate from impression data. The pattern was consistent:

Week 1–2: city overview pages (/areas/austin-tx/) and the most-linked neighborhood pages indexed first. These were the pages with the most inbound internal links from the navigation and the home page.

Week 3–4: the rest of the top-20 neighborhood pages indexed. School pages started showing up — but only the schools with the most distinctive content. Generic schools with sparse data sat in "Discovered."

Week 5–6: market reports indexed. ZIP code pages started showing up — these were the slowest because they had the least inbound linking from the rest of the site.

Past week 6: the long tail. About 30% of neighborhood pages and a larger portion of school pages stayed in "Discovered – currently not indexed" for weeks. These were almost all pages where the content was thinner than my own threshold — short curated character sections, sparse demographic data, no live listings because the MLS feed didn't have inventory in that zone that week.

What didn't work

I'll be straightforward about the misses.

The first version of the neighborhood character section was too templated. I wrote a four-paragraph structure that filled in neighborhood name, median price, walkability, and one "known for" detail. Across 50 neighborhoods this read as identical. Google noticed. I rewrote the character sections to be genuinely different lengths and structures per neighborhood — three paragraphs for one, five for another, a bulleted list for a third where that fit the content. Indexing rates went up.

School attendance zones were a disaster. Mapping homes to school attendance zones requires the actual zone polygons, which most districts don't publish in a machine-readable form. I tried using ZIP-to-school mappings as a proxy and it was wrong about 30% of the time. The fix was scoping back: I only showed "schools near this property" rather than "this property's attendance zone." Less powerful but defensible.

The market report pages didn't rank as well as I expected. Hindsight: most "[city] real estate market" searches return news articles, not data. The pages that ranked were the ones I wrote actual analysis paragraphs on, not just the data table. Quarterly updates with real commentary > monthly updates with just numbers.

Initial sitemap.xml was one file with everything. Google sampled it and indexed about 60% of pages over the first month. After I split into a sitemap index with per-template sub-sitemaps and re-submitted, the indexing rate caught up over the following two weeks.

Schema markup choices

I shipped JSON-LD on every template type. Specifics:

  • Neighborhood pages: Place schema with geo (centroid lat/lng), address (locality + region), containedInPlace (the city). Each FAQ at the bottom uses FAQPage schema.
  • School pages: EducationalOrganization with geo, address, and aggregateRating where I had it.
  • Market reports: Article with datePublished, dateModified, and an author field linking back to me.
  • Home page: Organization schema.

I did NOT use RealEstateListing schema on individual listings because the IDX widget renders listings via a third-party iframe — Google's structured data validator was unhappy with the result, so I dropped it.

One non-obvious win: adding BreadcrumbList schema everywhere. Breadcrumbs in SERPs improve click-through rate measurably, and Google honors them on neighborhood/school sub-pages where the URL structure is deep.

Internal linking strategy

This is where most pSEO real estate sites lose. They build 500 pages and then link every single one only to their home page and a contact form. That wastes PageRank.

What I did:

  • Every neighborhood page links to 3–4 adjacent neighborhoods in a sidebar widget. Adjacent meaning geographically next to it, not alphabetically.
  • Every neighborhood page links to 2–3 schools that serve the area.
  • Every school page links to the 1 neighborhood the school's address sits in, plus the 3–4 most relevant nearby neighborhoods.
  • Every market report links to the 5 highest-volume neighborhood pages in that city.
  • The home page links to city overview pages, not directly to neighborhoods. This keeps the home page from getting diluted across 50 outbound links.

The shape of this is: home → city overview → neighborhood → school. Maximum click depth from home: 3. Google's crawler likes shallow sites.

What I'd do differently

If I were starting over:

Ship 30 pages, not 100. I shipped about 50 pages on launch and another 50 in the following month. The second 50 added marginal traffic compared to the first 50. The cost of writing the curated content for an additional 50 mediocre-tier neighborhoods exceeded the SEO upside. Better: ship 30 excellent pages, see what ranks, then expand into the patterns that work.

Skip school attendance zones until I had the polygon data. I lost about three weeks trying to map ZIPs to schools. Should have just shown "schools nearby" from the start.

Build the editorial pipeline before the data pipeline. I built MLS-to-Postgres before I had a workflow for writing curated character sections. Result: 30 pages live with curated content, 20 pages live with placeholder content I had to backfill. Reverse the order next time.

Take ranking screenshots earlier. Search Console rolls off old data after 16 months. Screenshot the impression growth charts at week 6, week 12, week 24. They're the most valuable proof points you'll have when pitching this to clients later. I didn't, and I regret it.

What it took

In rough effort: about 6 weeks of nights and weekends from scratch. Two weeks of pipeline plus stack scaffolding. Two weeks of template work plus editorial writing for the first 30 neighborhoods. Two weeks of post-launch fixes, sitemap refinement, schema tuning, and writing the next 20 neighborhood pages.

Cost: about $40/mo in infrastructure for the first six months (Vercel Hobby until I hit a soft limit, Neon free tier, Cloudflare free). Plus the data sources: free Census, the IDX feed was bundled with the brokerage license. Total out-of-pocket cash: under $300.

What I'd tell another developer

If you're thinking about building one of these:

  1. Pick one metro. Don't try to ship two cities at once. The editorial work doesn't compress.
  2. The stack matters less than the data. Next.js, Astro, Remix — all fine. The MLS feed is the moat.
  3. Write your own neighborhood descriptions. Don't paste Wikipedia. Don't generate from an LLM unless you're willing to edit aggressively after.
  4. Submit the sitemap index, not one giant sitemap.xml. Split by template type.
  5. Look at Search Console twice a week. The "Discovered – not indexed" list is the canary. Pages stuck there for 3+ weeks have a content problem, not a technical one.

If you read this far and you're a realtor wondering whether this would work on your site — it almost certainly will, because almost nobody else is doing it. See the real estate SEO services page for what a build like this costs, or read the more general programmatic SEO for real estate agents walkthrough.

Related posts