@prmichaelsen/acp-visualizer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# OG Metadata Pattern
|
|
2
|
+
|
|
3
|
+
**Category**: Code
|
|
4
|
+
**Applicable To**: TanStack Start routes that need social media link previews (Open Graph + Twitter Cards)
|
|
5
|
+
**Status**: Stable
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Every route that can be shared on social media (iMessage, Slack, Discord, Twitter/X, Facebook) needs server-rendered Open Graph and Twitter Card meta tags in the HTML `<head>`. TanStack Start provides a `head()` function on route definitions that runs during SSR — this is the only mechanism that works for social crawlers, which do not execute JavaScript.
|
|
12
|
+
|
|
13
|
+
This pattern documents how to add OG metadata to routes, covering static pages, dynamic content pages with SSR loader data, and the global defaults that all routes inherit.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## When to Use This Pattern
|
|
18
|
+
|
|
19
|
+
**Use this pattern when:**
|
|
20
|
+
- Adding a new route that users might share (link previews)
|
|
21
|
+
- Adding SSR data to a route that already has OG tags
|
|
22
|
+
- Creating shareable content pages (memories, profiles, invite links)
|
|
23
|
+
- Debugging why a shared link shows the wrong preview
|
|
24
|
+
|
|
25
|
+
**Don't use this pattern when:**
|
|
26
|
+
- The route is behind authentication with no public preview needed
|
|
27
|
+
- The route is an API endpoint (`/api/*`)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Core Principles
|
|
32
|
+
|
|
33
|
+
1. **SSR-Only**: Social crawlers don't run JS — `head()` runs server-side so tags appear in the initial HTML response
|
|
34
|
+
2. **Route-Level Overrides**: Child route `head()` tags override parent (root) defaults by property
|
|
35
|
+
3. **Graceful Fallback**: If loader data fails, return generic site-level OG tags — never render a page with no OG
|
|
36
|
+
4. **Auth After Meta**: For shareable links (invite codes), check auth in the component, not `beforeLoad`, so crawlers see meta tags without being redirected
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Implementation
|
|
41
|
+
|
|
42
|
+
### Global Defaults (Root Route)
|
|
43
|
+
|
|
44
|
+
`src/routes/__root.tsx` defines site-wide fallback OG tags via `createRootRoute({ head() })`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
export const Route = createRootRoute({
|
|
48
|
+
head: () => ({
|
|
49
|
+
meta: [
|
|
50
|
+
{ charSet: 'utf-8' },
|
|
51
|
+
{ name: 'viewport', content: 'width=device-width, initial-scale=1, ...' },
|
|
52
|
+
{ title: 'agentbase — social AI with memory, ghosts, and shared spaces' },
|
|
53
|
+
{ name: 'description', content: 'A social AI platform with persistent memory...' },
|
|
54
|
+
{ property: 'og:type', content: 'website' },
|
|
55
|
+
{ property: 'og:site_name', content: 'agentbase' },
|
|
56
|
+
{ property: 'og:title', content: 'agentbase — social AI with memory, ghosts, and shared spaces' },
|
|
57
|
+
{ property: 'og:description', content: 'A social AI platform with persistent memory...' },
|
|
58
|
+
{ property: 'og:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
59
|
+
{ name: 'twitter:card', content: 'summary_large_image' },
|
|
60
|
+
{ name: 'twitter:title', content: 'agentbase — ...' },
|
|
61
|
+
{ name: 'twitter:description', content: '...' },
|
|
62
|
+
{ name: 'twitter:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
63
|
+
],
|
|
64
|
+
}),
|
|
65
|
+
component: RootComponent,
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The root also renders `<HeadContent />` inside `<head>` to inject these tags into the HTML.
|
|
70
|
+
|
|
71
|
+
### Static Page OG
|
|
72
|
+
|
|
73
|
+
For pages with fixed content (terms, privacy, homepage, invite links):
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
export const Route = createFileRoute('/terms')({
|
|
77
|
+
head: () => ({
|
|
78
|
+
meta: [
|
|
79
|
+
{ title: 'Terms of Service — agentbase' },
|
|
80
|
+
{ name: 'description', content: 'Terms of service for agentbase.' },
|
|
81
|
+
{ property: 'og:title', content: 'Terms of Service — agentbase' },
|
|
82
|
+
{ property: 'og:description', content: 'Terms of service for agentbase.' },
|
|
83
|
+
{ property: 'og:type', content: 'website' },
|
|
84
|
+
{ property: 'og:site_name', content: 'agentbase' },
|
|
85
|
+
{ property: 'og:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
86
|
+
{ name: 'twitter:card', content: 'summary' },
|
|
87
|
+
{ name: 'twitter:title', content: 'Terms of Service — agentbase' },
|
|
88
|
+
{ name: 'twitter:description', content: 'Terms of service for agentbase.' },
|
|
89
|
+
{ name: 'twitter:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
90
|
+
],
|
|
91
|
+
}),
|
|
92
|
+
component: TermsPage,
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Dynamic Content OG (SSR Loader)
|
|
97
|
+
|
|
98
|
+
For pages where OG tags depend on data (memory detail, profile), use `createServerFn` + `loader` + `head(loaderData)`:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// 1. Server function fetches data during SSR
|
|
102
|
+
const fetchMemoryDetail = createServerFn({ method: 'GET' })
|
|
103
|
+
.inputValidator((data: { memoryId: string }) => data)
|
|
104
|
+
.handler(async ({ data }) => {
|
|
105
|
+
// Fetch memory from backend
|
|
106
|
+
const memory = await svc.memories.get(userId, data.memoryId)
|
|
107
|
+
return { memory }
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// 2. Route loader calls server function
|
|
111
|
+
export const Route = createFileRoute('/memories/$memoryId')({
|
|
112
|
+
loader: async ({ params }) => {
|
|
113
|
+
const memoryData = await fetchMemoryDetail({ data: { memoryId: params.memoryId } })
|
|
114
|
+
return { memoryData }
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// 3. head() receives loader data and generates dynamic tags
|
|
118
|
+
head: (({ loaderData }: any) => {
|
|
119
|
+
const memory = loaderData?.memoryData?.memory
|
|
120
|
+
if (!memory) {
|
|
121
|
+
return {
|
|
122
|
+
meta: [
|
|
123
|
+
{ title: 'Memory — agentbase' },
|
|
124
|
+
{ property: 'og:title', content: 'Memory — agentbase' },
|
|
125
|
+
{ property: 'og:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
126
|
+
// ... fallback tags
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const rawTitle = memory.title || memory.content?.split('\n')[0] || 'Memory'
|
|
132
|
+
const title = rawTitle.length > 60 ? rawTitle.substring(0, 57) + '...' : rawTitle
|
|
133
|
+
const description = (memory.content ?? '').substring(0, 200)
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
meta: [
|
|
137
|
+
{ title: `${title} — agentbase` },
|
|
138
|
+
{ name: 'description', content: description },
|
|
139
|
+
{ property: 'og:title', content: title },
|
|
140
|
+
{ property: 'og:description', content: description },
|
|
141
|
+
{ property: 'og:type', content: 'article' },
|
|
142
|
+
{ property: 'og:site_name', content: 'agentbase' },
|
|
143
|
+
{ property: 'og:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
144
|
+
{ name: 'twitter:card', content: 'summary' },
|
|
145
|
+
{ name: 'twitter:title', content: title },
|
|
146
|
+
{ name: 'twitter:description', content: description },
|
|
147
|
+
{ name: 'twitter:image', content: 'https://agentbase.me/icon-512x512.png' },
|
|
148
|
+
],
|
|
149
|
+
}
|
|
150
|
+
}) as any,
|
|
151
|
+
|
|
152
|
+
component: MemoryDetailPage,
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Dynamic OG Image (Profile Pages)
|
|
157
|
+
|
|
158
|
+
Profile pages use the `/api/storage/image` proxy to serve user-uploaded profile pictures as OG images:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
head: (({ loaderData }: any) => {
|
|
162
|
+
const profile = loaderData?.profileForHead
|
|
163
|
+
if (profile && profile.is_published) {
|
|
164
|
+
const image = profile.profile_picture_path
|
|
165
|
+
? `https://agentbase.me/api/storage/image?path=${encodeURIComponent(profile.profile_picture_path)}&context=profile`
|
|
166
|
+
: 'https://agentbase.me/icon-512x512.png'
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
meta: [
|
|
170
|
+
{ property: 'og:image', content: image },
|
|
171
|
+
{ name: 'twitter:image', content: image },
|
|
172
|
+
// ... other tags
|
|
173
|
+
],
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// fallback for unpublished profiles
|
|
177
|
+
}) as any
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Shareable Invite Links (Auth After Meta)
|
|
181
|
+
|
|
182
|
+
For pages like `/friend-links/$code`, `/group-links/$code`, `/dm-links/$code` — use **static OG** and defer auth to the component:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
export const Route = createFileRoute('/friend-links/$code')({
|
|
186
|
+
// Static head — no loader needed, crawlers see this immediately
|
|
187
|
+
head: () => ({
|
|
188
|
+
meta: [
|
|
189
|
+
{ title: 'Friend Invite — agentbase' },
|
|
190
|
+
{ property: 'og:title', content: 'Friend Invite — agentbase' },
|
|
191
|
+
{ property: 'og:description', content: "You've been invited to connect on agentbase." },
|
|
192
|
+
// ...
|
|
193
|
+
],
|
|
194
|
+
}),
|
|
195
|
+
// Auth check happens HERE, not in beforeLoad — crawlers never hit this
|
|
196
|
+
component: FriendLinkPage,
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Required Meta Tags Checklist
|
|
203
|
+
|
|
204
|
+
Every route with OG metadata should include all of these:
|
|
205
|
+
|
|
206
|
+
| Tag | Property | Notes |
|
|
207
|
+
|-----|----------|-------|
|
|
208
|
+
| `<title>` | `{ title: '...' }` | Browser tab title |
|
|
209
|
+
| `description` | `{ name: 'description', content: '...' }` | Search engine description |
|
|
210
|
+
| `og:title` | `{ property: 'og:title', content: '...' }` | Social card title |
|
|
211
|
+
| `og:description` | `{ property: 'og:description', content: '...' }` | Social card description |
|
|
212
|
+
| `og:type` | `{ property: 'og:type', content: '...' }` | `website` or `article` |
|
|
213
|
+
| `og:site_name` | `{ property: 'og:site_name', content: 'agentbase' }` | Always `agentbase` |
|
|
214
|
+
| `og:image` | `{ property: 'og:image', content: '...' }` | Absolute URL, min 200x200 |
|
|
215
|
+
| `twitter:card` | `{ name: 'twitter:card', content: '...' }` | `summary` or `summary_large_image` |
|
|
216
|
+
| `twitter:title` | `{ name: 'twitter:title', content: '...' }` | Same as `og:title` |
|
|
217
|
+
| `twitter:description` | `{ name: 'twitter:description', content: '...' }` | Same as `og:description` |
|
|
218
|
+
| `twitter:image` | `{ name: 'twitter:image', content: '...' }` | Same as `og:image` |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## OG Type Reference
|
|
223
|
+
|
|
224
|
+
| Route Type | `og:type` | `twitter:card` |
|
|
225
|
+
|------------|-----------|----------------|
|
|
226
|
+
| Homepage / static pages | `website` | `summary_large_image` |
|
|
227
|
+
| Memory detail | `article` | `summary` |
|
|
228
|
+
| Profile page | `profile` | `summary` |
|
|
229
|
+
| Space / group pages | `website` | `summary` |
|
|
230
|
+
| Invite links | `website` | `summary` |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Anti-Patterns
|
|
235
|
+
|
|
236
|
+
### Auth in beforeLoad for shareable pages
|
|
237
|
+
|
|
238
|
+
**Description**: Redirecting unauthenticated users in `beforeLoad` before `head()` runs.
|
|
239
|
+
|
|
240
|
+
**Why it's bad**: Social crawlers are unauthenticated — they get redirected to login and never see OG tags.
|
|
241
|
+
|
|
242
|
+
**Instead**: Check auth in the component, not `beforeLoad`. The `head()` function runs regardless.
|
|
243
|
+
|
|
244
|
+
### Missing fallback when loader fails
|
|
245
|
+
|
|
246
|
+
**Description**: Not handling the case where `loaderData` is undefined in `head()`.
|
|
247
|
+
|
|
248
|
+
**Why it's bad**: A failed fetch produces a page with no OG tags — shared links show a blank preview.
|
|
249
|
+
|
|
250
|
+
**Instead**: Always return fallback OG tags when data is missing.
|
|
251
|
+
|
|
252
|
+
### Relative OG image URLs
|
|
253
|
+
|
|
254
|
+
**Description**: Using `/icon-512x512.png` instead of `https://agentbase.me/icon-512x512.png`.
|
|
255
|
+
|
|
256
|
+
**Why it's bad**: Some social crawlers don't resolve relative URLs.
|
|
257
|
+
|
|
258
|
+
**Instead**: Always use absolute URLs for `og:image` and `twitter:image`.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Implementation References
|
|
263
|
+
|
|
264
|
+
- **Root defaults**: `src/routes/__root.tsx` (lines 56-108)
|
|
265
|
+
- **Memory detail (dynamic)**: `src/routes/memories/$memoryId.tsx` (lines 131-160)
|
|
266
|
+
- **Profile (dynamic image)**: `src/routes/profile/$userId.tsx` (lines 90-127)
|
|
267
|
+
- **Spaces (conditional image)**: `src/routes/spaces/$spaceId.tsx` (lines 41-53)
|
|
268
|
+
- **Invite links (static, auth-deferred)**: `src/routes/friend-links/$code.tsx` (lines 15-29)
|
|
269
|
+
- **Image proxy**: `src/routes/api/storage/image.tsx`
|
|
270
|
+
- **Static assets**: `public/icon-512x512.png`, `public/the_void_02-512.png`
|
|
271
|
+
|
|
272
|
+
## Related Patterns
|
|
273
|
+
|
|
274
|
+
- **[SSR Preload](./ssr-preload.md)**: Server-side data loading that feeds into `head()` for dynamic OG
|
|
275
|
+
- **[Firebase Auth](./tanstack-cloudflare.firebase-auth.md)**: Auth patterns — important to understand which routes gate auth in `beforeLoad` vs component
|
|
276
|
+
- **[Firebase Storage](./tanstack-cloudflare.firebase-storage.md)**: Image proxy endpoint used for dynamic OG images
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Checklist for Implementation
|
|
281
|
+
|
|
282
|
+
- [ ] `head()` function defined on route with all required meta tags
|
|
283
|
+
- [ ] `og:image` and `twitter:image` use absolute URLs
|
|
284
|
+
- [ ] Dynamic pages have server function + loader feeding `head(loaderData)`
|
|
285
|
+
- [ ] `head()` handles missing/failed loader data with fallback tags
|
|
286
|
+
- [ ] Shareable pages defer auth to component (not `beforeLoad`)
|
|
287
|
+
- [ ] Title truncated to 60 chars max, description to 200 chars max
|
|
288
|
+
- [ ] `og:type` is appropriate (`website` vs `article` vs `profile`)
|
|
289
|
+
- [ ] `og:site_name` is `agentbase`
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
**Status**: Stable
|
|
294
|
+
**Recommendation**: Follow this pattern for every new route. Verify OG tags by pasting the URL into the Twitter Card Validator or Facebook Sharing Debugger.
|
|
295
|
+
**Last Updated**: 2026-03-14
|
|
296
|
+
**Contributors**: Community
|