diff --git a/src/lib/shared.ts b/src/lib/shared.ts index a5dc506..aa93eff 100644 --- a/src/lib/shared.ts +++ b/src/lib/shared.ts @@ -1,9 +1,14 @@ export const appName = 'Multigres'; export const siteUrl = 'https://multigres.com'; +export const defaultOgImage = `${siteUrl}/img/og-image.png`; export const docsRoute = '/docs'; export const blogRoute = '/blog'; export const docsImageRoute = '/og/docs'; +export function absoluteUrl(pathOrUrl: string) { + return new URL(pathOrUrl, siteUrl).href; +} + export const gitConfig = { user: 'multigres', repo: 'multigres', diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index d1077f3..251298d 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -4,6 +4,7 @@ import appCss from '@/styles/app.css?url'; import { RootProvider } from 'fumadocs-ui/provider/tanstack'; import { usePostHog } from '@/hooks/use-posthog'; import { SiteFooter } from '@/components/site-footer'; +import { defaultOgImage, siteUrl } from '@/lib/shared'; export const Route = createRootRoute({ head: () => ({ @@ -37,13 +38,9 @@ export const Route = createRootRoute({ property: 'og:type', content: 'website', }, - { - property: 'og:url', - content: 'https://multigres.com', - }, { property: 'og:image', - content: '/img/og-image.png', + content: defaultOgImage, }, { property: 'og:site_name', @@ -65,7 +62,7 @@ export const Route = createRootRoute({ }, { name: 'twitter:image', - content: '/img/og-image.png', + content: defaultOgImage, }, { name: 'twitter:site', @@ -106,8 +103,10 @@ export const Route = createRootRoute({ href: '/site.webmanifest', }, { - rel: 'canonical', - href: 'https://multigres.com', + rel: 'alternate', + type: 'application/rss+xml', + title: 'Multigres Blog', + href: `${siteUrl}/blog/rss.xml`, }, ], }), diff --git a/src/routes/blog/$slug.tsx b/src/routes/blog/$slug.tsx index 1f5f798..947618c 100644 --- a/src/routes/blog/$slug.tsx +++ b/src/routes/blog/$slug.tsx @@ -9,14 +9,12 @@ import { useMDXComponents } from '@/components/mdx'; import { parseAuthorKeys, resolveAuthors } from '@/lib/authors'; import { MarkdownActions } from '@/components/markdown-actions'; import { blogSource } from '@/lib/blog-source.server'; -import { siteUrl } from '@/lib/shared'; +import { absoluteUrl, defaultOgImage, siteUrl } from '@/lib/shared'; import { docPageHeadingClassName } from '@/lib/typography'; import { useFumadocsLoader } from 'fumadocs-core/source/client'; import { Suspense } from 'react'; import { serializeJsonLd } from '@/lib/json-ld'; -const logoUrl = 'https://multigres.com/img/og-image.png'; - export const Route = createFileRoute('/blog/$slug')({ component: BlogPostPage, loader: async ({ params }) => { @@ -27,7 +25,7 @@ export const Route = createFileRoute('/blog/$slug')({ head: ({ loaderData, params }) => { const title = loaderData?.title ? `${loaderData.title} | Blog | Multigres` : 'Blog | Multigres'; const description = loaderData?.description || 'Notes on Multigres, Postgres, consensus, and distributed databases.'; - const image = loaderData?.image || '/img/og-image.png'; + const image = loaderData?.image ? absoluteUrl(loaderData.image) : defaultOgImage; const canonicalUrl = `https://multigres.com/blog/${params.slug}`; const jsonLd: Record = { @@ -39,11 +37,11 @@ export const Route = createFileRoute('/blog/$slug')({ publisher: { '@type': 'Organization', name: 'Multigres', - logo: { '@type': 'ImageObject', url: logoUrl }, + logo: { '@type': 'ImageObject', url: defaultOgImage }, }, }; - jsonLd['image'] = image.startsWith('http') ? image : `https://multigres.com${image}`; + jsonLd['image'] = image; if (loaderData?.datePublished) jsonLd['datePublished'] = loaderData.datePublished; if (loaderData?.authorName) { jsonLd['author'] = { '@type': 'Person', name: loaderData.authorName }; diff --git a/src/routes/blog/index.tsx b/src/routes/blog/index.tsx index 4c21e77..b697782 100644 --- a/src/routes/blog/index.tsx +++ b/src/routes/blog/index.tsx @@ -10,7 +10,13 @@ import { getBlogPostSummaries, getBlogPosts, } from '@/lib/blog-source.server'; +import { serializeJsonLd } from '@/lib/json-ld'; +import { defaultOgImage, siteUrl } from '@/lib/shared'; + const FEATURED_COUNT = 3; +const title = 'Blog | Multigres'; +const description = 'Notes on Multigres, Postgres, consensus, and distributed databases.'; +const blogUrl = `${siteUrl}/blog`; const loadBlogIndex = createServerFn({ method: 'GET' }).handler(() => { const posts = getBlogPosts(); @@ -27,34 +33,28 @@ export const Route = createFileRoute('/blog/')({ loader: () => loadBlogIndex(), head: () => ({ meta: [ - { - title: 'Blog | Multigres', - }, - { - name: 'description', - content: 'Notes on Multigres, Postgres, consensus, and distributed databases.', - }, - { - property: 'og:title', - content: 'Blog | Multigres', - }, - { - property: 'og:description', - content: 'Notes on Multigres, Postgres, consensus, and distributed databases.', - }, - { - property: 'og:image', - content: '/img/og-image.png', - }, - { - name: 'twitter:image', - content: '/img/og-image.png', - }, + { title }, + { name: 'description', content: description }, + { property: 'og:title', content: title }, + { property: 'og:description', content: description }, + { property: 'og:url', content: blogUrl }, + { property: 'og:image', content: defaultOgImage }, + { name: 'twitter:title', content: title }, + { name: 'twitter:description', content: description }, + { name: 'twitter:image', content: defaultOgImage }, ], - links: [ + links: [{ rel: 'canonical', href: blogUrl }], + scripts: [ { - rel: 'canonical', - href: 'https://multigres.com/blog', + type: 'application/ld+json', + children: serializeJsonLd({ + '@context': 'https://schema.org', + '@type': 'Blog', + name: 'Multigres Blog', + description, + url: blogUrl, + publisher: { '@type': 'Organization', name: 'Multigres' }, + }), }, ], }), @@ -65,6 +65,7 @@ function BlogIndexPage() { return ( +

Multigres Blog

diff --git a/src/routes/blog/series/$seriesSlug.tsx b/src/routes/blog/series/$seriesSlug.tsx index d798d0f..078d8f5 100644 --- a/src/routes/blog/series/$seriesSlug.tsx +++ b/src/routes/blog/series/$seriesSlug.tsx @@ -11,6 +11,7 @@ import { getBlogPostSummaries, getBlogPostsInSeries, } from '@/lib/blog-source.server'; +import { defaultOgImage, siteUrl } from '@/lib/shared'; import { pageHeadingClassName } from '@/lib/typography'; import { cn } from '@/lib/utils'; @@ -48,6 +49,9 @@ export const Route = createFileRoute('/blog/series/$seriesSlug')({ const description = loaderData?.description ?? 'Notes on Multigres, Postgres, consensus, and distributed databases.'; + const pageUrl = loaderData?.seriesSlug + ? `${siteUrl}/blog/series/${loaderData.seriesSlug}` + : undefined; return { meta: [ @@ -55,17 +59,13 @@ export const Route = createFileRoute('/blog/series/$seriesSlug')({ { name: 'description', content: description }, { property: 'og:title', content: title }, { property: 'og:description', content: description }, - { property: 'og:image', content: '/img/og-image.png' }, - { name: 'twitter:image', content: '/img/og-image.png' }, + ...(pageUrl ? [{ property: 'og:url', content: pageUrl }] : []), + { property: 'og:image', content: defaultOgImage }, + { name: 'twitter:title', content: title }, + { name: 'twitter:description', content: description }, + { name: 'twitter:image', content: defaultOgImage }, ], - links: loaderData?.seriesSlug - ? [ - { - rel: 'canonical', - href: `https://multigres.com/blog/series/${loaderData.seriesSlug}`, - }, - ] - : [], + links: pageUrl ? [{ rel: 'canonical', href: pageUrl }] : [], }; }, }); diff --git a/src/routes/docs/$.tsx b/src/routes/docs/$.tsx index 7d63fd1..9fc4824 100644 --- a/src/routes/docs/$.tsx +++ b/src/routes/docs/$.tsx @@ -13,7 +13,7 @@ import { MarkdownActions } from '@/components/markdown-actions'; import { SiteDocsContainer } from '@/components/site-docs-container'; import { baseOptions } from '@/lib/layout.shared'; import { docPageHeadingClassName } from '@/lib/typography'; -import { siteUrl } from '@/lib/shared'; +import { defaultOgImage, siteUrl } from '@/lib/shared'; import { useFumadocsLoader } from 'fumadocs-core/source/client'; import { Suspense } from 'react'; import { useMDXComponents } from '@/components/mdx'; @@ -30,7 +30,7 @@ export const Route = createFileRoute('/docs/$')({ head: ({ loaderData, params }) => { const title = loaderData?.title ? `${loaderData.title} | Multigres Docs` : 'Docs | Multigres'; const description = loaderData?.description || 'Multigres documentation.'; - const canonicalUrl = `https://multigres.com/docs/${params._splat}`; + const canonicalUrl = params._splat ? `${siteUrl}/docs/${params._splat}` : `${siteUrl}/docs`; return { meta: [ @@ -40,10 +40,10 @@ export const Route = createFileRoute('/docs/$')({ { property: 'og:description', content: description }, { property: 'og:type', content: 'article' }, { property: 'og:url', content: canonicalUrl }, - { property: 'og:image', content: '/img/og-image.png' }, + { property: 'og:image', content: defaultOgImage }, { name: 'twitter:title', content: title }, { name: 'twitter:description', content: description }, - { name: 'twitter:image', content: '/img/og-image.png' }, + { name: 'twitter:image', content: defaultOgImage }, ], links: [ { rel: 'canonical', href: canonicalUrl }, diff --git a/src/routes/index.tsx b/src/routes/index.tsx index fbb87ef..2fb4380 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,13 +1,11 @@ import { createFileRoute } from '@tanstack/react-router'; import LandingPage from '@/components/landing-page'; import { serializeJsonLd } from '@/lib/json-ld'; -import { siteUrl } from '@/lib/shared'; +import { defaultOgImage, siteUrl } from '@/lib/shared'; const description = 'A horizontally scalable Postgres architecture supporting multi-tenant, highly available, and globally distributed deployments.'; -const logoUrl = 'https://multigres.com/img/og-image.png'; - const jsonLd = [ { '@context': 'https://schema.org', @@ -15,7 +13,7 @@ const jsonLd = [ '@id': 'https://multigres.com/#organization', name: 'Multigres', url: 'https://multigres.com', - logo: logoUrl, + logo: defaultOgImage, sameAs: ['https://github.com/multigres/multigres', 'https://twitter.com/multigres'], }, { @@ -59,13 +57,17 @@ export const Route = createFileRoute('/')({ property: 'og:description', content: description, }, + { + property: 'og:url', + content: `${siteUrl}/`, + }, { property: 'og:image', - content: '/img/og-image.png', + content: defaultOgImage, }, { name: 'twitter:image', - content: '/img/og-image.png', + content: defaultOgImage, }, ], links: [ diff --git a/src/routes/privacy.tsx b/src/routes/privacy.tsx index 279d10b..ec513d1 100644 --- a/src/routes/privacy.tsx +++ b/src/routes/privacy.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from '@tanstack/react-router'; import { BlogLayout } from '@/components/blog-layout'; +import { siteUrl } from '@/lib/shared'; import { docPageHeadingClassName } from '@/lib/typography'; export const Route = createFileRoute('/privacy')({ @@ -13,6 +14,18 @@ export const Route = createFileRoute('/privacy')({ name: 'description', content: 'Multigres Privacy Policy - Last Modified September 11, 2025', }, + { + property: 'og:title', + content: 'Privacy Policy | Multigres', + }, + { + property: 'og:description', + content: 'Multigres Privacy Policy - Last Modified September 11, 2025', + }, + { + property: 'og:url', + content: `${siteUrl}/privacy`, + }, ], links: [ {