Skip to content

feat(cms): one reusable SEO heading pattern for sections - #215

Merged
dogfrogfog merged 3 commits into
mainfrom
feat/cms-section-heading-pattern
Oct 28, 2026
Merged

dogfrogfog merged 3 commits into
mainfrom
feat/cms-section-heading-pattern

Conversation

@dogfrogfog

Copy link
Copy Markdown
Member

Recreates #162 on top of current main: the old base predated 20261012_110154_remove_author_required_flag_for_comment, so the migration is regenerated after it.

What

Every section heading is now built the same way, once:

  • Field: sectionHeadingField(defaults?) returns one heading group (eyebrow, title, description) with the generated type SectionHeadingFields.
  • Component: <SectionHeading {...block.heading} /> renders <hgroup><p>eyebrow</p><h2>title</h2><p>description</p></hgroup>. Alignment, size and eyebrow tone are props, so the visual variants (left/center, with or without description) stay in code and the content shape stays the same. as="h1" is used only for the hero and the blog index.
  • Extractors: sectionHeadingText() (search text) and sectionHeadingContent(heading, level) (SEO content) read the group the same way everywhere.

Used by Content, FAQ, CTA Band, Carousel, Chart, Cards Grid, Testimonials, Newsletter (which also gains a description) and Hero, plus the post FAQ/CTA, the CTA banner, related posts and the blog header. The old SectionHeader component, the prepareSectionHeaderProps adapter and sectionHeaderFields are removed.

Hero: the rich text is replaced by the plain heading description. The default hero rich text contained an extra Heading node, so heroes shipped with a second heading inside them. The unused "hero" rich text preset is removed.

Migration 20261028_134213_section_heading_group

  • Only RENAME COLUMNs (eyebrow → heading_eyebrow, heading/title → heading_title, description → heading_description), so existing content is kept. This covers page, global-section and preset tables, their versions, and posts.
  • Hero rich text is converted to plain text in SQL, one line per paragraph. Formatting such as bold or lists is lost; the text is kept.
  • down reverses everything and turns the description back into a one-paragraph rich text.

Cache key change

scopedCache now adds the latest migration name to the cache key. Vercel's data cache outlives deployments and the page cache key doesn't change, so without this, production would keep serving old-shape cached pages (no headings) after the migration until each page is republished. Every future schema change now invalidates the cache automatically.

Verified locally

  • Migrations apply from scratch on Postgres 17 + pgvector, and migrate:down then migrate round-trips the new migration cleanly.
  • payload migrate:create afterwards reports no schema changes, and generate:types produces no diff.
  • check-types passes.
  • Data migration was verified on the old PR (seeded pages, drafts, both locales, a global section, a post and a preset). The SQL is the same.
  • Two h1s are still possible when a page has two heroes; the stacked hero PR fixes that.
  • The migration SQL and snapshot are carried over unchanged from the old PR: the new snapshot differs from the old one only by its id.

claude and others added 3 commits October 2, 2026 09:19
Every section heading is now a single `heading` group (eyebrow, title,
description) built by `sectionHeadingField()` and rendered by
`<SectionHeading>`, which outputs an `<hgroup>` with the title as `h2`
(or `h1` for the hero). Blocks, the post FAQ/CTA and the blog header
all use it, so new sections reuse the pattern instead of re-inventing it.

The hero rich text is replaced by the plain heading description, so a
hero can no longer contain extra headings. The migration renames the
existing columns, so content is kept, and turns hero rich text into
plain text.

The data cache key now includes the latest migration name, so cached
pages in the old shape are not served after a schema change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6co4xd2fjA45YxTjXXjc3
…ing-v6okax

# Conflicts:
#	apps/cms/src/app/(payload)/admin/importMap.js
Merge the section heading work from #162 onto current main and recreate its migration
(section_heading_group) so it sorts after 20261012_110154_remove_author_required_flag_for_comment.

Scopes: apps/cms blocks, fields, components, contentExtraction, scopedCache, database/migrations.
@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ideal-cms Ready Ready Preview Oct 28, 2026 1:50pm UTC

Request Review

@dogfrogfog
dogfrogfog added this pull request to stack #217 October 28, 2026 13:54
@dogfrogfog
dogfrogfog merged commit 675f1ec into main Oct 28, 2026
2 checks passed
@dogfrogfog
dogfrogfog deleted the feat/cms-section-heading-pattern branch October 28, 2026 13:55
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.1.1 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.18.1 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.4.0 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — e673bcfc Deployed Oct 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants