SEO content

Collections are a good store for SEO content that an external engine generates: landing-page copy, meta tags, sitemap entries, redirect rules. Your site reads it back and renders everything. Floggy stores and versions the content; it does no SEO work itself.

What Floggy does not do

Floggy does not crawl your site, score content, generate meta tags, or serve a sitemap or robots.txt for your domain. Your site renders every one of those. A collection is just where the structured input lives, versioned and promotable. The SEO intelligence, whatever generates the content, lives in your own systems.

Generated content lands as a draft

An engine that writes to a collection produces drafts by default. Nothing it generates reaches a crawler until a production label points at it, which is usually what you want: a batch of machine-written meta tags gets a review gate for free.

When the batch is trusted, publish as you write. Pass label: "production" on create (@floggy/cms 0.2.1 or newer) or push with --publish:

floggy collections push seo-meta ./content/seo-meta --publish

Watch the update case. Regenerating metadata for a page that is already published writes a new version but leaves production on the old one, so the old tags keep serving. --publish moves the label onto the new version, which is what makes a regeneration actually take effect.

Reserved-convention collections

These are recipes, not features. Floggy treats every collection the same. Naming one seo-meta gives it no special behavior; the convention is only so your code and your agents agree on where things live.

Collection Holds Consumed by
seo-meta per-route title/description/OG fields generateMetadata
sitemap-entries one entry per URL, with changefreq/priority app/sitemap.ts
redirects { from, to, permanent } rows your middleware or config

Sitemap from entries

The SDK ships sitemapFromEntries to build sitemap XML from a list of entries.

// app/sitemap.ts
import { createClient, sitemapFromEntries } from "@floggy/cms";

const floggy = createClient({
  project: "projecta",
  key: process.env.FLOGGY_API_KEY,
});

export default async function sitemap() {
  const { entries } = await floggy.collections.entries.list("sitemap-entries", {
    perPage: 50,
  });
  return sitemapFromEntries(entries, {
    baseUrl: "https://blog.example.com",
    path: "slug", // which data field holds the path
  });
}

sitemapFromEntries(entries, { baseUrl, path }) returns the sitemap structure (URL list) for your framework's sitemap output. Page through the collection if you have more than one page of entries.

Metadata from entries

Read a seo-meta entry in generateMetadata and shape it into your framework's metadata object.

// app/[slug]/page.tsx
import { createClient } from "@floggy/cms";

const floggy = createClient({
  project: "projecta",
  key: process.env.FLOGGY_API_KEY,
});

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  // Default GET serves the production label.
  const { entries } = await floggy.collections.entries.list("seo-meta", {
    slug,
    full: 1,
  });
  const meta = entries[0]?.data;
  if (!meta) return {};

  return {
    title: meta.title,
    description: meta.description,
    openGraph: { title: meta.ogTitle ?? meta.title, images: meta.ogImage ? [meta.ogImage] : [] },
    alternates: { canonical: meta.canonical },
  };
}

Redirects

Store redirect rows in a redirects collection and read them where your framework resolves redirects (middleware, config, an edge function). Floggy holds the rules; your app applies them.

const { entries } = await floggy.collections.entries.list("redirects", { perPage: 50 });
const rules = entries.map((e) => e.data); // { from, to, permanent }

The point

Your domain serves its own sitemap, robots, metadata, and redirects. Collections just let an external engine version and promote the content behind them, with drafts and rollback, while your site stays the single thing that renders to crawlers.

Next