Astro¶
@easeo/astro injects the site config at build time and can emit an SEO
contract when the build finishes.
Simple: configure the integration¶
// astro.config.mjs
import { defineConfig } from "astro/config";
import easeo from "@easeo/astro";
export default defineConfig({
integrations: [
easeo({
config: {
canonicalHost: "example.com",
publicBaseUrl: "https://example.com",
siteName: "Example",
},
}),
],
});
Then read the injected config in a layout through the Vite define
__EASEO_CONFIG__.
Simple: one page¶
---
// src/pages/about.astro
import { buildSeoPayload } from "@easeo/core";
const config = { canonicalHost: "example.com", publicBaseUrl: "https://example.com" };
const payload = buildSeoPayload(
{ entityType: "page", title: "About", description: "About us." },
"/about",
config
);
---
<html>
<head>
<Fragment set:html={payload.renderHtml()} />
</head>
<body><h1>About</h1></body>
</html>
Complex: content collections¶
Build the entity from a content collection entry, and pass overrides for the fields the collection does not store.
---
// src/pages/blog/[slug].astro
import { getCollection } from "astro:content";
import { buildSeoPayload } from "@easeo/core";
import type { SEOConfig } from "@easeo/core";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }));
}
const { post } = Astro.props;
const config: SEOConfig = {
canonicalHost: "blog.example.com",
publicBaseUrl: "https://blog.example.com",
siteName: "Example Blog",
titleTemplate: "{title} - Example Blog",
defaultOgImage: "https://blog.example.com/assets/og-image.png",
};
const payload = buildSeoPayload(
{
entityType: "post",
title: post.data.title,
description: post.data.description,
image: post.data.cover,
authorName: post.data.author,
publishedAt: post.data.publishedAt,
breadcrumbs: [
{ name: "Home", url: "/" },
{ name: "Blog", url: "/blog" },
],
},
`/blog/${post.slug}`,
config
);
---
<html>
<head>
<Fragment set:html={payload.renderHtml()} />
</head>
<body><article set:html={post.body} /></body>
</html>
Complex: emit a contract at build time¶
// astro.config.mjs
import { defineConfig } from "astro/config";
import easeo from "@easeo/astro";
export default defineConfig({
integrations: [
easeo({
config: {
canonicalHost: "blog.example.com",
publicBaseUrl: "https://blog.example.com",
siteName: "Example Blog",
},
contract: {
canonicalHost: "blog.example.com",
scheme: "https",
defaults: { ogRequired: true, schemaRequired: true },
rules: [{ match: "/blog/*", expect: { schemaTypes: ["Article"] } }],
},
}),
],
});
The contract is written to dist/.easeo/contract.json. Commit it and gate the
deployment on a diff. See Contracts in CI.
Notes¶
- The contract is resolved with
fileURLToPath, so output paths with spaces work. - The integration works for static and SSR Astro projects. The metadata itself
is generated by
buildSeoPayload, so it works the same in both.