:::contentbit
How-to guides

Build a programmatic SEO workflow

Define a page family, brief a writer or agent, inspect the result, and enforce the contract in CI.

This guide takes one page family from configuration to a repeatable publishing gate. By the end, you can brief a page before its Markdown file exists, give the same plan to a writer or agent, and catch structural or linking gaps before the page ships.

The working loop is:

  1. Connect Contentbit to the project.
  2. Define the page family.
  3. Plan one page.
  4. Generate its writing brief.
  5. Inspect the finished draft.
  6. Run the publishing gate.

Before you start

If Contentbit is not installed yet, scaffold the project with SEO support:

npx contentbit@latest init --seo

This creates contentbit.config.ts and a starter contentbit.seo.config.ts. You can use the same steps below in an existing installation by adding those files yourself.

1. Connect the project

Put the shared command settings in contentbit.config.ts:

import { defineContentConfig } from '@contentbit/core'

export default defineContentConfig({
  content: 'content/**/*.md',
  registry: './blocks/registry.ts',
  seo: './contentbit.seo.config.ts',
})

Contentbit now knows which files to scan, which block registry to load, and where the SEO contracts live. The commands in the rest of this guide do not need repeated glob, registry, or SEO config flags.

Run a first scan to confirm the project loads:

contentbit doctor

It is normal to see SEO findings at this point. The next steps define what the pages should satisfy.

2. Define the page family

A page type describes the structure shared by every page in a family. This example defines glossary pages:

// contentbit.seo.config.ts
import { defineSeoConfig } from '@contentbit/core'

export default defineSeoConfig({
  pageTypes: {
    glossary: {
      requiredFrontmatter: ['name', 'summary', 'seoKeywords.primary'],
      requiredSections: [
        { id: 'definition', headings: ['What it means', 'Definition'] },
        { id: 'why-it-matters', headings: ['Why it matters'] },
      ],
      recommendedBlocks: ['quick-ref', 'faq'],
      minOutgoingLinks: 2,
    },
  },
  pageDefaults: [
    {
      pathPrefix: 'content/glossary/',
      type: 'glossary',
      intent: 'definition',
    },
  ],
})

pageDefaults applies the glossary contract to existing files in that folder. Use explicit page plans when a page does not exist yet or needs its own target keywords and links.

For every available contract field, see SEO briefs and contracts.

3. Plan one page

Add the first planned page to the same SEO config:

// Add this beside pageTypes and pageDefaults.
pages: {
  'content/glossary/recipe-import/en.md': {
    type: 'glossary',
    key: 'glossary/recipe-import',
    slug: 'recipe-import',
    title: 'Recipe import',
    intent: 'definition',
    keywords: {
      primary: 'recipe import',
      secondary: ['recipe importer', 'save recipes from websites'],
    },
    linksTo: ['glossary/recipe-manager', 'guides/save-recipes-from-websites'],
  },
},

The file does not need to exist yet. Contentbit can now resolve the page by its stable key or slug and combine its target facts with the glossary contract.

Check that the plan resolves:

contentbit brief glossary/recipe-import

You should see the page target, required sections, recommended blocks, required links, related pages, and acceptance checks.

4. Brief the writer

Use the Markdown brief for a human or an editorial ticket:

contentbit brief glossary/recipe-import

Use JSON when a coding agent or script will consume it:

contentbit brief glossary/recipe-import --json

Give the brief to the writer as the contract for this page. A short agent instruction is enough:

Write the planned glossary/recipe-import page from this Contentbit brief.
Preserve the target facts, include every required section, satisfy the required
links, and run contentbit doctor before you finish.

The brief defines the page structure. The live registry explains the syntax for any structured blocks:

contentbit instructions --audience llm

This separation keeps the page plan focused while ensuring the agent never has to guess block names, props, or body formats.

5. Inspect the draft

Once the writer creates the Markdown file, open Studio:

contentbit studio

Open the page and check four views:

  1. Preview for the rendered page.
  2. Brief for the original target and acceptance checks.
  3. Findings for structural, block, and SEO issues.
  4. Links for outgoing links and backlinks.

The live frontmatter becomes the source for page facts such as title, slug, intent, keywords, and linksTo. The page plan continues to supply anything the file does not override.

6. Run the publishing gate

Run Doctor after the draft exists:

contentbit doctor

The report combines normal content health checks with the SEO contract. It can flag:

  • missing required frontmatter
  • missing required sections
  • missing required or recommended blocks
  • missing required links
  • too few outgoing links or backlinks
  • invalid block markup, thin sections, and missing image alt text

Fix required findings, then run the strict gate:

contentbit doctor --strict-seo

The command exits with a failure when required SEO findings remain, so the same check works for a local handoff, an agent repair loop, and CI.

7. Add the gate to CI

Keep the package scripts short because contentbit.config.ts already carries the project settings:

{
  "scripts": {
    "content:doctor": "contentbit doctor --strict-seo",
    "studio": "contentbit studio"
  }
}

Run content:doctor in the same CI job that builds the site. A page that no longer satisfies its family contract will fail before publishing.

Multilingual page families

For translated slugs, configure one link strategy for every command:

// contentbit.config.ts
export default defineContentConfig({
  content: 'content/**/*.md',
  registry: './blocks/registry.ts',
  seo: './contentbit.seo.config.ts',
  links: {
    resolve: 'same-locale-key',
    localeField: 'locale',
    keyField: 'key',
    slugField: 'slug',
  },
})

Translated pages share a stable key and keep their own locale-specific slug:

---
key: glossary/recipe-import
locale: fr
slug: import-recettes
linksTo:
  - glossary/recipe-manager
seoKeywords:
  primary: import de recettes
---

Contentbit keeps each localized SEO page separate while resolving links within the right locale. seoKeywords is normalized as keyword data when keywords is absent.

If something does not resolve

  • No SEO config found: confirm that contentbit.config.ts points to contentbit.seo.config.ts.
  • Brief target not found: try the page's stable key, then its slug.
  • Page has no type: add a matching pageDefaults prefix or set type in an explicit page plan.
  • Links resolve in the wrong locale: put the shared resolver under links in contentbit.config.ts so every command uses the same strategy.

On this page