SEO Utilities
SEO Utilities
Two helpers in src/lib/seo.ts centralize meta tags and structured data so every
public page emits consistent SEO markup without hand-rolling tag arrays.
getSeoMeta(params?)
Returns a TanStack Router head.meta array: title, description, Open Graph
(og:title, og:description, og:url, og:type, optional og:image), and Twitter
card tags. Every field falls back to config.seo defaults (defaultTitle,
defaultDescription, defaultOgImage, twitterHandle) and config.domainName, so a
bare getSeoMeta() is always valid.
import { getSeoMeta } from '@/lib/seo';
export const Route = createFileRoute('/_landing/pricing')({
head: () => ({
meta: getSeoMeta({
title: 'Pricing',
description: 'Simple plans that scale with you.',
url: `https://${config.domainName}/pricing`
})
}),
component: PricingPage
});
Params: title, description, url, image, type ('website' | 'article') ,
all optional. og:image/twitter:image and twitter:site are emitted only when a
value exists, so unset config never produces empty tags.
getJsonLd(params)
Returns a schema.org JSON-LD object for Article, WebSite, Organization, or
SoftwareApplication. Article adds datePublished + author (as a Person);
SoftwareApplication adds the category/OS/offer boilerplate Google expects for app
listings. Render it into a script tag in the route head:
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify(
getJsonLd({ type: 'Article', name: post.title, datePublished: post.date })
)
}
];
Where it's used
All _landing.* routes (index, pricing, features, blog index + $slug, tos,
privacy, changelog) and the compare/* pages. Blog post routes are the reference
for the Article JSON-LD + type: 'article' meta combination
(src/routes/_landing.blog.$slug.tsx).
Adding SEO to a new public page
- Add
head: () => ({ meta: getSeoMeta({ ... }) })to the route definition. - Pass an absolute
url(prefix withhttps://${config.domainName}). - Only pages that should rank need JSON-LD; skip it for utility pages.
- Defaults live in
config.tsunderseo:, change branding there, not per page.