From bf27fe77affba012b8d153610852fa33836688c8 Mon Sep 17 00:00:00 2001 From: Jordan Winters Date: Tue, 21 Jul 2026 16:53:39 -0500 Subject: [PATCH 1/8] refactor(seo): centralize site identity in lib/site-config Single dependency-free source of truth for name, origin, contact, founder, and location facts. baseUrl moves out of app/sitemap.ts; all importers migrate. Relative import in the sitemap graph keeps the tsx-executed health-check script working. Co-Authored-By: Claude Fable 5 --- app/(main)/blog/agent-coordination/page.tsx | 2 +- app/components/structured-data.tsx | 2 +- app/layout.tsx | 2 +- app/robots.ts | 2 +- app/sitemap.ts | 3 +- lib/site-config.ts | 53 +++++++++++++++++++++ 6 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 lib/site-config.ts diff --git a/app/(main)/blog/agent-coordination/page.tsx b/app/(main)/blog/agent-coordination/page.tsx index 7a5a71f..f66b683 100644 --- a/app/(main)/blog/agent-coordination/page.tsx +++ b/app/(main)/blog/agent-coordination/page.tsx @@ -1,4 +1,4 @@ -import { baseUrl } from 'app/sitemap' +import { baseUrl } from 'lib/site-config' export const metadata = { title: 'Agent Coordination Structure', diff --git a/app/components/structured-data.tsx b/app/components/structured-data.tsx index fb06205..8fb2999 100644 --- a/app/components/structured-data.tsx +++ b/app/components/structured-data.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import { baseUrl } from '../sitemap' +import { baseUrl } from '../../lib/site-config' interface StructuredDataProps { type: 'website' | 'person' | 'organization' | 'article' | 'breadcrumb' diff --git a/app/layout.tsx b/app/layout.tsx index 5b6f521..7eaa447 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -5,8 +5,8 @@ import { GeistSans } from 'geist/font/sans' import type { Metadata } from 'next' import type React from 'react' +import { baseUrl } from '../lib/site-config' import StructuredData from './components/structured-data' -import { baseUrl } from './sitemap' export const metadata: Metadata = { metadataBase: new URL(baseUrl), diff --git a/app/robots.ts b/app/robots.ts index 15fe5eb..58120ef 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -1,4 +1,4 @@ -import { baseUrl } from 'app/sitemap' +import { baseUrl } from '../lib/site-config' export const dynamic = 'force-static' diff --git a/app/sitemap.ts b/app/sitemap.ts index ba27b2f..d1bb264 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,7 +1,6 @@ +import { baseUrl } from '../lib/site-config' import { experiments } from './(main)/experiments/config' -export const baseUrl = 'https://bidwell.info' - export const dynamic = 'force-static' export default async function sitemap() { diff --git a/lib/site-config.ts b/lib/site-config.ts new file mode 100644 index 0000000..89f625c --- /dev/null +++ b/lib/site-config.ts @@ -0,0 +1,53 @@ +/** + * Single source of truth for site identity, origin, and contact facts. + * + * Must stay dependency-free: `app/sitemap.ts` imports from here and is + * executed outside Next by `scripts/generate-health-checks.ts` (via tsx), + * so everything in this module's import graph must resolve without + * bundler aliases. + */ +export interface SiteConfig { + name: string + baseUrl: string + description: string + email: string + founder: { + name: string + jobTitle: string + sameAs: readonly string[] + } + location: { + locality: string + region: string + country: string + areaServed: readonly string[] + } + sameAs: readonly string[] +} + +export const siteConfig: SiteConfig = { + name: 'Bidwell Consulting', + baseUrl: 'https://bidwell.info', + description: 'Expert software engineering and organizational consulting services', + email: 'jordan@bidwell.info', + founder: { + name: 'Jordan Winters', + jobTitle: 'Software Engineer & Organizational Consultant', + sameAs: ['https://www.linkedin.com/in/wintersjordan/', 'https://github.com/dralgorhythm'], + }, + location: { + locality: 'Minneapolis', + region: 'MN', + country: 'US', + areaServed: ['Minneapolis', 'Saint Paul', 'Minneapolis–Saint Paul metro area'], + }, + sameAs: [ + 'https://www.linkedin.com/in/wintersjordan/', + 'https://github.com/dralgorhythm', + 'https://soundcloud.com/dralgorhythm', + ], +} + +export const baseUrl = siteConfig.baseUrl + +export const absoluteUrl = (path: string): string => `${baseUrl}${path === '/' ? '' : path}` From 3f0c174048f08cc1e84ef9d774f48843f7363f82 Mon Sep 17 00:00:00 2001 From: Jordan Winters Date: Tue, 21 Jul 2026 16:58:22 -0500 Subject: [PATCH 2/8] feat(seo): typed local-entity structured data via schema-dts Replace the hardcoded type-switch component with pure builders in lib/structured-data.ts, linked by stable @ids: ProfessionalService with a city-level Minneapolis address and Twin Cities areaServed (was Organization/areaServed: Worldwide), Person = Jordan Winters working from Minneapolis (was company-named person working Remote), WebSite without the SearchAction that pointed at a nonexistent /search route. schema-dts types caught serviceType being invalid on Organization (moved to knowsAbout; Service schemas carry serviceType per page). FAQPage schema now emitted from the career-guidance FAQ. JSON-LD output escapes < to prevent script breakout. Org email deliberately withheld until the forwarding alias exists. Co-Authored-By: Claude Fable 5 --- app/(main)/career-guidance/page.tsx | 3 + app/components/breadcrumb.tsx | 12 +- app/components/structured-data.test.tsx | 251 ++++++++++++++++-------- app/components/structured-data.tsx | 119 +---------- app/layout.tsx | 9 +- lib/structured-data.ts | 177 +++++++++++++++++ package-lock.json | 63 ++++-- package.json | 1 + 8 files changed, 412 insertions(+), 223 deletions(-) create mode 100644 lib/structured-data.ts diff --git a/app/(main)/career-guidance/page.tsx b/app/(main)/career-guidance/page.tsx index 30d6f33..45c122c 100644 --- a/app/(main)/career-guidance/page.tsx +++ b/app/(main)/career-guidance/page.tsx @@ -1,5 +1,7 @@ +import { faqSchema } from 'lib/structured-data' import type { Metadata } from 'next' import type React from 'react' +import JsonLd from '../../components/structured-data' export const metadata: Metadata = { title: 'Career Guidance', @@ -100,6 +102,7 @@ const engagementOptions = [ export default function CareerGuidancePage(): React.JSX.Element { return (
+ {/* Hero Section */}

Career Guidance

diff --git a/app/components/breadcrumb.tsx b/app/components/breadcrumb.tsx index b06571d..38b75f5 100644 --- a/app/components/breadcrumb.tsx +++ b/app/components/breadcrumb.tsx @@ -1,7 +1,8 @@ import Link from 'next/link' import type React from 'react' -import StructuredData from './structured-data' +import { breadcrumbSchema } from '../../lib/structured-data' +import JsonLd from './structured-data' interface BreadcrumbItem { name: string @@ -13,16 +14,9 @@ interface BreadcrumbProps { } export default function Breadcrumb({ items }: BreadcrumbProps): React.JSX.Element { - const breadcrumbData = items.map((item, index) => ({ - '@type': 'ListItem', - position: index + 1, - name: item.name, - item: item.href, - })) - return ( <> - + ({ name: item.name, path: item.href })))} />