diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8042018ece..c91af8ab74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2169,7 +2169,7 @@ importers: version: 3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@uiw/codemirror-extensions-basic-setup': specifier: ^4.25.1 - version: 4.25.1(@codemirror/autocomplete@6.19.0)(@codemirror/commands@6.9.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) + version: 4.25.1(@codemirror/autocomplete@6.19.0)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) '@uiw/codemirror-theme-github': specifier: ^4.25.1 version: 4.25.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) @@ -2750,9 +2750,15 @@ importers: astro: specifier: ^5.1.1 version: 5.16.9(@types/node@25.0.7)(idb-keyval@6.2.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.53.3)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) + astro-og-canvas: + specifier: ^0.10.0 + version: 0.10.0(astro@5.16.9(@types/node@25.0.7)(idb-keyval@6.2.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.53.3)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) autoprefixer: specifier: ^10.4.22 version: 10.4.22(postcss@8.5.6) + canvaskit-wasm: + specifier: ^0.40.0 + version: 0.40.0 chart.js: specifier: ^4.5.1 version: 4.5.1 @@ -8025,6 +8031,9 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@webgpu/types@0.1.21': + resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==} + '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} @@ -8289,6 +8298,11 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true + astro-og-canvas@0.10.0: + resolution: {integrity: sha512-JkSIYEo2sTJiuht7iVQ2CNT38+rrlvkRpJkqjA58WDFHdymgWktoT9zLkjEkn2u9K2zeZx2GTev16IBJzIoZNQ==} + peerDependencies: + astro: ^5.0.0 || ^6.0.0-alpha + astro@5.16.9: resolution: {integrity: sha512-gJvoZv0v8xCcKBcsxz1ZfXqoJ7sJJcyoKP8bUTjkuD4vDShLe0N26em4LQxitVv/2HLOpldQg67bEHB/qGoxJA==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} @@ -8631,6 +8645,9 @@ packages: canvas-confetti@1.9.3: resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==} + canvaskit-wasm@0.40.0: + resolution: {integrity: sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw==} + capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -9416,6 +9433,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + env-editor@0.4.2: resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} engines: {node: '>=8'} @@ -20092,16 +20113,6 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.2 - '@uiw/codemirror-extensions-basic-setup@4.25.1(@codemirror/autocomplete@6.19.0)(@codemirror/commands@6.9.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2)': - dependencies: - '@codemirror/autocomplete': 6.19.0 - '@codemirror/commands': 6.9.0 - '@codemirror/language': 6.11.3 - '@codemirror/lint': 6.9.0 - '@codemirror/search': 6.5.11 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.2 - '@uiw/codemirror-theme-github@4.25.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2)': dependencies: '@uiw/codemirror-themes': 4.25.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) @@ -20533,6 +20544,8 @@ snapshots: '@xtuc/long': 4.2.2 optional: true + '@webgpu/types@0.1.21': {} + '@xmldom/xmldom@0.7.13': {} '@xmldom/xmldom@0.8.11': {} @@ -20766,6 +20779,13 @@ snapshots: astring@1.9.0: {} + astro-og-canvas@0.10.0(astro@5.16.9(@types/node@25.0.7)(idb-keyval@6.2.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.53.3)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)): + dependencies: + astro: 5.16.9(@types/node@25.0.7)(idb-keyval@6.2.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.53.3)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) + canvaskit-wasm: 0.40.0 + deterministic-object-hash: 2.0.2 + entities: 7.0.0 + astro@5.16.9(@types/node@25.0.7)(idb-keyval@6.2.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(rollup@4.53.3)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1): dependencies: '@astrojs/compiler': 2.13.0 @@ -21281,6 +21301,10 @@ snapshots: canvas-confetti@1.9.3: {} + canvaskit-wasm@0.40.0: + dependencies: + '@webgpu/types': 0.1.21 + capital-case@1.0.4: dependencies: no-case: 3.0.4 @@ -21955,6 +21979,8 @@ snapshots: entities@6.0.1: {} + entities@7.0.0: {} + env-editor@0.4.2: {} env-paths@2.2.1: {} diff --git a/website/package.json b/website/package.json index 253bf364c7..a604e99f21 100644 --- a/website/package.json +++ b/website/package.json @@ -38,7 +38,9 @@ "@types/react-dom": "^19.0.0", "acorn": "^8.15.0", "astro": "^5.1.1", + "astro-og-canvas": "^0.10.0", "autoprefixer": "^10.4.22", + "canvaskit-wasm": "^0.40.0", "chart.js": "^4.5.1", "chartjs-adapter-date-fns": "^3.0.0", "clsx": "^2.1.1", diff --git a/website/src/content/docs/connect/connect-backend/endpoint-env-vars.png b/website/src/content/docs/connect/connect-backend/endpoint-env-vars.png new file mode 100644 index 0000000000..0f21a2aa7f Binary files /dev/null and b/website/src/content/docs/connect/connect-backend/endpoint-env-vars.png differ diff --git a/website/src/layouts/DocsLayout.astro b/website/src/layouts/DocsLayout.astro index 0b3425ec60..51e2189c49 100644 --- a/website/src/layouts/DocsLayout.astro +++ b/website/src/layouts/DocsLayout.astro @@ -6,12 +6,13 @@ interface Props { title: string; description?: string; canonicalUrl?: string; + ogImage?: string; } -const { title, description, canonicalUrl } = Astro.props; +const { title, description, canonicalUrl, ogImage } = Astro.props; --- - +
diff --git a/website/src/layouts/LearnLayout.astro b/website/src/layouts/LearnLayout.astro index a6a21b4a3e..5535978d73 100644 --- a/website/src/layouts/LearnLayout.astro +++ b/website/src/layouts/LearnLayout.astro @@ -8,6 +8,7 @@ interface Props { title: string; description?: string; canonicalUrl?: string; + ogImage?: string; act?: string; subtitle?: string; tableOfContents?: Array<{ title: string; id: string; children: Array<{ title: string; id: string }> }>; @@ -15,13 +16,13 @@ interface Props { scene?: string; } -const { title, description, canonicalUrl, act, subtitle, tableOfContents = [], isIndex = false, scene = '' } = Astro.props; +const { title, description, canonicalUrl, ogImage, act, subtitle, tableOfContents = [], isIndex = false, scene = '' } = Astro.props; // Extract page title from title prop (remove " - Rivet" suffix if present) const pageTitle = title.replace(/ - Rivet$/, ''); --- - +
diff --git a/website/src/lib/og-config.ts b/website/src/lib/og-config.ts new file mode 100644 index 0000000000..ebcff48d51 --- /dev/null +++ b/website/src/lib/og-config.ts @@ -0,0 +1,47 @@ +import type { OGImageOptions } from 'astro-og-canvas'; + +// Common OG image options for all pages +export function getOgImageOptions( + title: string, + description?: string +): OGImageOptions { + return { + title: title, + description: description || undefined, + logo: { + path: './public/icons/android-chrome-512x512.png', + size: [80], + }, + bgGradient: [[15, 15, 15], [25, 25, 30]], + border: { + color: [50, 50, 55], + width: 20, + side: 'inline-start', + }, + padding: 60, + font: { + title: { + color: [240, 240, 240], + size: 72, + lineHeight: 1.2, + }, + description: { + color: [160, 160, 165], + size: 36, + lineHeight: 1.4, + }, + }, + }; +} + +// Get section-specific styling +export function getSectionLabel(section: string): string { + const labels: Record = { + docs: 'Documentation', + blog: 'Blog', + changelog: 'Changelog', + guides: 'Guides', + learn: 'Learn', + }; + return labels[section] || section; +} diff --git a/website/src/pages/blog/[...slug].astro b/website/src/pages/blog/[...slug].astro index a210f90f24..4ebc268849 100644 --- a/website/src/pages/blog/[...slug].astro +++ b/website/src/pages/blog/[...slug].astro @@ -59,6 +59,9 @@ const tableOfContents = headings const isTechnical = category.name === 'Technical' || category.name === 'Guide'; +// Use custom image if available, otherwise fall back to generated OG image +const ogImage = image?.src || `https://rivet.gg/og/blog/${slug}.png`; + // Load other articles const allPosts = await getCollection('posts'); const filteredOtherPosts = allPosts @@ -82,7 +85,7 @@ const otherArticles = await Promise.all( title={title} description={description} canonicalUrl={`https://rivet.gg/blog/${slug}/`} - ogImage={image?.src} + ogImage={ogImage} publishedTime={entry.data.published.toISOString()} keywords={entry.data.keywords} > diff --git a/website/src/pages/changelog/[...slug].astro b/website/src/pages/changelog/[...slug].astro index 24d7c73eb4..3dd3245e9c 100644 --- a/website/src/pages/changelog/[...slug].astro +++ b/website/src/pages/changelog/[...slug].astro @@ -57,6 +57,9 @@ const tableOfContents = headings return acc; }, [] as any[]); +// Use custom image if available, otherwise fall back to generated OG image +const ogImage = image?.src || `https://rivet.gg/og/changelog/${slug}.png`; + // Load other articles const allPosts = await getCollection('posts'); const filteredOtherPosts = allPosts @@ -80,7 +83,7 @@ const otherArticles = await Promise.all( title={title} description={description} canonicalUrl={`https://rivet.gg/changelog/${slug}/`} - ogImage={image?.src} + ogImage={ogImage} publishedTime={entry.data.published.toISOString()} keywords={entry.data.keywords} > diff --git a/website/src/pages/docs/[...slug].astro b/website/src/pages/docs/[...slug].astro index 6d41fe2a31..f8da2e7c41 100644 --- a/website/src/pages/docs/[...slug].astro +++ b/website/src/pages/docs/[...slug].astro @@ -62,9 +62,13 @@ const canonicalUrl = `https://rivet.gg${fullPath}`; // Create markdown path for the dropdown const markdownPath = `docs/${entry.id}`; const componentSourcePath = `docs/${entry.id}.mdx`; + +// Build OG image URL +const ogSlug = entry.id === 'index' ? '' : entry.id.replace(/\/index$/, ''); +const ogImage = `https://rivet.gg/og/docs/${ogSlug}.png`; --- - + diff --git a/website/src/pages/guides/[...slug].astro b/website/src/pages/guides/[...slug].astro index c416738f47..d7fbf48b39 100644 --- a/website/src/pages/guides/[...slug].astro +++ b/website/src/pages/guides/[...slug].astro @@ -46,9 +46,12 @@ const canonicalUrl = `https://rivet.gg${fullPath}`; // Create markdown path for the dropdown const markdownPath = `guides/${entry.id}`; const componentSourcePath = `guides/${entry.id}.mdx`; + +// Build OG image URL +const ogImage = `https://rivet.gg/og/guides/${entry.id}.png`; --- - + diff --git a/website/src/pages/learn/[...slug].astro b/website/src/pages/learn/[...slug].astro index 008a45bc40..3287d07998 100644 --- a/website/src/pages/learn/[...slug].astro +++ b/website/src/pages/learn/[...slug].astro @@ -51,12 +51,17 @@ const tableOfContents = headings } return acc; }, [] as Array<{ title: string; id: string; children: Array<{ title: string; id: string }> }>); + +// Build OG image URL +const ogSlug = entry.id === 'index' ? '' : entry.id.replace(/\/index$/, ''); +const ogImage = `https://rivet.gg/og/learn/${ogSlug}.png`; --- post.data.category !== 'changelog') + .map(async (entry) => { + const { headings } = await render(entry); + const slug = entry.id.replace(/\/page$/, ''); + const title = headings.find((h) => h.depth === 1)?.text || slug; + const category = CATEGORIES[entry.data.category]; + const description = category?.name || 'Blog'; + return [slug, { title, description }]; + }) + ) +); + +export const { getStaticPaths, GET } = await OGImageRoute({ + param: 'slug', + pages: pages, + getImageOptions: (_path, page) => { + return getOgImageOptions(page.title, page.description); + }, +}); diff --git a/website/src/pages/og/changelog/[...slug].ts b/website/src/pages/og/changelog/[...slug].ts new file mode 100644 index 0000000000..d0d3b73b14 --- /dev/null +++ b/website/src/pages/og/changelog/[...slug].ts @@ -0,0 +1,27 @@ +import { getCollection, render } from 'astro:content'; +import { OGImageRoute } from 'astro-og-canvas'; +import { getOgImageOptions } from '@/lib/og-config'; + +const posts = await getCollection('posts'); + +// Build pages object with titles for changelog posts +const pages = Object.fromEntries( + await Promise.all( + posts + .filter((post) => post.data.category === 'changelog') + .map(async (entry) => { + const { headings } = await render(entry); + const slug = entry.id.replace(/\/page$/, ''); + const title = headings.find((h) => h.depth === 1)?.text || slug; + return [slug, { title, description: 'Changelog' }]; + }) + ) +); + +export const { getStaticPaths, GET } = await OGImageRoute({ + param: 'slug', + pages: pages, + getImageOptions: (_path, page) => { + return getOgImageOptions(page.title, page.description); + }, +}); diff --git a/website/src/pages/og/docs/[...slug].ts b/website/src/pages/og/docs/[...slug].ts new file mode 100644 index 0000000000..6b26ce6b7f --- /dev/null +++ b/website/src/pages/og/docs/[...slug].ts @@ -0,0 +1,27 @@ +import { getCollection, render } from 'astro:content'; +import { OGImageRoute } from 'astro-og-canvas'; +import { getOgImageOptions } from '@/lib/og-config'; + +const docs = await getCollection('docs'); + +// Build pages object with titles and descriptions +const pages = Object.fromEntries( + await Promise.all( + docs.map(async (entry) => { + const { headings } = await render(entry); + const title = entry.data.title || headings.find((h) => h.depth === 1)?.text || 'Documentation'; + const description = entry.data.description || ''; + // Handle index files - use empty string for root + const slug = entry.id === 'index' ? '' : entry.id.replace(/\/index$/, ''); + return [slug, { title, description }]; + }) + ) +); + +export const { getStaticPaths, GET } = await OGImageRoute({ + param: 'slug', + pages: pages, + getImageOptions: (_path, page) => { + return getOgImageOptions(page.title, page.description || 'Rivet Documentation'); + }, +}); diff --git a/website/src/pages/og/guides/[...slug].ts b/website/src/pages/og/guides/[...slug].ts new file mode 100644 index 0000000000..7e07913be2 --- /dev/null +++ b/website/src/pages/og/guides/[...slug].ts @@ -0,0 +1,25 @@ +import { getCollection, render } from 'astro:content'; +import { OGImageRoute } from 'astro-og-canvas'; +import { getOgImageOptions } from '@/lib/og-config'; + +const guides = await getCollection('guides'); + +// Build pages object with titles and descriptions +const pages = Object.fromEntries( + await Promise.all( + guides.map(async (entry) => { + const { headings } = await render(entry); + const title = entry.data.title || headings.find((h) => h.depth === 1)?.text || 'Guide'; + const description = entry.data.description || 'Rivet Guide'; + return [entry.id, { title, description }]; + }) + ) +); + +export const { getStaticPaths, GET } = await OGImageRoute({ + param: 'slug', + pages: pages, + getImageOptions: (_path, page) => { + return getOgImageOptions(page.title, page.description); + }, +}); diff --git a/website/src/pages/og/learn/[...slug].ts b/website/src/pages/og/learn/[...slug].ts new file mode 100644 index 0000000000..e825bb2f38 --- /dev/null +++ b/website/src/pages/og/learn/[...slug].ts @@ -0,0 +1,28 @@ +import { getCollection, render } from 'astro:content'; +import { OGImageRoute } from 'astro-og-canvas'; +import { getOgImageOptions } from '@/lib/og-config'; + +const learn = await getCollection('learn'); + +// Build pages object with titles and descriptions +const pages = Object.fromEntries( + await Promise.all( + learn.map(async (entry) => { + const { headings } = await render(entry); + const title = entry.data.title || headings.find((h) => h.depth === 1)?.text || 'Learn'; + const description = entry.data.subtitle || entry.data.description || 'Learn Rivet'; + // Handle index files + const slug = entry.id === 'index' ? '' : entry.id.replace(/\/index$/, ''); + return [slug, { title, description, act: entry.data.act }]; + }) + ) +); + +export const { getStaticPaths, GET } = await OGImageRoute({ + param: 'slug', + pages: pages, + getImageOptions: (_path, page) => { + const description = page.act ? `${page.act} - ${page.description}` : page.description; + return getOgImageOptions(page.title, description); + }, +});