[object Object]

← 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

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&apos;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&apos;s available inventory in
+            separate websites that never meet. HomesOnSpec puts every builder&apos;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 →