Adding Frameworks
The Adapter Contract
Every adapter is a pure mapping from SEO to a framework's native metadata
type:
import type { SEOAdapter, SEO, TagDescriptor } from "@better-seo/core"
interface MyFrameworkOutput { /* your framework's metadata type */ }
const myAdapter: SEOAdapter<MyFrameworkOutput> = {
id: "my-framework",
toFramework(seo: SEO): MyFrameworkOutput {
return {
title: seo.meta.title,
meta: [
{ name: "description", content: seo.meta.description },
// ... map all fields
],
links: seo.meta.canonical ? [{ rel: "canonical", href: seo.meta.canonical }] : [],
}
},
}
// Register
import { registerAdapter } from "@better-seo/core"
registerAdapter("my-framework", myAdapter)
Adapter Responsibilities
-
Map all fields from
SEOto framework output — don't skip optional fields -
Don't re-serialize JSON-LD — use
serializeJSONLDfrom core - Stay typed — no
anyin adapter output type -
Test with golden fixtures — snapshot
SEOinput → expected output
Example: Remix Adapter
// @better-seo/remix
import type { SEOAdapter, SEO } from "@better-seo/core"
export const remixAdapter: SEOAdapter<{ meta: object[]; links: object[] }> = {
id: "remix",
toFramework(seo: SEO) {
const meta: object[] = [
{ title: seo.meta.title },
{ name: "description", content: seo.meta.description },
...(seo.openGraph?.images?.map(img => ({ property: "og:image", content: img.url })) ?? []),
]
const links: object[] = seo.meta.canonical
? [{ rel: "canonical", href: seo.meta.canonical }]
: []
return { meta, links }
},
}
Then in a Remix route:
import { remixAdapter } from "@better-seo/remix"
import { createSEO } from "@better-seo/core"
export const meta = () => {
const seo = createSEO({ title: "Home" }, config)
return remixAdapter.toFramework(seo).meta
}
export const links = () => {
const seo = createSEO({ title: "Home" }, config)
return remixAdapter.toFramework(seo).links
}
Packaging
Published as @better-seo/<framework> with the framework as a
peerDependency. The adapter package depends on
@better-seo/core only.
{
"name": "@better-seo/remix",
"peerDependencies": {
"@remix-run/node": ">= 2.0",
"@better-seo/core": ">= 0.0.2"
}
}