diff --git a/blogs/AGENTS.md b/blogs/AGENTS.md index 74ce3c5d..d0992242 100644 --- a/blogs/AGENTS.md +++ b/blogs/AGENTS.md @@ -12,13 +12,15 @@ To keep a post as a draft (written but not published), move it into the `drafts/ --- title: "Tuning Reth for payments: how we hit 21,200 TPS" excerpt: "One or two sentences shown on the index and as the post's lede." +metaTitle: "Tuning Reth for payments: how we hit 21,200 TPS" # optional SEO title +metaDescription: "Custom search and social preview copy." # optional SEO description date: 2026-06-02 category: technical # network-upgrades | events | technical | case-studies featured: true # optional — pins the post to the hero card on /blog --- ``` -`category` must be one of the four slugs above (the build fails loudly otherwise). At most one post should be `featured`; if none is, the newest post takes the hero card. +`category` must be one of the four slugs above (the build fails loudly otherwise). `metaTitle` and `metaDescription` override the browser, OpenGraph, Twitter, and JSON-LD metadata. Blog OpenGraph images are generated dynamically from the post title with the `DEV BLOG` label. At most one post should be `featured`; if none is, the newest post takes the hero card. ## Body diff --git a/blogs/t6.md b/blogs/t6.md index 186efc9b..c7cc5c22 100644 --- a/blogs/t6.md +++ b/blogs/t6.md @@ -1,6 +1,8 @@ --- title: "T6 network upgrade: Receive policies, admin access keys, and more" excerpt: "The T6 network upgrade adds two new account-level controls to Tempo: receive policies, which let an account decide which tokens and senders it accepts, and admin access keys, which let an account delegate key management without using the root key." +metaTitle: "T6 network upgrade: Receive policies and admin access keys" +metaDescription: "Learn how Tempo's T6 network upgrade adds account-level receive policies, held-transfer recovery, and admin access keys for safer key management." date: 2026-06-23 category: network-upgrades --- diff --git a/src/marketing/blogPlugin.ts b/src/marketing/blogPlugin.ts index dd176379..2da13d58 100644 --- a/src/marketing/blogPlugin.ts +++ b/src/marketing/blogPlugin.ts @@ -9,8 +9,9 @@ import { unified } from 'unified' import type { Plugin } from 'vite' // Blog content lives as dev-managed markdown files in /blogs at the repo root. -// Frontmatter schema: title, excerpt, date (YYYY-MM-DD), category, and an -// optional `featured: true` to pin a post to the hero card. +// Frontmatter schema: title, excerpt, date (YYYY-MM-DD), category, optional +// metaTitle/metaDescription SEO overrides, and an optional `featured: true` to +// pin a post to the hero card. // // Markdown is rendered to HTML here, in Node, at build/dev time, so the heavy // markdown + Shiki toolchain never ships to the client bundle. The rendered @@ -31,6 +32,8 @@ export type RenderedPost = { slug: string title: string excerpt: string + metaTitle: string + metaDescription: string date: string category: string featured: boolean @@ -92,6 +95,8 @@ async function renderPost(filename: string): Promise { slug, title: data.title, excerpt: data.excerpt, + metaTitle: data.metaTitle || `${data.title} — Tempo Developers`, + metaDescription: data.metaDescription || data.excerpt, date: data.date, category: data.category, featured: data.featured === 'true', diff --git a/src/marketing/next.d.ts b/src/marketing/next.d.ts index 1e22b46a..e9b399b3 100644 --- a/src/marketing/next.d.ts +++ b/src/marketing/next.d.ts @@ -7,6 +7,8 @@ declare module 'virtual:blog-posts' { slug: string title: string excerpt: string + metaTitle: string + metaDescription: string date: string category: string featured: boolean diff --git a/src/marketing/seo.ts b/src/marketing/seo.ts index 11eceae2..3dcd925d 100644 --- a/src/marketing/seo.ts +++ b/src/marketing/seo.ts @@ -9,6 +9,8 @@ export type PostSeo = { slug: string title: string // raw post title (no " — Tempo Developers" suffix) excerpt: string + metaTitle: string + metaDescription: string date: string // YYYY-MM-DD category: CategorySlug } @@ -33,12 +35,24 @@ export function absoluteUrl(base: string, pathname: string): string { export function ogImageUrl( base: string, - params: { title: string; description: string; section: string }, + params: { title: string; description: string; section: string; eyebrow?: string }, ): string { const query = new URLSearchParams(params).toString() return absoluteUrl(base, `/api/og?${query}`) } +export function blogOgImageUrl( + base: string, + post: Pick, +): string { + return ogImageUrl(base, { + title: post.title, + description: post.metaDescription, + section: 'BLOG', + eyebrow: 'DEV BLOG', + }) +} + // schema.org BlogPosting payload for a post, serialized for a //