Skip to content

Commit e130127

Browse files
mostlyerrorclaude
andcommitted
Refactor SEO implementation for maintainability and type safety
Eliminates code duplication across 13+ files by centralizing SEO constants, creating reusable schema builders, and establishing single sources of truth. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a812bd1 commit e130127

17 files changed

Lines changed: 337 additions & 43 deletions

File tree

app/blog/[slug]/page.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import RichText from '@/components/RichText'
66
import CTA from '@/components/CTA'
77
import { getAllBlogPosts, getBlogPostBySlug } from '@/lib/sanity.client'
88
import { urlFor } from '@/lib/sanity.image'
9+
import { SEO } from '@/lib/seo.constants'
10+
import { buildBlogPostingSchema } from '@/lib/schema-builders'
11+
import { JsonLd } from '@/components/JsonLd'
912

1013
interface BlogPostPageProps {
1114
params: Promise<{
@@ -60,7 +63,7 @@ export async function generateMetadata({ params }: BlogPostPageProps): Promise<M
6063
openGraph: {
6164
title: post.title,
6265
description: metaDescription,
63-
url: `https://goodrobotco.com/blog/${post.slug}`,
66+
url: `${SEO.baseUrl}/blog/${post.slug}`,
6467
type: 'article',
6568
publishedTime: post.publishedAt,
6669
authors: [post.author],
@@ -104,6 +107,19 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
104107

105108
{/* Blog Post Content */}
106109
<article className="py-12 bg-cream">
110+
<JsonLd
111+
data={buildBlogPostingSchema({
112+
...post,
113+
excerpt: post.excerpt || '',
114+
featuredImage: post.featuredImage?.asset
115+
? {
116+
asset: {
117+
url: urlFor(post.featuredImage.asset).width(1200).height(630).url(),
118+
},
119+
}
120+
: undefined,
121+
})}
122+
/>
107123
<div className="max-w-4xl mx-auto px-6">
108124
{/* Meta info */}
109125
<div className="flex items-center gap-4 text-sm text-charcoal-light mb-8">

app/case-studies/llt/page.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,45 @@ import type { Metadata } from "next";
22
import Link from "next/link";
33
import HeroSimple from "@/components/HeroSimple";
44
import CTA from "@/components/CTA";
5+
import { SEO } from "@/lib/seo.constants";
6+
import { buildBreadcrumbSchema } from "@/lib/schema-builders";
7+
import { JsonLd } from "@/components/JsonLd";
58

69
export const metadata: Metadata = {
710
title: "Case Study: Let's Learn Together | Good Robot Co.",
811
description: "How we helped a growing tutoring agency streamline operations, evaluate business software, and implement the right platform for long-term growth.",
12+
openGraph: {
13+
title: "Let's Learn Together: Operations & Technology Advisory | Good Robot Co.",
14+
description: "From manual spreadsheets to scalable systems—saving hours weekly and setting up a tutoring agency for growth.",
15+
url: `${SEO.baseUrl}/case-studies/llt`,
16+
type: 'article',
17+
images: [
18+
{
19+
url: '/og-image.png',
20+
width: 1200,
21+
height: 630,
22+
alt: "Good Robot Co. - Technology That Works For Your Business",
23+
},
24+
],
25+
},
26+
twitter: {
27+
card: 'summary_large_image',
28+
title: "Let's Learn Together: Operations & Technology Advisory",
29+
description: "From manual spreadsheets to scalable systems—saving hours weekly and setting up a tutoring agency for growth.",
30+
images: ['/og-image.png'],
31+
},
932
};
1033

1134
export default function LLTCaseStudy() {
1235
return (
1336
<>
37+
<JsonLd
38+
data={buildBreadcrumbSchema([
39+
{ name: 'Home', url: SEO.baseUrl },
40+
{ name: 'Case Studies', url: `${SEO.baseUrl}/case-studies` },
41+
{ name: "Let's Learn Together", url: `${SEO.baseUrl}/case-studies/llt` },
42+
])}
43+
/>
1444
<HeroSimple
1545
title="Let's Learn Together: Operations & Technology Advisory"
1646
subtitle="From manual spreadsheets to scalable systems—saving hours weekly and setting up a tutoring agency for growth"

app/case-studies/mayday/page.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,45 @@ import type { Metadata } from "next";
22
import Image from "next/image";
33
import HeroSimple from "@/components/HeroSimple";
44
import CTA from "@/components/CTA";
5+
import { SEO } from "@/lib/seo.constants";
6+
import { buildBreadcrumbSchema } from "@/lib/schema-builders";
7+
import { JsonLd } from "@/components/JsonLd";
58

69
export const metadata: Metadata = {
710
title: "Case Study: Mayday | Good Robot Co.",
811
description: "How we built an intelligent lead generation system that automatically discovers and qualifies web development opportunities.",
12+
openGraph: {
13+
title: "Mayday: Intelligent Lead Generation | Good Robot Co.",
14+
description: "Automating the hunt for web development clients—from 15 hours/week of manual work to zero.",
15+
url: `${SEO.baseUrl}/case-studies/mayday`,
16+
type: 'article',
17+
images: [
18+
{
19+
url: '/case-studies/mayday/dashboard.png',
20+
width: 1600,
21+
height: 1200,
22+
alt: 'Mayday lead generation dashboard showing automated business scanning',
23+
},
24+
],
25+
},
26+
twitter: {
27+
card: 'summary_large_image',
28+
title: "Mayday: Intelligent Lead Generation",
29+
description: "Automating the hunt for web development clients—from 15 hours/week of manual work to zero.",
30+
images: ['/case-studies/mayday/dashboard.png'],
31+
},
932
};
1033

1134
export default function MaydayCaseStudy() {
1235
return (
1336
<>
37+
<JsonLd
38+
data={buildBreadcrumbSchema([
39+
{ name: 'Home', url: SEO.baseUrl },
40+
{ name: 'Case Studies', url: `${SEO.baseUrl}/case-studies` },
41+
{ name: 'Mayday', url: `${SEO.baseUrl}/case-studies/mayday` },
42+
])}
43+
/>
1444
<HeroSimple
1545
title="Mayday: Intelligent Lead Generation"
1646
subtitle="Automating the hunt for web development clients—from 15 hours/week of manual work to zero"

app/case-studies/swapp/page.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,45 @@ import Link from "next/link";
33
import Image from "next/image";
44
import HeroSimple from "@/components/HeroSimple";
55
import CTA from "@/components/CTA";
6+
import { SEO } from "@/lib/seo.constants";
7+
import { buildBreadcrumbSchema } from "@/lib/schema-builders";
8+
import { JsonLd } from "@/components/JsonLd";
69

710
export const metadata: Metadata = {
811
title: "Case Study: SWAPP | Good Robot Co.",
912
description: "How we built a rapid-response system that reduced intake time from 11 minutes to 2.5 minutes and prevented 8,300+ nights of unsheltered homelessness.",
13+
openGraph: {
14+
title: "SWAPP: Severe Weather Emergency Response | Good Robot Co.",
15+
description: "From weekend prototype to production—reducing intake time by 80% during Colorado's coldest winter.",
16+
url: `${SEO.baseUrl}/case-studies/swapp`,
17+
type: 'article',
18+
images: [
19+
{
20+
url: '/case-studies/swapp/intake-session.jpg',
21+
width: 1600,
22+
height: 1200,
23+
alt: 'Caseworker conducting digital intake during severe weather',
24+
},
25+
],
26+
},
27+
twitter: {
28+
card: 'summary_large_image',
29+
title: "SWAPP: Severe Weather Emergency Response",
30+
description: "Reducing intake time by 80% during Colorado's coldest winter.",
31+
images: ['/case-studies/swapp/intake-session.jpg'],
32+
},
1033
};
1134

1235
export default function SwappCaseStudy() {
1336
return (
1437
<>
38+
<JsonLd
39+
data={buildBreadcrumbSchema([
40+
{ name: 'Home', url: SEO.baseUrl },
41+
{ name: 'Case Studies', url: `${SEO.baseUrl}/case-studies` },
42+
{ name: 'SWAPP', url: `${SEO.baseUrl}/case-studies/swapp` },
43+
])}
44+
/>
1545
<HeroSimple
1646
title="SWAPP: Severe Weather Emergency Response"
1747
subtitle="From weekend prototype to production—reducing intake time by 80% during Colorado's coldest winter"

app/layout.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import Nav from '@/components/Nav'
55
import Footer from '@/components/Footer'
66
import { Analytics } from '@vercel/analytics/next'
77
import { SpeedInsights } from '@vercel/speed-insights/next'
8+
import { SEO } from '@/lib/seo.constants'
9+
import { buildOrganizationSchema } from '@/lib/schema-builders'
10+
import { JsonLd } from '@/components/JsonLd'
811

912
const dmSans = DM_Sans({
1013
subsets: ['latin'],
@@ -24,29 +27,29 @@ const fraunces = Fraunces({
2427

2528
export const metadata: Metadata = {
2629
title: 'Good Robot Co. | Technology That Works For Your Business',
27-
description: 'Technology consulting for small and mid-size businesses. Honest guidance, practical solutions, and the right tech approach for your needs.',
28-
metadataBase: new URL('https://goodrobotco.com'),
30+
description: SEO.organization.description,
31+
metadataBase: new URL(SEO.baseUrl),
2932
openGraph: {
3033
title: 'Good Robot Co. | Technology That Works For Your Business',
31-
description: 'Technology consulting for small and mid-size businesses. Honest guidance, practical solutions, and the right tech approach for your needs.',
32-
url: 'https://goodrobotco.com',
33-
siteName: 'Good Robot Co.',
34+
description: SEO.organization.description,
35+
url: SEO.baseUrl,
36+
siteName: SEO.siteName,
3437
locale: 'en_US',
3538
type: 'website',
3639
images: [
3740
{
38-
url: '/og-image.png',
39-
width: 1200,
40-
height: 630,
41-
alt: 'Good Robot Co. - Technology That Works For Your Business',
41+
url: SEO.images.og.url,
42+
width: SEO.images.og.width,
43+
height: SEO.images.og.height,
44+
alt: SEO.images.og.alt,
4245
},
4346
],
4447
},
4548
twitter: {
4649
card: 'summary_large_image',
4750
title: 'Good Robot Co. | Technology That Works For Your Business',
48-
description: 'Technology consulting for small and mid-size businesses. Honest guidance, practical solutions, and the right tech approach for your needs.',
49-
images: ['/og-image.png'],
51+
description: SEO.organization.description,
52+
images: [SEO.images.og.url],
5053
},
5154
}
5255

@@ -58,6 +61,7 @@ export default function RootLayout({
5861
return (
5962
<html lang="en" className={`${dmSans.variable} ${fraunces.variable}`}>
6063
<body className="bg-cream text-charcoal text-[17px] leading-relaxed overflow-x-hidden font-body">
64+
<JsonLd data={buildOrganizationSchema()} />
6165
<Nav />
6266
<main>{children}</main>
6367
<Footer />

app/page.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
import Image from 'next/image'
22
import Link from 'next/link'
33
import CTA from '@/components/CTA'
4-
import { HEADSHOT_SRC } from '@/components/constants'
4+
import { JsonLd } from '@/components/JsonLd'
55

66
export default function Home() {
7+
const faqSchema = {
8+
'@context': 'https://schema.org',
9+
'@type': 'FAQPage',
10+
mainEntity: [
11+
{
12+
'@type': 'Question',
13+
name: 'What if a developer ghosted us and our website is broken?',
14+
acceptedAnswer: {
15+
'@type': 'Answer',
16+
text: 'Good Robot Co. specializes in tech rescue services, helping businesses recover from abandoned projects and fixing broken websites.',
17+
},
18+
},
19+
{
20+
'@type': 'Question',
21+
name: 'Can AI actually help my business?',
22+
acceptedAnswer: {
23+
'@type': 'Answer',
24+
text: 'Good Robot Co. provides practical AI integration services, cutting through the hype to implement AI solutions that are actually useful and maintainable for your business.',
25+
},
26+
},
27+
{
28+
'@type': 'Question',
29+
name: 'Why am I being quoted $50K for simple software?',
30+
acceptedAnswer: {
31+
'@type': 'Answer',
32+
text: 'Good Robot Co. offers right-sized solutions that match the problem to your actual needs and budget, with fair pricing and no overselling.',
33+
},
34+
},
35+
{
36+
'@type': 'Question',
37+
name: 'How can I automate manual processes?',
38+
acceptedAnswer: {
39+
'@type': 'Answer',
40+
text: 'Good Robot Co. specializes in process automation using tools like Zapier, Make, or custom scripts to eliminate repetitive tasks.',
41+
},
42+
},
43+
],
44+
}
45+
746
return (
847
<>
948
{/* Hero */}
@@ -80,6 +119,7 @@ export default function Home() {
80119

81120
{/* Sound Familiar + Philosophy Combined */}
82121
<section className="py-20 bg-white">
122+
<JsonLd data={faqSchema} />
83123
<div className="max-w-4xl mx-auto px-6">
84124
<h2 className="text-2xl md:text-3xl font-semibold mb-8">Sound familiar?</h2>
85125

@@ -113,9 +153,11 @@ export default function Home() {
113153
<p>I&apos;m transparent about where I am: building my portfolio with real client projects. That means accessible pricing and personalized attention.</p>
114154
</div>
115155
<div className="flex items-center gap-3">
116-
<img
117-
src={HEADSHOT_SRC}
118-
alt="Ben Poon"
156+
<Image
157+
src="/ben-headshot.jpg"
158+
alt="Ben Poon"
159+
width={64}
160+
height={64}
119161
className="w-16 h-16 rounded-full object-cover ring-2 ring-cream"
120162
/>
121163
<div>

app/sitemap.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,63 @@
11
import { MetadataRoute } from 'next'
2+
import { getAllBlogPosts } from '@/lib/sanity.client'
3+
import { SEO } from '@/lib/seo.constants'
4+
import { CASE_STUDIES } from '@/lib/case-studies.config'
25

36
export const dynamic = 'force-static'
47

5-
export default function sitemap(): MetadataRoute.Sitemap {
6-
const baseUrl = 'https://goodrobotco.com'
8+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
9+
// Fetch blog posts from Sanity at build time
10+
const blogPosts = await getAllBlogPosts()
711

8-
return [
12+
// Static pages
13+
const staticPages: MetadataRoute.Sitemap = [
914
{
10-
url: baseUrl,
15+
url: SEO.baseUrl,
1116
lastModified: new Date(),
1217
changeFrequency: 'monthly',
1318
priority: 1,
1419
},
1520
{
16-
url: `${baseUrl}/ai-for-business`,
21+
url: `${SEO.baseUrl}/ai-for-business`,
1722
lastModified: new Date(),
1823
changeFrequency: 'monthly',
1924
priority: 0.8,
2025
},
2126
{
22-
url: `${baseUrl}/pricing`,
27+
url: `${SEO.baseUrl}/pricing`,
2328
lastModified: new Date(),
2429
changeFrequency: 'monthly',
2530
priority: 0.8,
2631
},
2732
{
28-
url: `${baseUrl}/case-studies`,
33+
url: `${SEO.baseUrl}/case-studies`,
2934
lastModified: new Date(),
3035
changeFrequency: 'monthly',
3136
priority: 0.9,
3237
},
3338
{
34-
url: `${baseUrl}/case-studies/swapp`,
35-
lastModified: new Date(),
36-
changeFrequency: 'monthly',
37-
priority: 0.8,
38-
},
39-
{
40-
url: `${baseUrl}/case-studies/mayday`,
41-
lastModified: new Date(),
42-
changeFrequency: 'monthly',
43-
priority: 0.8,
44-
},
45-
{
46-
url: `${baseUrl}/case-studies/llt`,
47-
lastModified: new Date(),
48-
changeFrequency: 'monthly',
49-
priority: 0.8,
50-
},
51-
{
52-
url: `${baseUrl}/blog`,
39+
url: `${SEO.baseUrl}/blog`,
5340
lastModified: new Date(),
5441
changeFrequency: 'weekly',
5542
priority: 0.6,
5643
},
5744
]
45+
46+
// Add dynamic case study pages
47+
const caseStudyPages: MetadataRoute.Sitemap = CASE_STUDIES.map((study) => ({
48+
url: `${SEO.baseUrl}/case-studies/${study.slug}`,
49+
lastModified: new Date(),
50+
changeFrequency: 'monthly' as const,
51+
priority: study.priority,
52+
}))
53+
54+
// Add dynamic blog post pages
55+
const blogPostPages: MetadataRoute.Sitemap = blogPosts.map((post) => ({
56+
url: `${SEO.baseUrl}/blog/${post.slug}`,
57+
lastModified: new Date(post.publishedAt),
58+
changeFrequency: 'monthly',
59+
priority: 0.7,
60+
}))
61+
62+
return [...staticPages, ...caseStudyPages, ...blogPostPages]
5863
}

0 commit comments

Comments
 (0)