Skip to content

JavaScript

Simple: a minimal payload

const { buildSeoPayload } = require("@easeo/core");

const config = {
  canonicalHost: "example.com",
  publicBaseUrl: "https://example.com",
};

const payload = buildSeoPayload(
  { entityType: "post", title: "Hello World", description: "An example post." },
  "/blog/hello",
  config
);

console.log(payload.renderHtml());

Simple: read and serialize

payload.title;             // resolved title
payload.openGraph.type;    // "article"
payload.schemaJsonLd;      // JSON-LD object

payload.toObject();        // camelCase object (used by JSON.stringify)
payload.toDict();          // canonical snake_case object
payload.toJSONString();    // canonical pretty JSON string

JSON.stringify(payload);   // an object, not a double-encoded string

Simple: overrides

const payload = buildSeoPayload(
  entity,
  "/blog/hello",
  config,
  { metaTitle: "A one-off title", skipTitleTemplate: true, twitterCreator: "@easeo" }
);

Simple: factories

const { fromBlogPost, fromProduct, fromFaq } = require("@easeo/core");

const post = fromBlogPost({ title: "Hello", bodyHtml: "<p>Body</p>", author: "Jane" });
const product = fromProduct({ name: "Widget", sku: "W-1", price: 9.99 });
const faq = fromFaq({ questions: [{ question: "Q?", answer: "A." }] });

Complex: a full config module

// seo.config.mjs
export const config = {
  canonicalHost: "shop.example.com",
  publicBaseUrl: "https://shop.example.com",
  siteName: "Example Shop",
  titleTemplate: "{title} - Example Shop",
  trailingSlash: "never",
  allowedQueryParams: ["page", "q"],
  defaultOgImage: "https://shop.example.com/assets/og-image.png",
  publisherName: "Example Shop",
  locale: "en_US",
  localeAlternate: ["es_UY", "pt_BR"],
  twitterSite: "@exampleshop",
  searchUrlTemplate: "https://shop.example.com/search?q={search_term_string}",
};

Complex: caching headers from the payload

const payload = buildSeoPayload(entity, route, config);

return new Response(html, {
  headers: {
    ETag: payload.etag(),
    "Cache-Control": "public, max-age=300",
  },
});

Complex: hooks for a site-wide schema

const { HookRegistry, buildSeoPayload } = require("@easeo/core");

const hooks = new HookRegistry();

hooks.register("post_process", (payload, entity, config) => {
  const org = {
    "@context": "https://schema.org",
    "@type": "Organization",
    name: config.publisherName ?? "Example",
    url: config.publicBaseUrl,
  };
  const existing = payload.schema_jsonld;
  if (Array.isArray(existing)) {
    payload.schema_jsonld = [org, ...existing];
  } else if (existing) {
    payload.schema_jsonld = [org, existing];
  } else {
    payload.schema_jsonld = org;
  }
  return payload;
});

const configWithHooks = { ...config, hooks };

Complex: a registered schema generator

const { SchemaRegistry } = require("@easeo/core");

const registry = new SchemaRegistry();

registry.register("Article", (entity, config, canonical, title, description, ogImage) => ({
  "@context": "https://schema.org",
  "@type": "PodcastEpisode",
  name: title,
  url: canonical,
  description,
}));

const configWithRegistry = { ...config, schemaRegistry: registry };

Complex: TypeScript

import {
  buildSeoPayload,
  HookRegistry,
  SchemaRegistry,
  fromProduct,
  type SEOPayload,
  type SEOConfig,
} from "@easeo/core";

const config: SEOConfig = {
  canonicalHost: "shop.example.com",
  publicBaseUrl: "https://shop.example.com",
  siteName: "Example Shop",
};

const payload: SEOPayload = buildSeoPayload(
  fromProduct({ name: "Widget", sku: "W-1", price: 9.99 }),
  "/products/widget",
  config
);

const data = payload.toObject();
console.log(data.openGraph.title, payload.hash());

Complex: validation warnings

Set emitWarnings to have the build log issues through console.warn:

const payload = buildSeoPayload(entity, route, {
  ...config,
  emitWarnings: true,
});
// [EASEO108] OG image is missing; set default_og_image or an entity image (https://...)

Complex: determinism check in tests

const assert = require("node:assert");

const a = buildSeoPayload(entity, "/blog/hello", config);
const b = buildSeoPayload(entity, "/blog/hello", config);

assert(a.equals(b));
assert.equal(a.hash(), b.hash());