diff --git a/website/index.html b/website/index.html
index cfb16a2..cb633ea 100644
--- a/website/index.html
+++ b/website/index.html
@@ -7,6 +7,26 @@
name="description"
content="Voily turns raw voice into refined text, right where your cursor is."
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Voily - Just speak. We'll handle the rest.
diff --git a/website/public/og-image.png b/website/public/og-image.png
new file mode 100644
index 0000000..0b6d333
Binary files /dev/null and b/website/public/og-image.png differ
diff --git a/website/src/components/SeoMetadata.test.ts b/website/src/components/SeoMetadata.test.ts
new file mode 100644
index 0000000..f41b561
--- /dev/null
+++ b/website/src/components/SeoMetadata.test.ts
@@ -0,0 +1,50 @@
+import { existsSync, readFileSync } from 'node:fs'
+import { describe, expect, test } from 'bun:test'
+
+const indexHtml = readFileSync(new URL('../../index.html', import.meta.url), 'utf8')
+const ogImageUrl = new URL('../../public/og-image.png', import.meta.url)
+const siteUrl = 'https://voily.pages.dev/'
+const socialImageUrl = `${siteUrl}og-image.png`
+
+function metaContent(property: string) {
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ const pattern = new RegExp(
+ ``,
+ )
+
+ return indexHtml.match(pattern)?.[1]
+}
+
+function linkHref(rel: string) {
+ const escapedRel = rel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ const pattern = new RegExp(``)
+
+ return indexHtml.match(pattern)?.[1]
+}
+
+function readPngDimensions(buffer: Buffer) {
+ return {
+ width: buffer.readUInt32BE(16),
+ height: buffer.readUInt32BE(20),
+ }
+}
+
+describe('SEO metadata', () => {
+ test('exposes a social preview image for Open Graph and Twitter cards', () => {
+ expect(linkHref('canonical')).toBe(siteUrl)
+ expect(metaContent('og:url')).toBe(siteUrl)
+ expect(metaContent('og:image')).toBe(socialImageUrl)
+ expect(metaContent('og:image:secure_url')).toBe(socialImageUrl)
+ expect(metaContent('twitter:image')).toBe(socialImageUrl)
+ expect(metaContent('og:image:width')).toBe('1200')
+ expect(metaContent('og:image:height')).toBe('630')
+ })
+
+ test('ships the social preview image as a 1200x630 PNG source asset', () => {
+ expect(existsSync(ogImageUrl)).toBe(true)
+
+ const buffer = readFileSync(ogImageUrl)
+ expect(buffer.subarray(1, 4).toString('ascii')).toBe('PNG')
+ expect(readPngDimensions(buffer)).toEqual({ width: 1200, height: 630 })
+ })
+})