cartwright
Features

SEO automation

sitemap, robots, structured data, OG images, and AI-assisted category/product copy.

Cartwright ships the search-engine boring stuff so a fork does not have to think about it on day one.

A Google Shopping product feed is also available behind the merchantFeed flag (runtime, default off, ecommerce): a g:-namespaced RSS feed at /feed/google.xml for Google Merchant Center.

sitemap.ts and robots.ts

app/sitemap.ts returns the URL list at build time — landing, all product pages, all category pages, and any pages in the admin Sider CRUD. app/robots.ts returns a permissive robots.txt with the sitemap URL.

Both files read from Prisma at request time (or build time, depending on cache config). New products added through the admin appear in the sitemap automatically on next revalidation.

Structured data (JSON-LD)

The components layer ships components/JsonLd.tsx for product- and category-level structured data. Whether each page actually emits JSON-LD depends on the route — verify in your fork by viewing source on a product page.

A product page (app/[locale]/product/[slug]/page.tsx) emits a Product whose offers is an AggregateOffer when the product has variants, with one Offer per variant carrying the variant's name (the attribute values the picker shows — "Whole beans, 250 g"), sku, price, availability, priceValidUntil, shipping and return policy — so an agent can tell sibling offers apart by more than their sku. The same names and stock states are also printed as plain text in a collapsed "Availability by variant" list under the picker, so a crawler that runs no JavaScript can read each variant's name and stock state as page text, not only inside the JSON-LD. The Product itself carries the product's own article number as sku when one is set, and falls back to its internal id when it is not. Prices are in the shop's base currency only.

Audit your fork's structured-data output before launch, but start from what upstream already emits: Organization on every page from app/layout.tsx, WebSite on the homepage, Product + BreadcrumbList on a product page, CollectionPage + BreadcrumbList on /produkter, BreadcrumbList + an ItemList of its products on a category page (plus FAQPage on a category with FAQ entries, and on a product with FAQ entries when aeoContent is on), Service on a service page (the saas website template), BlogPosting on blog pages when the blog plugin is on, and section-derived JSON-LD on a Visual Builder page. Each route renders its <JsonLd /> around the design pack's override (homepage, plpLayout, pdpLayout), so a custom pack keeps all of it without doing anything. Routes not listed here — /contact, the services index, cart, checkout, the account pages, and /about, /privacy or any other CMS page without a Visual Builder layout — and routes you add yourself under app/[locale]/ ship nothing beyond Organization until they render <JsonLd data={...} /> — the builders live in lib/storefront-jsonld.ts.

OG images

Next.js supports OG image generation via app/og/route.tsx patterns (Satori + next/og ImageResponse). Upstream cartwright does not currently ship a dedicated OG image route — pages fall back to brand.config.ts:metadata.socialImageUrl (a static asset).

For a launch-ready fork, add app/og/route.tsx that reads from the URL slug, looks up the product/category in Prisma, and renders a branded card. The cartwright-app repo (this docs site) ships a working OG route at apps/web/app/og/docs/[...slug]/route.tsx as a reference implementation.

AI-assisted copy generation

Two flows in lib/ai/:

  • lib/ai/category-seo-generator.ts — generates category descriptions, meta-titles, meta-descriptions, and the long-form descriptionLong block. Uses Anthropic by default (despite the file name suggesting otherwise — verify with your fork).
  • lib/ai/product-seo-generator.ts — equivalent for products.

Both are exposed in /admin/ai as buttons next to the relevant form fields. The flow: click "Generate", the admin AI calls the generator with product/category data + brand context, the result lands in the form, you edit, then save.

Always edit AI-generated copy before publishing. The generators are good at structure (length, keyword density, schema-shape) but bland on voice. Two minutes of human editing dramatically improves both customer perception and search ranking.

Metadata pattern

Every page that needs custom metadata exports generateMetadata (or static metadata) from its page.tsx. The pattern: read from Prisma + brand.config.ts, compose title/description with the shop's metadata defaults as fallback, return.

The cartwright-app repo's docs pages are a useful reference — every MDX page picks up its frontmatter title: and description: and Fumadocs surfaces them as OG metadata automatically.

On this page