Skip to content

Commit 0f33933

Browse files
gmoonclaude
andcommitted
Add OG image, Twitter cards, publisher logo, and Cache-Control headers
Fixes three SEO critical issues blocking rich result eligibility: - Add og:image and twitter:card meta tags to pre-rendered HTML and client-side page components (HomePage, BlogPage, GettingStartedPage) - Add publisher.logo (ImageObject) and image to BlogPosting schema, logo and sameAs to Organization schema - Split S3 deploy into per-resource-type Cache-Control policies: hashed assets (immutable/1yr), static assets (1 day), HTML (5min) - Add branded OG image (1200x630) and publisher logo (200x200) SVGs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3f5c121 commit 0f33933

7 files changed

Lines changed: 165 additions & 1 deletion

File tree

.github/workflows/deploy.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,25 @@ jobs:
4040
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
4141
AWS_DEFAULT_REGION: us-east-1
4242
run: |
43-
aws s3 sync dist/ s3://forkzero-web-prod --delete
43+
# Hashed assets: cache forever (filename changes on content change)
44+
aws s3 sync dist/assets/ s3://forkzero-web-prod/assets/ \
45+
--cache-control "public, max-age=31536000, immutable" \
46+
--delete
47+
48+
# Static assets (images, SVGs, robots.txt, sitemap): cache 1 day
49+
aws s3 sync dist/ s3://forkzero-web-prod/ \
50+
--exclude "assets/*" \
51+
--exclude "*.html" \
52+
--cache-control "public, max-age=86400" \
53+
--delete
54+
55+
# HTML pages: short cache, revalidate often
56+
aws s3 sync dist/ s3://forkzero-web-prod/ \
57+
--exclude "assets/*" \
58+
--include "*.html" \
59+
--cache-control "public, max-age=300, s-maxage=86400"
60+
61+
# Invalidate CloudFront
4462
aws cloudfront create-invalidation \
4563
--distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
4664
--paths "/*"

public/logo.svg

Lines changed: 8 additions & 0 deletions
Loading

public/og-default.svg

Lines changed: 23 additions & 0 deletions
Loading

scripts/prerender.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ for (const route of routes) {
7373
`<meta property="og:description" content="${escapeAttr(route.description)}" />`,
7474
`<meta property="og:type" content="${ogType}" />`,
7575
`<meta property="og:url" content="${route.canonical}" />`,
76+
`<meta property="og:image" content="https://forkzero.ai/og-default.svg" />`,
77+
`<meta property="og:site_name" content="Forkzero" />`,
78+
`<meta name="twitter:card" content="summary_large_image" />`,
79+
`<meta name="twitter:title" content="${escapeAttr(route.title)}" />`,
80+
`<meta name="twitter:description" content="${escapeAttr(route.description)}" />`,
81+
`<meta name="twitter:image" content="https://forkzero.ai/og-default.svg" />`,
7682
`<link rel="canonical" href="${route.canonical}" />`,
7783
].join('\n ')
7884

@@ -202,6 +208,11 @@ function buildJsonLd(route: RouteMeta): string {
202208
'@type': 'Organization',
203209
name: 'Forkzero',
204210
url: 'https://forkzero.ai',
211+
logo: {
212+
'@type': 'ImageObject',
213+
url: 'https://forkzero.ai/logo.svg',
214+
},
215+
sameAs: ['https://github.com/forkzero'],
205216
description:
206217
'Forkzero builds developer tools that connect research, strategy, requirements, and implementation into a traversable knowledge graph.',
207218
})
@@ -255,6 +266,7 @@ function buildJsonLd(route: RouteMeta): string {
255266
headline: post.title,
256267
description: post.excerpt,
257268
datePublished: post.date,
269+
image: 'https://forkzero.ai/og-default.svg',
258270
author: {
259271
'@type': 'Person',
260272
name: post.author.name,
@@ -263,6 +275,10 @@ function buildJsonLd(route: RouteMeta): string {
263275
'@type': 'Organization',
264276
name: 'Forkzero',
265277
url: 'https://forkzero.ai',
278+
logo: {
279+
'@type': 'ImageObject',
280+
url: 'https://forkzero.ai/logo.svg',
281+
},
266282
},
267283
mainEntityOfPage: route.canonical,
268284
})

src/pages/BlogPage.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,15 @@ function BlogListing() {
731731
)
732732
setOgTag('og:type', 'website')
733733
setOgTag('og:url', 'https://forkzero.ai/blog')
734+
setOgTag('og:image', 'https://forkzero.ai/og-default.svg')
735+
setOgTag('og:site_name', 'Forkzero')
736+
setMetaTag('twitter:card', 'summary_large_image')
737+
setMetaTag('twitter:title', 'Blog \u2014 Forkzero')
738+
setMetaTag(
739+
'twitter:description',
740+
'Technical writing on knowledge coordination, context engineering, and AI-first developer tooling.',
741+
)
742+
setMetaTag('twitter:image', 'https://forkzero.ai/og-default.svg')
734743
setCanonical('https://forkzero.ai/blog')
735744
}, [])
736745

@@ -770,6 +779,12 @@ function BlogPostView({ post }: { post: BlogPost }) {
770779
setOgTag('og:description', post.excerpt)
771780
setOgTag('og:type', 'article')
772781
setOgTag('og:url', `https://forkzero.ai/blog/${post.slug}`)
782+
setOgTag('og:image', 'https://forkzero.ai/og-default.svg')
783+
setOgTag('og:site_name', 'Forkzero')
784+
setMetaTag('twitter:card', 'summary_large_image')
785+
setMetaTag('twitter:title', post.title)
786+
setMetaTag('twitter:description', post.excerpt)
787+
setMetaTag('twitter:image', 'https://forkzero.ai/og-default.svg')
773788
setCanonical(`https://forkzero.ai/blog/${post.slug}`)
774789
}, [post])
775790

src/pages/GettingStartedPage.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,46 @@ const NAV_LINKS = [
429429
export function GettingStartedPage() {
430430
useEffect(() => {
431431
document.title = 'Get Started with Lattice — Forkzero'
432+
433+
const setMetaTag = (name: string, content: string) => {
434+
let el = document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement | null
435+
if (!el) {
436+
el = document.createElement('meta')
437+
el.name = name
438+
document.head.appendChild(el)
439+
}
440+
el.content = content
441+
}
442+
const setOgTag = (property: string, content: string) => {
443+
let el = document.querySelector(`meta[property="${property}"]`) as HTMLMetaElement | null
444+
if (!el) {
445+
el = document.createElement('meta')
446+
el.setAttribute('property', property)
447+
document.head.appendChild(el)
448+
}
449+
el.content = content
450+
}
451+
452+
setMetaTag(
453+
'description',
454+
'Install Lattice and start building a knowledge-coordinated codebase in under five minutes.',
455+
)
456+
setOgTag('og:title', 'Get Started with Lattice — Forkzero')
457+
setOgTag(
458+
'og:description',
459+
'Install Lattice and start building a knowledge-coordinated codebase in under five minutes.',
460+
)
461+
setOgTag('og:type', 'website')
462+
setOgTag('og:url', 'https://forkzero.ai/getting-started')
463+
setOgTag('og:image', 'https://forkzero.ai/og-default.svg')
464+
setOgTag('og:site_name', 'Forkzero')
465+
setMetaTag('twitter:card', 'summary_large_image')
466+
setMetaTag('twitter:title', 'Get Started with Lattice — Forkzero')
467+
setMetaTag(
468+
'twitter:description',
469+
'Install Lattice and start building a knowledge-coordinated codebase in under five minutes.',
470+
)
471+
setMetaTag('twitter:image', 'https://forkzero.ai/og-default.svg')
432472
}, [])
433473

434474
return (

src/pages/HomePage.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,50 @@ const NAV_LINKS = [
814814
]
815815

816816
export function HomePage() {
817+
useEffect(() => {
818+
document.title = 'Forkzero — Knowledge Coordination for AI-Native Teams'
819+
820+
const setMetaTag = (name: string, content: string) => {
821+
let el = document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement | null
822+
if (!el) {
823+
el = document.createElement('meta')
824+
el.name = name
825+
document.head.appendChild(el)
826+
}
827+
el.content = content
828+
}
829+
const setOgTag = (property: string, content: string) => {
830+
let el = document.querySelector(`meta[property="${property}"]`) as HTMLMetaElement | null
831+
if (!el) {
832+
el = document.createElement('meta')
833+
el.setAttribute('property', property)
834+
document.head.appendChild(el)
835+
}
836+
el.content = content
837+
}
838+
839+
setMetaTag(
840+
'description',
841+
'Forkzero builds developer tools that connect research, strategy, requirements, and implementation into a traversable knowledge graph.',
842+
)
843+
setOgTag('og:title', 'Forkzero — Knowledge Coordination for AI-Native Teams')
844+
setOgTag(
845+
'og:description',
846+
'Forkzero builds developer tools that connect research, strategy, requirements, and implementation into a traversable knowledge graph.',
847+
)
848+
setOgTag('og:type', 'website')
849+
setOgTag('og:url', 'https://forkzero.ai/')
850+
setOgTag('og:image', 'https://forkzero.ai/og-default.svg')
851+
setOgTag('og:site_name', 'Forkzero')
852+
setMetaTag('twitter:card', 'summary_large_image')
853+
setMetaTag('twitter:title', 'Forkzero — Knowledge Coordination for AI-Native Teams')
854+
setMetaTag(
855+
'twitter:description',
856+
'Forkzero builds developer tools that connect research, strategy, requirements, and implementation into a traversable knowledge graph.',
857+
)
858+
setMetaTag('twitter:image', 'https://forkzero.ai/og-default.svg')
859+
}, [])
860+
817861
return (
818862
<>
819863
<Header navLinks={NAV_LINKS} githubUrl={GITHUB_ORG_URL} />

0 commit comments

Comments
 (0)