← back to Homesonspec
rebrand SpecHomes → HomesOnSpec; add /builders directory (all 30 builders w/ coverage, website, live home counts + links)
1a425935f9113b54d13080b4e9abe3cbd9100fdf · 2026-07-22 14:14:02 -0700 · Steve Abrams
Files touched
M ROLLBACK-DNS.txtM apps/web/src/app/builders/[slug]/page.tsxA apps/web/src/app/builders/page.tsxM apps/web/src/app/homes/[id]/page.tsxM apps/web/src/app/layout.tsxM apps/web/src/app/page.tsx
Diff
commit 1a425935f9113b54d13080b4e9abe3cbd9100fdf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 22 14:14:02 2026 -0700
rebrand SpecHomes → HomesOnSpec; add /builders directory (all 30 builders w/ coverage, website, live home counts + links)
---
ROLLBACK-DNS.txt | 6 +-
apps/web/src/app/builders/[slug]/page.tsx | 2 +-
apps/web/src/app/builders/page.tsx | 106 ++++++++++++++++++++++++++++++
apps/web/src/app/homes/[id]/page.tsx | 2 +-
apps/web/src/app/layout.tsx | 5 +-
apps/web/src/app/page.tsx | 2 +-
6 files changed, 115 insertions(+), 8 deletions(-)
diff --git a/ROLLBACK-DNS.txt b/ROLLBACK-DNS.txt
index 4468684..2e6d3d8 100644
--- a/ROLLBACK-DNS.txt
+++ b/ROLLBACK-DNS.txt
@@ -1,4 +1,4 @@
-# Undo the spechomes.com cutover — point NS back to GoDaddy default:
-curl -s -X PUT "https://api.godaddy.com/v1/domains/spechomes.com" \
+# Undo the homesonspec.com cutover — restore the pre-cutover nameservers:
+curl -s -X PUT "https://api.godaddy.com/v1/domains/homesonspec.com" \
-H "Authorization: sso-key REDACTED_GODADDY_KEY" -H "Content-Type: application/json" \
- --data '{"nameServers":["ns23.domaincontrol.com","ns24.domaincontrol.com"]}'
+ --data '{"nameServers": ["ns69.domaincontrol.com", "ns70.domaincontrol.com"]}'
diff --git a/apps/web/src/app/builders/[slug]/page.tsx b/apps/web/src/app/builders/[slug]/page.tsx
index 0ce758b..4125caa 100644
--- a/apps/web/src/app/builders/[slug]/page.tsx
+++ b/apps/web/src/app/builders/[slug]/page.tsx
@@ -25,7 +25,7 @@ export default async function BuilderPage({ params }: { params: Promise<{ slug:
{builder.coverageArea} · {homeCount} available homes · {builder.communities.length} communities
</p>
<p className="mt-2 text-xs text-neutral-400">
- Listing on SpecHomes does not imply sponsorship or endorsement by the builder.
+ Listing on HomesOnSpec does not imply sponsorship or endorsement by the builder.
</p>
<section className="mt-8">
diff --git a/apps/web/src/app/builders/page.tsx b/apps/web/src/app/builders/page.tsx
new file mode 100644
index 0000000..52175ec
--- /dev/null
+++ b/apps/web/src/app/builders/page.tsx
@@ -0,0 +1,106 @@
+import Link from "next/link";
+import { prisma } from "@spechomes/database";
+
+export const dynamic = "force-dynamic";
+
+export const metadata = {
+ title: "Home Builders Directory | HomesOnSpec",
+ description:
+ "Every home builder on HomesOnSpec — national, regional, and local — with their coverage area, website, and live inventory count.",
+};
+
+export default async function BuildersDirectoryPage() {
+ const builders = await prisma.builder.findMany({
+ where: { isDemo: false },
+ include: {
+ _count: {
+ select: {
+ communities: true,
+ homes: { where: { status: "PUBLISHED", isDemo: false } },
+ },
+ },
+ },
+ });
+
+ const rows = builders
+ .map((b) => ({
+ slug: b.slug,
+ name: b.name,
+ website: b.websiteUrl,
+ coverage: b.coverageArea,
+ communities: b._count.communities,
+ homeCount: b._count.homes,
+ }))
+ .sort((a, b) => b.homeCount - a.homeCount || a.name.localeCompare(b.name));
+
+ const totalHomes = rows.reduce((s, r) => s + r.homeCount, 0);
+ const withInventory = rows.filter((r) => r.homeCount > 0).length;
+
+ return (
+ <div className="mx-auto max-w-6xl px-4 py-8">
+ <h1 className="text-3xl font-bold">Home Builders Directory</h1>
+ <p className="mt-2 text-neutral-600">
+ Every builder on HomesOnSpec — national, regional, and local. {rows.length} builders ·{" "}
+ {withInventory} with live inventory · {totalHomes.toLocaleString()} verified homes.
+ </p>
+ <p className="mt-1 text-xs text-neutral-400">
+ Listing does not imply sponsorship or endorsement by the builder. Inventory is verified from each
+ builder's own website.
+ </p>
+
+ <div className="mt-6 overflow-x-auto rounded-xl border border-neutral-200">
+ <table className="w-full text-left text-sm">
+ <thead className="border-b border-neutral-200 bg-neutral-50 text-xs uppercase tracking-wide text-neutral-500">
+ <tr>
+ <th className="px-4 py-3">Builder</th>
+ <th className="px-4 py-3">Coverage area</th>
+ <th className="px-4 py-3">Website</th>
+ <th className="px-4 py-3 text-right">Homes</th>
+ </tr>
+ </thead>
+ <tbody>
+ {rows.map((r) => (
+ <tr key={r.slug} className="border-b border-neutral-100 hover:bg-neutral-50">
+ <td className="px-4 py-3">
+ <Link href={`/builders/${r.slug}`} className="font-medium text-teal-800 hover:underline">
+ {r.name}
+ </Link>
+ {r.communities > 0 && (
+ <span className="ml-2 text-xs text-neutral-400">{r.communities} communities</span>
+ )}
+ </td>
+ <td className="px-4 py-3 text-neutral-600">{r.coverage ?? "—"}</td>
+ <td className="px-4 py-3">
+ {r.website ? (
+ <a
+ href={r.website}
+ target="_blank"
+ rel="nofollow noopener noreferrer"
+ className="text-teal-700 hover:underline"
+ >
+ {r.website.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, "")}
+ </a>
+ ) : (
+ <span className="text-neutral-400">—</span>
+ )}
+ </td>
+ <td className="px-4 py-3 text-right">
+ {r.homeCount > 0 ? (
+ <Link
+ href={`/search?builder=${r.slug}`}
+ className="rounded-full bg-teal-50 px-3 py-1 text-xs font-semibold text-teal-800 hover:bg-teal-100"
+ >
+ {r.homeCount.toLocaleString()} homes →
+ </Link>
+ ) : (
+ <span className="text-xs text-neutral-400">coming soon</span>
+ )}
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ );
+}
diff --git a/apps/web/src/app/homes/[id]/page.tsx b/apps/web/src/app/homes/[id]/page.tsx
index df8c462..58188fc 100644
--- a/apps/web/src/app/homes/[id]/page.tsx
+++ b/apps/web/src/app/homes/[id]/page.tsx
@@ -113,7 +113,7 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
))}
</dl>
<p className="mt-2 text-xs text-neutral-400">
- “Not published” means the source did not state this fact. SpecHomes never fills in missing values.
+ “Not published” means the source did not state this fact. HomesOnSpec never fills in missing values.
</p>
{incentives.length > 0 && (
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 90045b9..5096474 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -3,7 +3,7 @@ import Link from "next/link";
import "./globals.css";
export const metadata: Metadata = {
- title: "SpecHomes — Every new home. Every builder. One search.",
+ title: "HomesOnSpec — Every new home. Every builder. One search.",
description:
"Discover, compare, and contact builders for newly constructed homes across the United States. Verified from the source.",
};
@@ -20,10 +20,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<header className="border-b border-neutral-200">
<nav className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3">
<Link href="/" className="text-xl font-bold tracking-tight text-teal-800">
- SpecHomes<span className="text-neutral-400">.com</span>
+ HomesOnSpec<span className="text-neutral-400">.com</span>
</Link>
<div className="flex items-center gap-5 text-sm text-neutral-700">
<Link href="/search" className="hover:text-teal-700">Search</Link>
+ <Link href="/builders" className="hover:text-teal-700">Builders</Link>
<Link href="/compare" className="hover:text-teal-700">Compare</Link>
<Link href="/saved" className="hover:text-teal-700">Saved</Link>
<Link href="/contact" className="hover:text-teal-700">Contact a builder</Link>
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 5641d3c..8ec1fa5 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -25,7 +25,7 @@ export default async function HomePage() {
<p className="mx-auto mt-3 max-w-2xl text-sm text-teal-200">
Search “spec home listings” today and you get scattered builder sites, generic portals, and
blog posts — there is no one place to see them all. Builders list inventory on dozens of
- separate websites that never meet. SpecHomes puts every builder's available inventory in
+ separate websites that never meet. HomesOnSpec puts every builder's available inventory in
one verified search.
</p>
<form action="/search" method="get" className="mx-auto mt-8 flex max-w-xl gap-2">
← af67cea clean-relaunch script for homesonspec.com (ship demo-filtere
·
back to Homesonspec
·
auto-save: 2026-07-22T14:44:58 (1 files) — cert-homesonspec. ab1a330 →