@bettercms-ai/mcp 0.83.0 → 0.83.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +86 -86
  2. package/SKILL.md +160 -160
  3. package/dist/index.js +38 -9
  4. package/dist/index.js.map +1 -1
  5. package/dist/kit-catalog.json +1509 -1509
  6. package/dist/skills/bettercms/SKILL.md +206 -206
  7. package/dist/skills/bettercms/references/accessibility.md +171 -171
  8. package/dist/skills/bettercms/references/animation.md +296 -296
  9. package/dist/skills/bettercms/references/astro.md +148 -148
  10. package/dist/skills/bettercms/references/bindings.md +366 -366
  11. package/dist/skills/bettercms/references/componentize.md +185 -185
  12. package/dist/skills/bettercms/references/components.md +415 -415
  13. package/dist/skills/bettercms/references/content-experiments.md +62 -62
  14. package/dist/skills/bettercms/references/content-modeling.md +382 -382
  15. package/dist/skills/bettercms/references/detection.md +465 -465
  16. package/dist/skills/bettercms/references/dock-input.md +121 -121
  17. package/dist/skills/bettercms/references/editor-chrome.md +92 -92
  18. package/dist/skills/bettercms/references/external-content.md +78 -78
  19. package/dist/skills/bettercms/references/finish-site.md +377 -377
  20. package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
  21. package/dist/skills/bettercms/references/frameworks.md +159 -159
  22. package/dist/skills/bettercms/references/getting-started.md +156 -156
  23. package/dist/skills/bettercms/references/integrations.md +364 -364
  24. package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
  25. package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
  26. package/dist/skills/bettercms/references/localization.md +85 -85
  27. package/dist/skills/bettercms/references/media-and-brand.md +181 -181
  28. package/dist/skills/bettercms/references/next.md +189 -189
  29. package/dist/skills/bettercms/references/other-frameworks.md +85 -85
  30. package/dist/skills/bettercms/references/portable-text.md +213 -213
  31. package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
  32. package/dist/skills/bettercms/references/publishing.md +289 -289
  33. package/dist/skills/bettercms/references/seo.md +357 -357
  34. package/dist/skills/bettercms/references/shopify-headless.md +52 -52
  35. package/dist/skills/bettercms/references/site-blueprints.md +895 -895
  36. package/dist/skills/bettercms/references/site-review.md +74 -74
  37. package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
  38. package/dist/skills/bettercms/references/worked-examples.md +885 -885
  39. package/dist/skills/bettercms/references/write-limits.md +217 -217
  40. package/package.json +1 -1
@@ -1,895 +1,895 @@
1
- ---
2
- title: "Site blueprints"
3
- description: "Route, collection, form, structured-data and redirect plans per business type: portfolio, agency, corporate, SaaS/AI, ecommerce, directory, blog or publication, docs or knowledge base, local business or restaurant, events, course, job board, real estate, nonprofit and multi-brand."
4
- ---
5
-
6
- # Site blueprints by business type
7
-
8
- A route and content model that fits the business, while editor folders, public URLs and reusable
9
- components stay separate. A blueprint is a starting hypothesis, not permission to replace a
10
- working site's taxonomy. The platform rules behind each layer are in
11
- [content-modeling.md](content-modeling.md).
12
-
13
- ## Contents
14
-
15
- - [Common decision process](#common-decision-process)
16
- - [Starter rules](#starter-rules)
17
- - [Deliverables](#deliverables)
18
- - [Production shapes every blueprint uses](#production-shapes-every-blueprint-uses): foundations,
19
- field types, list limits, URL limits, structured data, forms, checklist
20
- - Catalogs, read only the one that matches (a mixed site can use two, but give each page family
21
- one route owner):
22
- - [Portfolio](#portfolio-website): a person or studio presents selected work, services and a
23
- contact path
24
- - [Agency](#agency-website): services, sectors, proof and lead generation drive the site
25
- - [Corporate](#corporate-website): several business units, regions, audiences, governance or
26
- regulated content coexist
27
- - [SaaS, AI, enterprise tech](#saas-ai-and-enterprise-technology-website): product,
28
- solutions, industries, integrations, trust and education need separate models
29
- - [Large ecommerce](#large-ecommerce-website): catalog, categories, products and shopping
30
- journeys dominate; a Shopify-backed store also reads [shopify-headless.md](shopify-headless.md)
31
- - [Directory](#directory-or-listings-website): hundreds of same-shaped listings with category
32
- and location hubs
33
- - [Blog / publication](#blog-or-publication): dated articles with bylines, taxonomy and a feed
34
- - [Docs / knowledge base](#docs-or-knowledge-base): a navigation tree, code, versions, search
35
- - [Local business / restaurant](#local-business-or-restaurant): address, hours, menu, booking
36
- - [Events](#events-or-conference): dated events, agenda, speakers, venue, sponsors
37
- - [Course](#course-or-education): courses, curriculum, instructors, cohorts
38
- - [Job board](#job-board-or-careers): open roles, departments, an ATS
39
- - [Real estate](#real-estate-or-property-listings): properties, agents, offices, areas
40
- - [Nonprofit](#nonprofit): programs, impact, stories, reports, donations
41
- - [Multi-brand](#multi-brand-and-multi-language): several brands or languages on one design
42
- system
43
-
44
- Data-generated page families (glossary, integrations, locations): [programmatic-seo.md](programmatic-seo.md).
45
-
46
- ## Common decision process
47
-
48
- 1. Inventory existing public URLs, navigation, templates, source content, business goals,
49
- locales, owners, analytics and design tokens. Keep established URLs and brand rules unless a
50
- change is in scope.
51
- 2. Give each family a **source of truth**: a BetterCMS page, a collection entry, an external
52
- system (commerce, ATS, docs), or application runtime. Name its editorial owner and publishing
53
- gate. Record capabilities BetterCMS does not have as gaps; never invent fields that imply
54
- them.
55
- 3. Produce a route matrix: pattern, purpose, static / list / detail, content source, template,
56
- canonical, indexability, sitemap eligibility, and the redirect it replaces. A collection's
57
- list is a page; its entries render through one dynamic detail page.
58
- 4. Produce a model matrix: fields and groups, references, taxonomy, reusable sections, global
59
- nav/footer, labels, and variants. Components for reusable presentation, collections for
60
- entities with their own lifecycle. Whether bands become components or fields follows
61
- `authoringPreference` from `get_project`, which only the owner answers.
62
- 5. Propose **Content sidebar** folders for editor tasks, **component groups** for definitions,
63
- and **media folders** for assets. None of these changes a URL. **URL page folders** do change
64
- paths, and renaming one 301s its pages, so they need explicit review. Sidebar doctrine:
65
- `bettercms://playbook/structure`.
66
- 6. Define per-family SEO policy: one canonical target, real indexable content, native SEO
67
- metadata (never modelled fields, §5b of `bettercms://playbook/schema`), structured data only
68
- where visible facts support it, and the treatment of filters, search, pagination, drafts,
69
- gated pages and deleted records. Pages marked noindex are left out of the generated sitemap.
70
- 7. Validate one representative route per family and the edge cases: empty collection, long
71
- title, unpublished detail, duplicate slug, moved URL, mobile nav, optional section, locale,
72
- no-result state. Confirm edit, preview and public render, then the build and binding reports.
73
-
74
- ## Starter rules
75
-
76
- - Reference page lists illustrate common shapes; do not create pages the business does not have.
77
- - Prefer a few stable, human-readable route patterns. The best URL is often the existing one.
78
- - Keep a slug unique within its route namespace; check static against dynamic paths and locales.
79
- When a published URL changes, add a redirect (`create_redirect`).
80
- - Keep a dedicated 404 and access/error pages. Leave drafts, private areas, internal search,
81
- parameter duplicates and 404s out of the sitemap.
82
- - Sitemaps hold absolute, canonical, indexable URLs; Google documents a limit of 50,000 URLs /
83
- 50 MB uncompressed per file. [Google sitemap guide](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap)
84
- - Content sidebar folders are navigation for editors. Public hierarchy comes from routes and
85
- internal links, never from folder labels. [Google ecommerce site structure](https://developers.google.com/search/docs/specialty/ecommerce/help-google-understand-your-ecommerce-site-structure)
86
- - With locale variants, keep explicit equivalent-page mappings and one `hreflang`
87
- implementation, verified in the rendered head. [Google localized versions](https://developers.google.com/search/docs/specialty/international/localized-versions)
88
-
89
- ## Deliverables
90
-
91
- Return a route tree, route matrix, content and model matrix, folder plan, component inventory,
92
- sitemap and redirect policy, open ownership decisions, and a phased implementation plan. On a
93
- live project, implement only the scope the user asked for and verify the routes you touched.
94
-
95
- ## Production shapes every blueprint uses
96
-
97
- Read this once; every catalog below assumes it. A site is often two types (a SaaS site with a
98
- blog and docs): open each matching catalog, merge them, and create shared collections (Authors,
99
- Topics, FAQs, Testimonials) once. Drop any collection the source does not have.
100
-
101
- ### Foundations
102
-
103
- | Piece | Where it lives |
104
- |---|---|
105
- | Header, footer, announcement bar | Global Layout via `update_layout`. Never a page section. Footer link columns are a repeater of groups; the newsletter box points at a form. A bar or promo that runs for a period takes a `window` on its Section, and a seasonal band one on its page block ([publishing.md](publishing.md) §6) |
106
- | Company name, address, phone, social profiles | One place in the Global Layout, read by the footer and the contact page. Site JSON-LD repeats it once in `siteSchema` (`update_seo`) |
107
- | Cookie consent | A third-party consent tool stays code. Report it; do not rebuild it as content |
108
- | 404, legal, thank-you pages | Static pages in a `System` Content sidebar folder. Thank-you pages are noindex |
109
- | Meta and structured data | Native per page and entry, site defaults with `update_seo` ([seo.md](seo.md)). Never modelled |
110
-
111
- ### Field types
112
-
113
- Field keys below are entry keys in one flat namespace per model. `title` and the slug are on
114
- every entry and are not repeated in the tables. SEO fields are never listed.
115
-
116
- | The source shows | Production field |
117
- |---|---|
118
- | Short text, long plain text | `text`, `longtext` |
119
- | The one long body | `document` (one per model, top level). Shorter formatted copy is `richtext` |
120
- | Image, download | `image`, `file` |
121
- | Postal address | `group` of `text` children: `"street"`, `"city"`, `"region"`, `"postal_code"`, `"country"` |
122
- | Map pin | `location` (lat, lng, optional label). Never two text fields |
123
- | Tags, amenities, bullet lists | `array` with `itemType: "text"`. One choice from a fixed list is `select` |
124
- | Rows of the same shape (hours, facts, metrics, tickets) | `repeater` of `text`, `number`, `select`, `boolean` children |
125
- | Mixed ordered blocks in a case study | one component per block shape, placed as sections |
126
- | One or many other entries | `reference`, `multi-reference`. A missing target is refused on write |
127
- | Call to action | `button` (label and link in one value). A bare link is `link`; also `email`; a phone number is `text` |
128
- | Date, date and time | `date`, `datetime`. Keep the zone as a `text` IANA name beside it when the site shows times |
129
- | A spec or comparison table | `repeater` with one field per column (any field type). There is no separate table field type |
130
-
131
- An external system's id (`"store_product_id"`, `"ats_job_id"`, `"feed_id"`, `"lms_course_id"`)
132
- is a `text` field kept as migration metadata and locked: `readOnly: "developer"` with a
133
- `lockReason` naming the system it comes from. Place it last. A write that changes it without
134
- schema access is refused (403 FIELD_LOCKED). The lock is enforced on entries only.
135
-
136
- ### Lists
137
-
138
- A collection's list is its own dynamic page holding one `collection` block. The block takes
139
- `limit` (1 to 200, default 20), `order` (`newest` or `oldest`, by creation time), `emptyText`,
140
- and optional card and detail components. Entry URLs are that page's path plus the entry slug.
141
-
142
- With `source: { modelId }` the block lists that collection on any hosted page, and takes
143
- `filters` (max 5; `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `in`), `sort`
144
- (`-publishedAt,title`), `pinnedIds` (max 10, shown first) and `excludeCurrent`. In an article
145
- template a filter value may be `{{field}}` of the entry shown. Filter values are stored on the
146
- block: a visitor cannot change them, and none means "today".
147
-
148
- Not available: pagination, grouping, visitor-driven filters. Only the hosted renderer draws the
149
- block; a route built from the repo's code queries the Delivery API with the same filter grammar.
150
- So for each hub the source has, choose and report one of:
151
-
152
- | Source list | Production shape |
153
- |---|---|
154
- | All entries, newest first | The `collection` block |
155
- | Filtered by category, location or status | On a hosted page a `collection` block with `source` and `filters`. On a code-built site the site's own code filters the delivered entries |
156
- | Sorted by a field (A to Z, a date, an order number) | `sort` on a sourced block |
157
- | Upcoming or past, by today's date | The site's code, or a `"status"` field the block filters on |
158
- | "Related" row | On a hosted template, a sourced block filtered by a `{{field}}` of the entry shown, with `excludeCurrent` |
159
- | "Featured" row | `pinnedIds`, a filter on a featured flag, or a short `multi-reference` chosen by hand |
160
- | Numbered pages, groups under headings, filter chips | The site's code |
161
- | Counts ("1,240 venues", "42 lessons") | Rendered by code from the entries. Never typed into copy |
162
-
163
- ### URLs and redirects
164
-
165
- - Every collection gets a `urlPattern` holding `:slug` exactly once (`/blog/:slug`). A fixed
166
- multi-segment prefix is valid (`/docs/v2/:slug`).
167
- - Nested (`/:category/:listing`) and dated (`/2024/05/my-post/`) source paths cannot be
168
- expressed. Use the flat pattern and add one `create_redirect` (301) per old URL.
169
- - A slug change on an entry with a pattern 301s by itself.
170
- - Redirect types are 301, 302, 307, 308 and 410. A retired entry either stays published and
171
- marked ended, redirects to a successor, or is unpublished and answers 410 (`create_redirect`
172
- with `redirectType: "410"`, no destination).
173
-
174
- ### Structured data
175
-
176
- Order: Automatic, then `schemaType`, then pasted `schema`. `schemaType` accepts `WebPage`,
177
- `AboutPage`, `ContactPage`, `CollectionPage`, `Blog`, `BlogPosting`, `Article`, `Product`,
178
- `FAQPage`, `Event`, `Organization`, `LocalBusiness`, `JobPosting`, `Course`, `Recipe`, `HowTo`,
179
- `Service`, `Person`, `VideoObject`. What the common ones emit from the content:
180
-
181
- | `schemaType` | Emits |
182
- |---|---|
183
- | `BlogPosting`, `Article` | Headline, description, image, published and modified dates, author |
184
- | `Product` | Name, description, image, SKU, offer |
185
- | `Event` | Name, description, image, start, end, a location name |
186
- | `LocalBusiness`, `Organization` | Name, description, URL, image or logo, telephone, email, address as one line |
187
- | `FAQPage` | The page's question and answer pairs. Never automatic |
188
-
189
- Anything richer is pasted `schema` built only from facts visible on the page: opening hours, geo
190
- coordinates, event status and offers, salary, `RealEstateListing`, `Restaurant`,
191
- `SoftwareApplication`, `NewsArticle`. A hosted page gets its `BreadcrumbList` automatically. Set a
192
- collection-wide default in `collectionSchemaTypes`. Never a self-serving `AggregateRating`.
193
-
194
- ### Forms
195
-
196
- Thirteen field types: `text`, `textarea`, `email`, `phone`, `number`, `url`, `date`, `select`,
197
- `radio`, `checkbox`, `checkboxes`, `consent`, `hidden`, plus the `step` divider for multi-step forms. No file upload, so CVs, floor plans and
198
- attachments go to the external system. Payment details never go in a form. Submissions land in
199
- the inbox; an editor turns one into an entry. Full pipeline:
200
- [forms-and-interactions.md](forms-and-interactions.md).
201
-
202
- ### Checklist
203
-
204
- - Every collection exists in the source, or was dropped on purpose.
205
- - Shared collections exist once across merged catalogs.
206
- - Store, ATS, LMS and feed facts are ids, never copies.
207
- - Every list has `emptyText` and a stated shape for each filter the source had.
208
- - Every collection has a `urlPattern`; every changed URL has a 301.
209
- - Structured data is built from visible fields, with the type its catalog names.
210
-
211
- ### Built-in sections
212
-
213
- The section library ships 23 platform blueprints in 17 families, so a standard band needs no new
214
- component: Hero, Feature grid (three, two, bento), Feature rows, Steps, Timeline, Stats, Logo cloud,
215
- Testimonial (quote, three cards), Team, Case study, Comparison, Pricing, FAQ, Call to action,
216
- Capture (newsletter band) and Listing (latest, related). Read one with `list_components`; inline its
217
- `blockJson` as a `section` block for a one-off band, or copy it with `create_component` `from:
218
- "builtin:<key>"` for a recurring one (see [components.md](components.md)). Two ship unbound on purpose: the Capture form has no form until you
219
- set its `form` prop, and both Listing layouts list the page's own entries until you give them a
220
- `source`; each shows its empty text meanwhile. Not available as a section, so build it in the repo
221
- or leave it out: mega menu, docs sidebar, cookie consent, exit intent, store locator, map, billing
222
- toggle, countdown and anything commerce.
223
-
224
- ## Portfolio website
225
-
226
- Use this when the primary job is to show selected work and make an individual or small studio credible. First determine whether the work is private, client-approved, dated, or still under embargo.
227
-
228
- ### Route and model starter
229
-
230
- | Family | Example path | BetterCMS model | Index and sitemap |
231
- |---|---|---|---|
232
- | Home | `/` | Static page with selected projects and clear contact action | Yes |
233
- | Work index | `/work/` | Static index page querying Projects | Yes if substantive |
234
- | Project detail | `/work/{slug}/` | Projects collection + one dynamic template | Published, approved projects only |
235
- | About / services | `/about/`, `/services/` | Static pages unless services need independent owners and detail pages | If public and useful |
236
- | Writing / speaking | `/writing/`, `/writing/{slug}/` | Optional collection and index | Published articles only |
237
- | Contact | `/contact/` | Static page + form | Usually yes |
238
- | Password/preview case study | Existing route | Other page or protected runtime route | No |
239
-
240
- Keep the existing work prefix if it is `/projects/`, `/case-studies/`, or another established route; route changes need a redirect map.
241
-
242
- ### Editor structure
243
-
244
- Content sidebar: `Start here` (Home, About, Contact), `Portfolio` (Work index, Projects), `Writing` if used, `Site essentials` (legal/other). Component groups: `Globals`, `Portfolio` (project hero, gallery, outcomes), `Shared sections` (CTA, quote, logo strip). Media folders may separate approved client work, self-initiated work, portraits, and downloadable files. These folders do not determine URLs.
245
-
246
- Project entry fields: display title, slug, client display name, service/category references, summary, role, dates when relevant, hero image plus alt text, ordered gallery items with captions/alt, challenge, approach, verified outcomes, credits, related projects, SEO, approval/visibility state. Do not require a public client name, metric, or testimonial when consent is absent. Support anonymous case studies without accidentally exposing the original client in image filenames, alt text, schema, or social previews.
247
-
248
- Use repeatable gallery items rather than a long list of `image1` fields. Give each item a meaningful editor label and image metadata. Keep project-specific storytelling sections as instances; make hero/gallery/quote layouts reusable definitions with instance overrides.
249
-
250
- ### Validation
251
-
252
- Check case-study consent, working gallery at different aspect ratios, captions, full-size media performance, downloadable file links, contact submission, related-work references, archived projects, and redirect behavior for renamed slugs. A portfolio with only a few projects does not need auto-generated service × industry pages.
253
-
254
- ### Production detail
255
-
256
- Projects collection, `urlPattern` `/work/:slug` (or the established prefix):
257
-
258
- | Field key | Type | Notes |
259
- |---|---|---|
260
- | `"client"` | `reference` to Clients, or `text` | A reference only when clients have a logo or page of their own |
261
- | `"year"` | `number` | |
262
- | `"services"` | `multi-reference` to Services, or `array` of text | References when service pages exist |
263
- | `"cover_image"`, `"excerpt"` | `image`, `longtext` | Words baked into a hero image move into fields |
264
- | `"modules"` or `"body"` | components, or `document` | one component per block (text, image, gallery, quote, video, stats) for image-led studies; `document` when mostly prose |
265
- | `"results"` | `repeater`: `"value"`, `"label"` | Only verified outcomes |
266
- | `"credits"` | `repeater`: `"role"`, `"name"` | |
267
- | `"testimonial"` | `reference` to Testimonials | |
268
- | `"live_link"` | `link` | |
269
- | `"is_featured"` | `boolean` | The home grid is a hand-picked `multi-reference` or code filtering on this |
270
-
271
- - **Chrome:** a minimal header and a footer with contact details, both in the Global Layout.
272
- - **Forms:** one enquiry form (name, email, message, consent).
273
- - **Structured data:** `Article` on projects, `AboutPage` and `ContactPage` on those pages,
274
- `Organization` or a `Person` node in `siteSchema`.
275
- - **Pitfalls:** videos without a poster or captions; scroll-driven storytelling flattened into
276
- one blob (keep each step a `modular` item); downscaled uploads (upload the original once, the
277
- media pipeline serves sizes); client work under NDA published without confirmed rights (flag
278
- it in the report and ask).
279
-
280
- ## Agency website
281
-
282
- Use when the site must explain services, prove results, and convert qualified leads. The key distinction from a portfolio is a durable offer taxonomy and repeatable proof linked to each offer.
283
-
284
- ### Route and model starter
285
-
286
- | Family | Example path | Model and purpose |
287
- |---|---|---|
288
- | Home | `/` | Static overview, positioning, primary CTA |
289
- | Services index/detail | `/services/`, `/services/{slug}/` | Static index + Services collection only if multiple services have owners, proof, and maintained detail content; otherwise static pages |
290
- | Industries | `/industries/`, `/industries/{slug}/` | Optional collection; publish only distinct industry expertise |
291
- | Work index/detail | `/work/`, `/work/{slug}/` | Case studies collection and dynamic detail template |
292
- | Insights | `/insights/`, `/insights/{slug}/` | Editorial collection, authors, categories |
293
- | Agency | `/about/`, `/team/`, `/careers/` | Static or collection by update cadence |
294
- | Contact | `/contact/` | Form with routing/consent behavior |
295
-
296
- Use the established `/case-studies/`, `/blog/`, etc. when the current site already has them. A service × industry matrix should be created only for combinations with distinct evidence, offer, and maintenance owner; otherwise link the existing service and industry pages instead of generating thin duplicates.
297
-
298
- ### Content relationships and components
299
-
300
- Cases reference services, industries, client permission state, outcome evidence, and optional quotes. Service pages reference relevant cases, deliverables, process, FAQ, and CTA. Industry pages reference services/cases and sector-specific language. Avoid duplicating client logos or metrics across pages: maintain permission and source context on the case/client record.
301
-
302
- Component groups: `Globals` (nav/footer), `Proof` (case card, metrics, testimonial), `Offers` (service card, process), `Conversion` (CTA, contact form), `Editorial`. Reuse definitions with page-specific props, and allow section order to vary by page. Editors should see grouped fields such as `Primary button → Label, Link`, `Proof item → Metric, Context, Source`.
303
-
304
- Content sidebar folders should match editor work: `Core pages`, `Services`, `Industries`, `Case studies`, `Insights`, `Company`. These are dashboard folders, not paths.
305
-
306
- ### Validation
307
-
308
- Review lead form destination, attribution and consent, internal links from every offer to relevant proof, case-study rights, schema claims, duplicate service routes, old URL redirects, and mobile CTA visibility. Keep draft or client-confidential work out of sitemaps and public data feeds.
309
-
310
- ### Production detail
311
-
312
- | Collection | `urlPattern` | Fields |
313
- |---|---|---|
314
- | Case studies | `/work/:slug` | The Projects fields in [Portfolio](#portfolio-website), plus `"industry"` `reference` |
315
- | Services | `/services/:slug` | `"icon"` `image`, `"excerpt"` `longtext`, `"body"` `document`, `"related_work"` `multi-reference` |
316
- | Clients | none (no detail page) | `"logo"` `image` with the company name as alt, `"website"` `link` |
317
- | Team | `/team/:slug`, only when profiles have pages | `"role"` `text`, `"photo"` `image`, `"bio"` `longtext`, `"social_links"` `repeater`: `"network"` `select`, `"url"` `link` |
318
- | Testimonials | none | `"quote"` `longtext`, `"name"`, `"role"`, `"company"` `text`, `"photo"`, `"logo"` `image` |
319
-
320
- - **Lists:** "work by service" on a service page is `"related_work"` picked by hand, code
321
- filtering the delivered case studies, or on a hosted page a `collection` block with `source`
322
- and a filter on the service.
323
- - **Forms:** a new-business enquiry with `select` fields for budget and timeline, plus `hidden`
324
- fields for campaign attribution.
325
- - **Structured data:** `Organization` in `siteSchema`, `Article` on case studies, `WebPage` on
326
- services, `FAQPage` only where a service page shows real questions. A `Person` node per team
327
- page is pasted `schema`.
328
- - **Pitfalls:** a logo wall with no alt text; client names retyped per page instead of
329
- referenced; a testimonial with no record of consent (keep a `"consented_on"` `date`).
330
-
331
- ## Corporate website
332
-
333
- Use for multiple audiences, brands, regions, business units, approval chains, or regulated statements. The main modeling problem is ownership: who can update a statement, which sites reuse it, and when it expires.
334
-
335
- ### Route and model starter
336
-
337
- | Family | Example path | Owner and model |
338
- |---|---|---|
339
- | Corporate home | `/` | Global communications; static page |
340
- | Business units | `/businesses/{slug}/` | Collection if units share a template and lifecycle |
341
- | Products/services | Existing taxonomy | Collection or static pages by unit ownership |
342
- | Newsroom | `/news/`, `/news/{slug}/` | News collection, media contact, publish date |
343
- | Leadership | `/leadership/`, `/leadership/{slug}/` | People collection if profiles recur |
344
- | Careers | `/careers/` | Landing page; job details may remain in an ATS |
345
- | Investor / governance | `/investors/`, `/governance/` | Gated approval; files with version/date |
346
- | Policies and legal | `/privacy/`, `/terms/` | Static pages with accountable owner |
347
- | Contact / locations | `/contact/`, `/locations/{slug}/` | Form plus locations collection when needed |
348
-
349
- Do not copy ATS jobs, stock prices, investor filings, or regulated data into BetterCMS unless BetterCMS is deliberately the system of record. Preserve external systems and public links.
350
-
351
- ### Governance and editor structure
352
-
353
- Record owner, reviewer, source of claim, effective date, review date, and version where these matter. Separate draft, legal-reviewed, approved, and published states with the project's own editorial board: read its stages and which one is the publish gate with `get_workflow_board` before `move_stage`, because stage keys are per project. Do not claim an approval stage exists if the board has none. Scope access and publishing by the product's real permissions. High-risk copy (financial figures, certifications, compliance claims, regulated products) needs a verified source and designated reviewer.
354
-
355
- Content sidebar: `Corporate pages`, `Business units`, `Newsroom`, `People`, `Investors`, `Legal`, `Regions` if used. Component groups: `Globals`, `Corporate identity`, `Business unit`, `Newsroom`, `Data/proof`. Distinguish sitewide global identity from unit-specific branding and local navigation. A shared global component should not overwrite an independently branded unit page.
356
-
357
- ### Regions and languages
358
-
359
- Choose a deliberate URL strategy, such as `/{locale}/...` or regional domains. Keep locale-to-locale equivalent-page IDs, fallback behavior, canonical target, and language switcher destinations. Check what the connected project models for locales before planning; implement and verify `hreflang` output in the frontend, where the rendered head is decided. Do not auto-redirect by IP or index machine placeholders. Follow [Google's localized-page guidance](https://developers.google.com/search/docs/specialty/international/localized-versions).
360
-
361
- ### Validation
362
-
363
- Test unit-specific nav/footers, ownership permissions, embargoed news, expiring files, superseded legal pages, regional links, localization fallbacks, 404/redirects, accessibility, and the published head for every major template. Separate search visibility from access control: `noindex` does not protect private information.
364
-
365
- ## SaaS, AI, and enterprise technology website
366
-
367
- Use when a product site must explain a platform to multiple buyers and evaluators. Distinguish product truth, marketing interpretation, developer documentation, and live application behavior.
368
-
369
- ### Route and model starter
370
-
371
- | Family | Example path | Suggested ownership |
372
- |---|---|---|
373
- | Home | `/` | Static marketing composition |
374
- | Product/platform | `/product/`, `/features/{slug}/` | Static overview; Features collection only if repeated and maintained |
375
- | Solutions | `/solutions/{slug}/` | Collection for distinct jobs-to-be-done/use cases |
376
- | Industries | `/industries/{slug}/` | Collection where sector-specific workflows/proof exist |
377
- | Integrations | `/integrations/{slug}/` | Collection with provider status, auth, capability, owner |
378
- | Customers | `/customers/{slug}/` | Case studies with approvals and proof sources |
379
- | Pricing | `/pricing/` | Static page or runtime-driven product data; one owner for actual pricing |
380
- | Resources | `/resources/{slug}/`, `/blog/{slug}/` | Editorial collections and their list pages |
381
- | Glossary | `/glossary/{term}/` | Definition collection with editorial gates |
382
- | Comparisons / alternatives | `/compare/{slug}/` | Dedicated reviewed model, never blind competitor templating |
383
- | Trust and legal | `/security/`, `/privacy/`, `/trust/` | Static pages or external trust center |
384
- | Docs / API | `/docs/...` | Often a separate docs system/repository; link rather than duplicate |
385
- | Conversion | `/demo/`, `/contact/` | Forms with routing, consent, and success states |
386
-
387
- These are options, not required pages. Keep existing routes and product naming. For a large enterprise site, consider ownership by product line/market and a shared navigation contract; avoid separate page schemas for identical layouts.
388
-
389
- ### Model and section kit
390
-
391
- Entities: Solutions, Industries, Features, Integrations, Cases, Resources, Glossary Terms, and optional Comparisons. Use references across them so an integration or proof item updates once. Each entry needs an owner, lifecycle, canonical URL, native SEO metadata, and sufficient unique content. Integration entries need a clear status (live, beta, planned) and must never imply a planned connector is available.
392
-
393
- Shared sections: product hero, outcome grid, workflow steps, feature visual, integration grid, proof strip, case excerpt, FAQ, security/trust callout, pricing CTA. Keep a single component definition per visual family and use variants/instance overrides. Give buttons and media grouped editor controls. Expose only supported rich-text headings; a hero title may be plain text rendered as H1 while an article body may allow H2/H3, links, lists, and code if the renderer supports them.
394
-
395
- Content sidebar: `Core pages`, `Product`, `Solutions`, `Industries`, `Integrations`, `Customers`, `Resources`, `Trust`, `Other`. Component groups should reflect role, not route.
396
-
397
- ### Search and AEO
398
-
399
- Build topic hubs and link related solution, industry, integration, case, and glossary pages. Only publish a specific solution × industry page when it adds differentiated workflow, evidence, and value. Answer questions directly with verifiable facts and useful context; do not fabricate FAQ, schema, or “AI answer” pages. Use [programmatic-seo.md](programmatic-seo.md) for generated page families and [Google's AI-search guidance](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide) for the current search-policy boundary.
400
-
401
- ### Validation
402
-
403
- Verify product claims against shipped capability, integration statuses, pricing consistency, demo form routing, docs links, internal topic paths, glossary duplicates, comparison accuracy, rendering of code/rich text, and desktop/mobile navigation. Noindex preview, gated assets, and incomplete pages.
404
-
405
- ### Production detail
406
-
407
- | Collection | `urlPattern` | Fields |
408
- |---|---|---|
409
- | Case studies | `/customers/:slug` | `"customer"` `text`, `"logo"`, `"cover_image"` `image`, `"industry"` `reference`, `"excerpt"` `longtext`, `"metrics"` `repeater` (`"value"`, `"label"`, at most 3 rows), `"quote"` `reference` to Testimonials, `"body"` `document` with challenge, solution, results as H2s, `"facts"` `repeater` (`"label"`, `"value"`) |
410
- | Testimonials | none | `"quote"`, `"name"`, `"role"`, `"company"`, `"photo"`, `"logo"`, `"source"` `link`, `"consented_on"` `date`. `"rating"` `number` only when the source shows one |
411
- | Integrations | `/integrations/:slug` | `"logo"`, `"category"` `reference`, `"excerpt"`, `"body"` `document`, `"app_link"` `button`, `"docs_link"` `link`, `"is_native"` `boolean`, `"status"` `select` (live, beta, planned) |
412
- | Releases | `/changelog/:slug`, or none when releases are anchors on one page | `"version"` `text`, `"published_on"` `datetime`, `"changes"` `repeater` (`"kind"` `select`: new, improved, fixed; `"text"`), `"body"` `richtext` |
413
- | Comparisons | `/compare/:slug` | `"competitor"` `text`, `"competitor_logo"`, `"intro"` `longtext`, `"matrix"` `repeater` (`"feature"`, `"us"`, `"them"`), `"verified_on"` `date`, `"body"` `document` |
414
-
415
- - **Static until repeated:** feature and product pages stay static pages until three or more
416
- share one layout. Pricing tiers stay in the pricing page's fields unless the same plans show
417
- on two or more pages; then a Plans collection.
418
- - **Pricing:** store the monthly and the yearly price as two fields read from the source. Never
419
- derive one from a discount. The full feature grid is a `repeater` whose row holds one value
420
- per plan; inside a component it can be the `table` prop ([components.md](components.md)).
421
- - **Lists:** customer and integration hubs are one `collection` block each. Category filter
422
- chips are code on a code-built site; otherwise leave them out and say so. A home logo wall is
423
- a `repeater` of images, or a `multi-reference` when logos link to stories.
424
- - **Forms:** demo request and contact sales with `hidden` attribution fields, and a newsletter
425
- form with `consent`. A gated asset is a form whose success state links the file.
426
- - **Structured data:** `Organization` and a `WebSite` node in `siteSchema`, `FAQPage` on the
427
- pricing page (one per URL), `BlogPosting` on posts. `JobPosting` as the careers collection's
428
- `schemaType`; `SoftwareApplication` is pasted `schema` from visible facts.
429
- - **Pitfalls:** "Trusted by 10,000 teams" style counters with no source (report as unverified);
430
- a feature matrix pasted as an image; a screenshot holding the only copy of a claim.
431
-
432
- ## Large ecommerce website
433
-
434
- Use for a catalog-heavy site regardless of commerce engine. The core choice is the boundary between **commerce facts** and **editorial merchandising**.
435
-
436
- ### Source-of-truth contract
437
-
438
- Products, variants, SKUs, price, availability, tax, orders, checkout, customer accounts, and fulfillment normally belong to the commerce platform. BetterCMS owns editorial landing pages, category storytelling, buying guides, banners, proof, and selected product-page content blocks. A CMS reference to a product uses a stable external ID plus a verified lookup; don't silently duplicate transactional fields into editable CMS text.
439
-
440
- ### Route and model starter
441
-
442
- | Family | Example path | Index policy |
443
- |---|---|---|
444
- | Home and campaigns | `/`, `/campaigns/{slug}/` | Public, unique campaigns |
445
- | Catalog root / category | `/shop/`, `/collections/{slug}/` | Curated category pages with products and links |
446
- | Product | `/products/{slug}/` | Available public products; decide retired-product policy |
447
- | Brand | `/brands/{slug}/` | Only substantive brand pages |
448
- | Buying guides | `/guides/{slug}/` | Unique advice with product references |
449
- | Editorial | `/stories/{slug}/` | Published articles |
450
- | Search, cart, account, checkout | Runtime routes | Usually excluded from sitemap; private data protected |
451
- | Filters and sort | Parameters on category routes | Explicit crawl/index policy; avoid unbounded URL combinations |
452
-
453
- Keep existing paths if the store already uses another taxonomy. Decide whether unavailable products stay indexable, redirect to a successor, or are unpublished and return 404 or a 410 rule, based on permanence and user value. Do not redirect every missing product to the homepage.
454
-
455
- ### Editor model and reuse
456
-
457
- Models: Campaigns, Buying Guides, optional Brand and Category editorial records, plus product references. Category merchandising groups can include hero, intro, featured products, editorial cards, filters, and FAQs. Components: global store nav, search, promo bar, product card, variant selector, add-to-cart, category hero, recommendation rail, cart feedback. Commerce controls remain functional code tied to live data; an editor can change allowed presentation and copy but must not create an unfulfillable cart action.
458
-
459
- Content sidebar: `Campaigns`, `Collections`, `Guides`, `Stories`, `Store pages`; group media by campaigns/editorial needs. Content sidebar folders never change a URL; URL page folders do, and renaming one 301s its pages, so do not turn category folders into URL folders unless a route change is intended.
460
-
461
- ### Crawl and scale
462
-
463
- Provide crawlable category → subcategory → product links and paginated result URLs. Decide which facet combinations deserve dedicated curated landing pages; keep the rest out of the index/crawl space by an intentional policy. Do not rely on canonical alone to control a near-infinite faceted URL space. Keep canonical, internal link, and sitemap URLs consistent. Sitemaps should include only eligible canonical pages and split before protocol limits. Product price/availability markup must match the current displayed commerce data; validate variant markup against Google's current `ProductGroup` guidance. [Google ecommerce structure](https://developers.google.com/search/docs/specialty/ecommerce/help-google-understand-your-ecommerce-site-structure) · [Faceted navigation](https://developers.google.com/crawling/docs/faceted-navigation) · [Product variants](https://developers.google.com/search/docs/appearance/structured-data/product-variants)
464
-
465
- ### Validation
466
-
467
- Test a product with several variants, a sold-out product, missing image, empty category, deep pagination, filter combinations, mobile search, currency/market change, cart handoff, and changed price after cache expiry. Verify that sitemap counts match eligible catalog records and that deleted routes/redirects do not strand internal links.
468
-
469
- ### Production detail
470
-
471
- Applies when BetterCMS renders product or category pages. A Shopify-backed store follows
472
- [shopify-headless.md](shopify-headless.md) first.
473
-
474
- | Collection | `urlPattern` | Fields |
475
- |---|---|---|
476
- | Products | `/products/:slug`, slug equal to the store handle | `"store_product_id"` `text` (migration metadata), `"excerpt"` `longtext`, `"story"` `document`, `"gallery"` `repeater` (`"image"`, `"caption"`; editorial images only), `"highlights"` `array` of text, `"specs"` `repeater` (`"label"`, `"value"`), `"care"` `richtext`, `"categories"` `multi-reference`, `"badge"` `select`, `"related"` `multi-reference` (at most 4), `"faqs"` `multi-reference` |
477
- | Categories | `/collections/:slug` | `"intro"` `longtext`, `"image"`, `"parent"` `reference` to Categories (one level), `"store_collection_id"` `text` |
478
- | FAQs (when shared) | none | `"question"` `text`, `"answer"` `richtext` |
479
-
480
- A catalogue with no commerce engine adds `"price"` and `"compare_at_price"` `number`, a
481
- `"currency"` `select` of ISO codes, `"sku"` `text`, `"is_in_stock"` `boolean`.
482
-
483
- - **Lists:** a category's product grid is code filtering by the category, or the store's own
484
- listing. A "Staff picks" row is a `multi-reference`.
485
- - **Chrome:** header (search, account and cart are app state in code, never content), a promo
486
- bar, a footer whose payment badges are a fixed set in code.
487
- - **Forms:** newsletter, contact, wholesale enquiry. Back-in-stock and checkout stay with the
488
- store.
489
- - **Structured data:** `Product` only where price and availability are entry fields. When the
490
- store owns them, the site's code emits `Product` with the live `Offer`; do not paste a frozen
491
- price. `CollectionPage` on categories. `AggregateRating` only from a connected review source.
492
- - **Redirects:** facet and sort parameters canonical to the category. Cart, checkout, account
493
- and search are noindex.
494
- - **Pitfalls:** price, stock, options or variants copied into content (they drift within a
495
- day); one static page per product, or one entry per variant; a size guide as an image instead
496
- of a `repeater`; typed review counts or star scores; editorial images hotlinked from the
497
- store CDN instead of uploaded.
498
-
499
- ## Directory or listings website
500
-
501
- Use when the source has hundreds of same-shaped detail pages (businesses, tools, venues,
502
- vendors), category and location hubs, filters, a "Submit a listing" form, often a map.
503
-
504
- ### Routes
505
-
506
- | Family | Path | Model |
507
- |---|---|---|
508
- | Home, submit, featured-listing pricing, about, contact | `/`, `/submit`, ... | Static pages |
509
- | Listings list and detail | `/listings`, `/listings/:slug` | Listings collection, dynamic page, `urlPattern` `/listings/:slug` |
510
- | Category hub | `/categories`, `/categories/:slug` | Categories collection |
511
- | Location hub | `/locations`, `/locations/:slug` | Locations collection (cities or regions) |
512
-
513
- ### Collections
514
-
515
- | Collection | Fields |
516
- |---|---|
517
- | Listings | `"logo"`, `"cover_image"` `image`; `"excerpt"` `longtext`; `"body"` `document`; `"category"` `reference` (required); `"subcategories"` `multi-reference`; `"place"` `reference` to Locations; `"address"` `group`; `"map_pin"` `location`; `"website"` `link`; `"email"` `email`; `"phone"` `phone`; `"opening_hours"` `repeater` (`"day"` `select`, `"opens"`, `"closes"` `text`, `"is_closed"` `boolean`); `"amenities"` `array` of text, or `multi-reference` to Features when each has a page; `"facts"` `repeater` (`"label"`, `"value"`); `"gallery"` `repeater` (`"image"`, `"caption"`); `"social_links"` `repeater`; `"plan"` `select` (free, featured, premium); `"is_verified"` `boolean`; `"external_id"` `text` |
518
- | Categories | `"intro"` `longtext`, `"icon"` `image`, `"parent"` `reference` to Categories |
519
- | Locations | `"region"` `text`, `"country"` `select`, `"map_pin"` `location`, `"intro"` `longtext`, `"cover_image"` `image` |
520
-
521
- `"plan"`, `"is_verified"` and `"external_id"` are locked: `readOnly: "developer"` with a
522
- `lockReason`. A write without schema access that changes one is refused.
523
-
524
- - **Chrome:** header with search, footer, an announcement for featured-listing offers.
525
- - **Lists:** the all-listings page is a `collection` block. On a hosted page a category or
526
- location hub is a block with `source` and a filter, A to Z is `sort`, featured first is
527
- `pinnedIds`, and "related listings" is a filter on a `{{field}}` of the listing shown with
528
- `excludeCurrent`. Numbered pagination needs the site's code: report the gap. Site search
529
- uses the hosted index: run `refresh_search_index` after the import.
530
- - **Forms:** submit a listing, claim a listing, report a problem, contact. A submission never
531
- publishes itself; an editor creates or updates the entry from the inbox.
532
- - **Structured data:** `schemaType: "LocalBusiness"` on listings that are places. Paste `schema`
533
- when the page shows hours or coordinates, or needs a subtype. `CollectionPage` on hubs.
534
- - **Redirects:** nested source paths (`/:category/:listing`) become `/listings/:slug` plus one
535
- 301 each.
536
- - **Pitfalls:** filterable facts stored as free tags (use `select`, `reference`, `number`);
537
- duplicate listings from several sources (match on website domain, phone or address before
538
- creating); a one-line address when structured data needs the parts; a paid outbound link with
539
- no `rel="sponsored"` (the `link` value carries no rel, so the rendering code adds it from
540
- `"plan"`); hub pages with fewer than three listings left indexable (set noindex until they
541
- grow); thousands of near-empty pages (run `generate_pages_from_dataset` with `dryRun: true`
542
- first, and only for rows with real content).
543
-
544
- ## Blog or publication
545
-
546
- Use when the source has dated articles with bylines, categories, an index and a feed.
547
-
548
- ### Routes
549
-
550
- | Family | Path | Model |
551
- |---|---|---|
552
- | Home (or the index itself), about, newsletter, contact | `/`, `/about`, ... | Static pages |
553
- | Post list and detail | `/blog`, `/blog/:slug` (or the site's own word, `/journal/:slug`) | Posts collection, `urlPattern` `/blog/:slug` |
554
- | Author | `/authors/:slug` | Authors collection |
555
- | Category, topic | `/category/:slug`, `/topics/:slug` | One collection per taxonomy the source really has |
556
-
557
- ### Collections
558
-
559
- | Collection | Fields |
560
- |---|---|
561
- | Posts | `"dek"`, `"excerpt"` `longtext`; `"cover_image"` `image`; `"body"` `document`; `"authors"` `multi-reference` (at least one); `"category"` `reference`; `"topics"` `multi-reference`; `"published_on"`, `"updated_on"` `datetime`; `"series"` `reference`; `"is_featured"` `boolean`; `"related"` `multi-reference` (at most 3) only when the source curates them |
562
- | Authors | Title is the name. `"role"` `text`, `"photo"` `image`, `"bio"` `longtext`, `"social_links"` `repeater` |
563
- | Categories, Topics | `"intro"` `longtext` |
564
-
565
- - **Chrome:** header, footer with the newsletter form, announcement.
566
- - **Lists:** the index is a `collection` block, `order: "newest"`. It orders by creation time,
567
- so import posts oldest first, or the list will not follow `"published_on"`. A block with
568
- `source` takes `sort: "-published_on"` instead. Category, topic and author lists on a hosted
569
- page are sourced blocks with a filter; numbered pagination needs the site's code. A pinned
570
- feature slot is `pinnedIds` on a sourced block, or a `reference` on the index page.
571
- - **Forms:** newsletter with `consent`, contact, tips.
572
- - **Structured data:** `BlogPosting` or `Article` as the collection default in
573
- `collectionSchemaTypes`; `Blog` on the index. `NewsArticle` is pasted `schema`; an author page takes
574
- `schemaType: "Person"`. Turn the feed on in `rssConfig`; item links come from
575
- the `urlPattern`. A syndicated post keeps its cross-site canonical in the entry's `meta`.
576
- - **Redirects:** dated WordPress paths (`/2024/05/my-post/`) become `/blog/:slug` with a 301
577
- per post.
578
- - **Pitfalls:** share bars, author boxes and related cards inside the body (they are template
579
- sections); an H1 inside the body, or the cover repeated as the first body image; authors as
580
- strings; a stored reading time (compute it); WordPress shortcodes (`[gallery]`, `[caption]`)
581
- left as text (map them to a Portable Text block or a component, report unknown ones); tags
582
- and categories that duplicate each other.
583
-
584
- ## Docs or knowledge base
585
-
586
- Use when the source has a left navigation tree, "On this page" anchors, code blocks,
587
- admonitions, a version switcher, search, "Was this helpful?".
588
-
589
- ### Routes
590
-
591
- | Family | Path | Model |
592
- |---|---|---|
593
- | Docs home, support home, contact support | `/docs`, `/support`, ... | Static pages |
594
- | Doc page | `/docs/:slug` | Docs collection |
595
- | Versioned docs | `/docs/v2/:slug` | One collection per supported major version, each with its own fixed-prefix `urlPattern` |
596
- | Knowledge-base article | `/help/:slug` | KB articles collection |
597
- | Glossary | `/glossary`, `/glossary/:slug` | Terms collection ([programmatic-seo.md](programmatic-seo.md)) |
598
-
599
- ### Collections
600
-
601
- | Collection | Fields |
602
- |---|---|
603
- | Docs | `"excerpt"` `longtext`; `"body"` `document`; `"section"` `reference` to Doc sections (required); `"parent"` `reference` to Docs (at most 3 levels); `"order"` `number`; `"status"` `select` (new, beta, deprecated); `"last_reviewed_on"` `date` |
604
- | Doc sections | `"icon"` `image`, `"order"` `number` |
605
- | KB articles | `"body"` `document`, `"category"` `reference`, `"related"` `multi-reference` |
606
- | Terms | `"definition"` `richtext`, `"related"` `multi-reference` |
607
-
608
- - **Chrome:** header with search and footer in the Global Layout. The sidebar tree is rendered
609
- by code from `"section"`, `"parent"` and `"order"`; previous and next links and "On this page"
610
- are derived the same way. Curate the tree in the Layout only when the source hand-orders it.
611
- - **Lists:** a section landing page ordered by `"order"` is a `collection` block with `source`,
612
- a filter on `"section"` and `sort: "order"` on a hosted page, or the site's code. Run
613
- `refresh_search_index` after the import.
614
- - **Forms:** a one-question feedback form (`radio` plus a `hidden` page path), contact support.
615
- - **Structured data:** `Article` or `WebPage`; `FAQPage` only on real question pages. A deprecated version is noindex or canonicals to the current one.
616
- - **Redirects:** relative `.md` links in source files resolve to the target entry's URL. A
617
- nested source path (`/docs/guides/setup/install`) becomes `/docs/:slug` plus a 301.
618
- - **Pitfalls:** the navigation tree copied by hand (it drifts); admonitions flattened to
619
- blockquotes with the tone lost (map each to an embedded block that keeps its tone,
620
- [portable-text.md](portable-text.md)); code as images or highlighted markup (a `bcmsCode`
621
- block keeps the plain text and a language); MDX components with no
622
- equivalent (map known ones to components, report the rest as logic left in code); an OpenAPI
623
- reference converted by hand (it is generated from the spec: link or embed it and report it).
624
-
625
- ## Local business or restaurant
626
-
627
- Use when the source shows an address, hours, a phone number, a menu, "Book a table", a map, and
628
- one or a few locations.
629
-
630
- ### Routes
631
-
632
- | Family | Path | Model |
633
- |---|---|---|
634
- | One location | `/visit` | Static page; address and hours live once in the Global Layout |
635
- | Two or more locations | `/locations`, `/locations/:slug` | Locations collection |
636
- | Menu | `/menu`, or `/menus/:slug` | A page section; a Menus collection when menus differ per location or season |
637
- | Events, specials | `/events/:slug` | Collections only when dated |
638
-
639
- ### Collections
640
-
641
- | Collection | Fields |
642
- |---|---|
643
- | Locations | `"address"` `group`; `"map_pin"` `location`; `"phone"` `phone`; `"email"` `email`; `"opening_hours"` `repeater` (`"day"` `select`, `"opens"`, `"closes"` `text`, `"is_closed"` `boolean`); `"special_hours"` `repeater` (`"date"` `date`, `"opens"`, `"closes"`, `"is_closed"`, `"note"`); `"time_zone"` `text`; `"cover_image"` `image`; `"menus"` `multi-reference`; `"booking_provider"` `select`; `"booking_id"` `text` |
644
- | Menus | Title is "Dinner", "Brunch". `"sections"` `repeater` (`"title"`, `"items"` `repeater`: `"name"`, `"description"`, `"price"` `number`, `"dietary"` `array` of text, `"is_new"` `boolean`); `"notes"` `longtext` (allergen statement); `"pdf"` `file` (a download, never the only copy) |
645
- | Events, Specials | `"starts_at"`, `"ends_at"` `datetime`; `"place"` `reference` to Locations; `"body"` `richtext`; `"image"` |
646
-
647
- Give `"opens"` and `"closes"` help text with the format (24-hour `HH:MM`). The currency is a
648
- site setting in code, not a symbol typed into each price.
649
-
650
- - **Chrome:** header with a booking button that links to the provider; footer listing
651
- locations. Hours appear in one source: with a Locations collection the footer renders from
652
- the entries in code, otherwise from the Global Layout.
653
- - **Forms:** private-events enquiry, contact, newsletter. Reservations are the provider's
654
- embed, stored as provider and id only.
655
- - **Structured data:** `schemaType: "LocalBusiness"` gives name, phone, email and a one-line
656
- address. Hours, geo, a structured postal address and the `Restaurant` subtype need pasted
657
- `schema` built from the fields. Same name, address and phone everywhere. `Event` on events.
658
- - **Redirects:** a single-location site that grows to several moves `/visit` to
659
- `/locations/:slug` with a 301.
660
- - **Pitfalls:** a menu as a PDF or image only; hours typed in the footer, the contact page and
661
- the location page; prices as "$14" strings; "Closed" typed as a time; holiday hours
662
- forgotten; Google reviews copied as testimonials (connect the source or get consent).
663
-
664
- ## Events or conference
665
-
666
- Use when the source shows dates with times, an agenda, speakers, tickets, a venue, sponsors, a
667
- countdown.
668
-
669
- ### Routes
670
-
671
- | Family | Path | Model |
672
- |---|---|---|
673
- | Current edition home, tickets, travel, code of conduct, call for papers | `/`, `/tickets`, ... | Static pages |
674
- | Event | `/events`, `/events/:slug` | Events collection |
675
- | Session | `/sessions/:slug` | Sessions collection |
676
- | Speaker | `/speakers`, `/speakers/:slug` | Speakers collection |
677
- | Venue, sponsor | none unless the source has pages | Venues, Sponsors collections |
678
-
679
- ### Collections
680
-
681
- | Collection | Fields |
682
- |---|---|
683
- | Events | `"starts_at"`, `"ends_at"` `datetime`; `"time_zone"` `text`; `"status"` `select` (scheduled, postponed, rescheduled, cancelled, moved online); `"attendance"` `select` (in person, online, mixed); `"venue"` `reference`; `"excerpt"`; `"cover_image"`; `"body"` `document`; `"tickets_button"` `button`; `"tickets"` `repeater` (`"name"`, `"price"` `number`, `"currency"` `select`, `"availability"` `select`) only when no ticketing provider owns them; `"speakers"`, `"sponsors"` `multi-reference` |
684
- | Sessions | `"event"` `reference`; `"starts_at"`, `"ends_at"`; `"room"` `text`; `"track"` `reference` or `select`; `"format"` `select` (talk, workshop, panel, break); `"level"` `select`; `"speakers"` `multi-reference`; `"description"` `richtext`; `"recording_url"` `link` |
685
- | Speakers | Title is the name. `"role"`, `"company"`, `"photo"`, `"bio"`, `"social_links"` |
686
- | Venues | `"address"` `group`, `"map_pin"` `location`, `"accessibility"`, `"directions"` `richtext` |
687
- | Sponsors | `"logo"`, `"tier"` `select`, `"website"` `link` |
688
-
689
- - **Chrome:** header with a tickets button; an announcement bar whose countdown end is one
690
- `datetime` with its zone, and whose Section `window` ends with it. Never migrate a countdown
691
- that resets per visitor.
692
- - **Lists:** a `collection` block with `source` sorts by `"starts_at"`, but its filter values
693
- are fixed: none means "today". Upcoming and past lists, and an agenda grouped by day and
694
- track, are the site's code filtering on `"starts_at"`. State that in the report; do not fake
695
- it with two hand-kept lists.
696
- - **Forms:** registration (or the ticketing provider's embed), call for papers (links only, no
697
- upload), sponsor enquiry, newsletter.
698
- - **Structured data:** `schemaType: "Event"` gives name, dates and a location name. Event
699
- status, attendance mode, a `Place` with address or a `VirtualLocation`, offers from visible
700
- ticket prices and performers need pasted `schema`.
701
- - **Redirects:** past events keep their URLs and stay published. A yearly path
702
- (`/2025/schedule/talk-name`) becomes `/sessions/:slug` plus a 301.
703
- - **Pitfalls:** times with no zone (ask when the site shows none); the agenda as an image or
704
- PDF only; speakers retyped per session; past events deleted; a sponsor link with no
705
- `rel="sponsored"` in the rendering code.
706
-
707
- ## Course or education
708
-
709
- Use when the source has courses with a curriculum, instructors, levels, durations, enrol
710
- buttons, cohorts.
711
-
712
- ### Routes
713
-
714
- | Family | Path | Model |
715
- |---|---|---|
716
- | Home, catalogue hub, pricing, for teams, FAQ | `/`, `/courses`, ... | Static pages; the hub holds the `collection` block |
717
- | Course | `/courses/:slug` | Courses collection |
718
- | Instructor | `/instructors/:slug` | Instructors collection |
719
- | Cohort | none | Cohorts collection, only when dated |
720
-
721
- Lesson content behind a login belongs to the LMS, not BetterCMS.
722
-
723
- ### Collections
724
-
725
- | Collection | Fields |
726
- |---|---|
727
- | Courses | `"excerpt"`; `"cover_image"`; `"level"` `select`; `"duration"` `text` (ISO 8601, `PT6H30M`); `"category"` `reference`; `"instructors"` `multi-reference`; `"outcomes"`, `"prerequisites"` `array` of text; `"curriculum"` `repeater` (`"title"`, `"lessons"` `repeater`: `"title"`, `"duration"`, `"is_preview"` `boolean`); `"enrol_button"` `button`; `"lms_course_id"` `text`; `"price"` `number` only when no checkout owns it |
728
- | Cohorts | `"course"` `reference`; `"starts_at"`, `"ends_at"` `datetime`; `"time_zone"` `text`; `"seats"` `text` only when shown; `"status"` `select` |
729
- | Instructors | Title is the name. `"role"`, `"photo"`, `"bio"` |
730
-
731
- - **Chrome:** header with a sign-in link to the LMS, footer.
732
- - **Lists:** the catalogue is a `collection` block; category and level filter chips need the
733
- site's code. A fixed category or level list on a hosted page is a block with `source` and a
734
- filter. An instructor's courses are a `multi-reference` or code.
735
- - **Forms:** waitlist, team enquiry, newsletter.
736
- - **Structured data:** `Course` as the collection's `schemaType` (name, description, provider);
737
- offers and course instances from cohorts need pasted `schema`. `FAQPage` on a course with a real FAQ.
738
- - **Redirects:** LMS marketing paths (`/p/course-name`) become `/courses/:slug` plus 301s.
739
- - **Pitfalls:** stored totals ("6 modules, 42 lessons, 8 hours"); lesson videos or paid content
740
- copied in; cohort dates with no zone; prices copied from the checkout.
741
-
742
- ## Job board or careers
743
-
744
- Use when the source lists open roles by department, location tags, Apply buttons, Greenhouse,
745
- Lever or Ashby links, salary ranges, `JobPosting` JSON-LD.
746
-
747
- ### Routes
748
-
749
- | Family | Path | Model |
750
- |---|---|---|
751
- | Careers home, benefits, culture, hiring process | `/careers`, ... | Static pages |
752
- | Job | `/careers/:slug` or `/jobs/:slug` | Jobs collection |
753
- | Department | `/departments/:slug`, only if the source has them | Departments collection |
754
- | Employer (multi-company boards) | `/companies/:slug` | Employers collection |
755
-
756
- ### Collections
757
-
758
- | Collection | Fields |
759
- |---|---|
760
- | Jobs | `"department"` `reference`; `"offices"` `multi-reference` to Locations; `"workplace"` `select` (on site, hybrid, remote); `"employment_type"` `select` (full time, part time, contract, internship); `"salary"` `group` (`"min"`, `"max"` `number`, `"currency"`, `"period"` `select`) only when shown; `"excerpt"`; `"body"` `document`; `"apply_button"` `button` (the ATS URL); `"posted_on"` `date`; `"closes_at"` `datetime`; `"status"` `select` (open, closed); `"ats_job_id"` `text` |
761
- | Departments | `"intro"` `longtext` |
762
- | Employers | `"logo"`, `"about"` `richtext`, `"website"` `link` |
763
-
764
- - **Chrome:** the header carries a "Careers" link; no separate chrome.
765
- - **Lists:** open roles grouped by department with location filter chips need the site's code.
766
- On a hosted page a `collection` block with `source` and a filter on `"status"` lists the open
767
- jobs, one block per department when the source groups them; set `emptyText` for "no open
768
- roles".
769
- - **Forms:** applications go to the ATS. Forms have no file upload, so never collect a CV in a
770
- BetterCMS form. A "Stay in touch" form is fine.
771
- - **Structured data:** `JobPosting` as the collection's `schemaType`: it fills title,
772
- description, posted date, valid through, employment type, hiring organisation and job location
773
- from the entry's fields, and is not emitted without a company and a posting date. A remote type
774
- with applicant location requirements, or a base salary, needs pasted `schema`, only when
775
- visible. Unpublish the entry when the job closes.
776
- - **Redirects:** closed jobs keep their URLs. ATS-hosted paths that move onto the site get a
777
- 301 where the old host allows one.
778
- - **Pitfalls:** hand-copying an ATS (sync by `"ats_job_id"` in code or link out, and report
779
- which); deleting closed jobs; one job duplicated per city; invented salaries.
780
-
781
- ## Real estate or property listings
782
-
783
- Use when the source has properties with price, beds, baths, area, status, galleries, floor
784
- plans, map search, agents, a viewing request form.
785
-
786
- ### Routes
787
-
788
- | Family | Path | Model |
789
- |---|---|---|
790
- | Home, valuation, sell, let, about, contact | `/`, `/valuation`, ... | Static pages |
791
- | Property | `/properties`, `/properties/:slug` | Properties collection |
792
- | Area guide | `/areas/:slug` | Areas collection |
793
- | Agent | `/agents/:slug` | Agents collection |
794
- | Office | `/offices/:slug` | Offices collection, fields as Locations in [Local business](#local-business-or-restaurant) |
795
-
796
- ### Collections
797
-
798
- | Collection | Fields |
799
- |---|---|
800
- | Properties | Title is the display address or name. `"status"` `select` (for sale, for rent, under offer, sold, let); `"listing_type"` `select` (sale, rent); `"price"` `number`; `"currency"` `select`; `"price_period"` `select` (month, week); `"price_qualifier"` `select` (guide price, offers over, price on application); `"address"` `group`; `"map_pin"` `location`; `"property_type"` `select`; `"bedrooms"`, `"bathrooms"`, `"receptions"`, `"area"` `number`; `"area_unit"` `select`; `"features"` `array` of text; `"body"` `document`; `"gallery"` `repeater` (`"image"`, `"caption"`; at least one); `"floor_plans"` `repeater` of images with alt; `"virtual_tour"` `link`; `"energy_rating"` `select`; `"facts"` `repeater` (`"label"`, `"value"`); `"agent"`, `"office"`, `"area_guide"` `reference`; `"available_from"` `date`; `"feed_id"` `text` |
801
- | Agents | Title is the name. `"role"`, `"photo"`, `"phone"` `phone`, `"email"` `email`, `"office"` `reference` |
802
- | Areas | `"intro"`, `"cover_image"`, `"body"` `document`, `"map_pin"` `location` |
803
-
804
- - **Chrome:** header with a valuation button, footer listing offices.
805
- - **Lists:** search by area, type, price band and bedrooms, the map view and "similar
806
- properties" need the site's code or the CRM's search. On a hosted page a `collection` block
807
- with `source` gives a fixed list (one area, one type), never a visitor's search.
808
- - **Forms:** viewing request with the property passed in a `hidden` field, valuation request,
809
- contact.
810
- - **Structured data:** `RealEstateListing` with an `Offer` is pasted `schema` from visible
811
- facts. `schemaType: "LocalBusiness"` on offices. A portal copy keeps the canonical on this
812
- site.
813
- - **Redirects:** sold and let properties keep their URLs and stay published. Portal-style
814
- paths (`/for-sale/london/3-bed-flat-12345`) become `/properties/:slug` plus 301s.
815
- - **Pitfalls:** prices as strings ("£450,000"); status typed into the title ("SOLD - 3
816
- bed..."); listings hand-imported when a CRM feed owns them (sync by `"feed_id"` in code and
817
- report it); images hotlinked from the feed host; floor plans with no alt text; sold
818
- properties deleted.
819
-
820
- ## Nonprofit
821
-
822
- Use when the source has a Donate button, programs, impact numbers, stories, annual reports,
823
- volunteers, a charity number.
824
-
825
- ### Routes
826
-
827
- | Family | Path | Model |
828
- |---|---|---|
829
- | Home, mission, impact, donate, get involved, contact | `/`, `/donate`, ... | Static pages |
830
- | Program or campaign | `/programs/:slug` | Programs collection |
831
- | Story, news | `/stories/:slug` | Stories collection, post fields from [Blog](#blog-or-publication) |
832
- | Report | `/reports/:slug` | Reports collection |
833
- | Events | `/events/:slug` | As [Events](#events-or-conference) |
834
- | Team and board, partners | none unless the source has pages | People, Partners collections |
835
-
836
- ### Collections
837
-
838
- | Collection | Fields |
839
- |---|---|
840
- | Programs | `"excerpt"`; `"cover_image"`; `"body"` `document`; `"impact"` `repeater` (`"value"`, `"label"`, `"as_of"` `date`, `"source"` `link`); `"place"` `reference`; `"is_active"` `boolean`; `"donate_button"` `button` |
841
- | Stories | Post fields, plus `"consented_on"` `date` for any person shown |
842
- | Reports | `"year"` `number`; `"file"` `file`; `"cover_image"`; `"summary"` `richtext` holding the key facts as text |
843
- | Partners | `"logo"`, `"tier"` `select`, `"website"` `link` |
844
- | People | `"role"`, `"photo"`, `"bio"`, `"team"` `select` (staff, board, advisors) |
845
-
846
- - **Chrome:** header with a Donate button; footer with the registered charity number, kept
847
- once in the Global Layout.
848
- - **Donations:** the payment provider (Stripe, Donorbox, Classy, GiveWP) owns amounts,
849
- recurring options and payment fields. Store the provider and its id, plus the copy around
850
- the widget. The widget script stays code.
851
- - **Forms:** volunteer sign-up, newsletter, contact, event registration. Never payment details.
852
- - **Structured data:** `Organization` in `siteSchema` (`NGO` when it fits) with the registered
853
- number, `Article` on stories, `Event` on events.
854
- - **Redirects:** campaign microsite URLs that fold into the main site get 301s to the program.
855
- - **Pitfalls:** impact numbers with no date or source (fill `"as_of"` and `"source"`, report
856
- the rest as unverified); beneficiary photos and stories with no recorded consent (report as
857
- rights unconfirmed); annual reports only as PDFs.
858
-
859
- ## Multi-brand and multi-language
860
-
861
- Use when the source has several brands on one design system, `hreflang` alternates, `/de/` and
862
- `/fr/` paths or country domains, or a language switcher.
863
-
864
- ### Multi-brand
865
-
866
- | Decision | Rule |
867
- |---|---|
868
- | Project boundary | One project per brand and domain. Each has its own brand kit, tokens, Global Layout and SEO settings |
869
- | Starting a new brand | Build one template project, then `clone_project` per brand. The clone copies pages, models, entries, components, media, forms and SEO settings; it leaves out submissions, analytics, domains and secrets |
870
- | Shared sections | The cloned components. After the clone they are separate copies: a later change is applied per project |
871
- | Shared content | Projects cannot reference each other's entries. Copy it and note where it came from, or serve it from one project that every brand's code reads. Report the choice |
872
- | Routes | Each brand keeps its own routes and `urlPattern` values; identical patterns across brands are fine |
873
- | Structured data | One `Organization` per brand in its own `siteSchema`. A parent company is a `parentOrganization` property, not a shared node |
874
-
875
- ### Multi-language
876
-
877
- Full method: [localization.md](localization.md). The decisions that shape a blueprint:
878
-
879
- - A translation is its own page or entry carrying `locale` and `translationOf` (the source id).
880
- Per-field localization does not exist. Never model `"title_en"` beside `"title_fr"`.
881
- - When crawls disagree on the default locale, take the one the root path serves and report
882
- it; do not ask. The default fills the source rows; every other locale links back to them.
883
- - Match sections across locales by position and structure, never by text.
884
- - Never translate during a conversion. Copy what each locale publishes; report missing
885
- translations. `translate_entry` creates drafts, costs AI credits and runs only on request.
886
- - Slugs are per locale; keep each locale's URLs. Alt text is copy and is translated; the image
887
- is shared unless a locale shows a different one.
888
- - `hreflang` is never a field. The sitemap emits it from `locale` and `translationOf`; the
889
- hosted head does not, so a site that needs it in the head renders the links in its own code.
890
- - A market page with a different structure (a US-only pricing page) is a separate page, not a
891
- translation. A per-market header is a Layout locale variant, not a translation of copy.
892
- - **Structured data:** one canonical per locale. Never canonical every locale to the default.
893
- - **Redirects:** no IP-based redirects. A retired locale 301s to the default locale's page.
894
- - **Pitfalls:** machine translation during import; one model with a field per language; a
895
- switcher linking to a page that exists in one locale only.
1
+ ---
2
+ title: "Site blueprints"
3
+ description: "Route, collection, form, structured-data and redirect plans per business type: portfolio, agency, corporate, SaaS/AI, ecommerce, directory, blog or publication, docs or knowledge base, local business or restaurant, events, course, job board, real estate, nonprofit and multi-brand."
4
+ ---
5
+
6
+ # Site blueprints by business type
7
+
8
+ A route and content model that fits the business, while editor folders, public URLs and reusable
9
+ components stay separate. A blueprint is a starting hypothesis, not permission to replace a
10
+ working site's taxonomy. The platform rules behind each layer are in
11
+ [content-modeling.md](content-modeling.md).
12
+
13
+ ## Contents
14
+
15
+ - [Common decision process](#common-decision-process)
16
+ - [Starter rules](#starter-rules)
17
+ - [Deliverables](#deliverables)
18
+ - [Production shapes every blueprint uses](#production-shapes-every-blueprint-uses): foundations,
19
+ field types, list limits, URL limits, structured data, forms, checklist
20
+ - Catalogs, read only the one that matches (a mixed site can use two, but give each page family
21
+ one route owner):
22
+ - [Portfolio](#portfolio-website): a person or studio presents selected work, services and a
23
+ contact path
24
+ - [Agency](#agency-website): services, sectors, proof and lead generation drive the site
25
+ - [Corporate](#corporate-website): several business units, regions, audiences, governance or
26
+ regulated content coexist
27
+ - [SaaS, AI, enterprise tech](#saas-ai-and-enterprise-technology-website): product,
28
+ solutions, industries, integrations, trust and education need separate models
29
+ - [Large ecommerce](#large-ecommerce-website): catalog, categories, products and shopping
30
+ journeys dominate; a Shopify-backed store also reads [shopify-headless.md](shopify-headless.md)
31
+ - [Directory](#directory-or-listings-website): hundreds of same-shaped listings with category
32
+ and location hubs
33
+ - [Blog / publication](#blog-or-publication): dated articles with bylines, taxonomy and a feed
34
+ - [Docs / knowledge base](#docs-or-knowledge-base): a navigation tree, code, versions, search
35
+ - [Local business / restaurant](#local-business-or-restaurant): address, hours, menu, booking
36
+ - [Events](#events-or-conference): dated events, agenda, speakers, venue, sponsors
37
+ - [Course](#course-or-education): courses, curriculum, instructors, cohorts
38
+ - [Job board](#job-board-or-careers): open roles, departments, an ATS
39
+ - [Real estate](#real-estate-or-property-listings): properties, agents, offices, areas
40
+ - [Nonprofit](#nonprofit): programs, impact, stories, reports, donations
41
+ - [Multi-brand](#multi-brand-and-multi-language): several brands or languages on one design
42
+ system
43
+
44
+ Data-generated page families (glossary, integrations, locations): [programmatic-seo.md](programmatic-seo.md).
45
+
46
+ ## Common decision process
47
+
48
+ 1. Inventory existing public URLs, navigation, templates, source content, business goals,
49
+ locales, owners, analytics and design tokens. Keep established URLs and brand rules unless a
50
+ change is in scope.
51
+ 2. Give each family a **source of truth**: a BetterCMS page, a collection entry, an external
52
+ system (commerce, ATS, docs), or application runtime. Name its editorial owner and publishing
53
+ gate. Record capabilities BetterCMS does not have as gaps; never invent fields that imply
54
+ them.
55
+ 3. Produce a route matrix: pattern, purpose, static / list / detail, content source, template,
56
+ canonical, indexability, sitemap eligibility, and the redirect it replaces. A collection's
57
+ list is a page; its entries render through one dynamic detail page.
58
+ 4. Produce a model matrix: fields and groups, references, taxonomy, reusable sections, global
59
+ nav/footer, labels, and variants. Components for reusable presentation, collections for
60
+ entities with their own lifecycle. Whether bands become components or fields follows
61
+ `authoringPreference` from `get_project`, which only the owner answers.
62
+ 5. Propose **Content sidebar** folders for editor tasks, **component groups** for definitions,
63
+ and **media folders** for assets. None of these changes a URL. **URL page folders** do change
64
+ paths, and renaming one 301s its pages, so they need explicit review. Sidebar doctrine:
65
+ `bettercms://playbook/structure`.
66
+ 6. Define per-family SEO policy: one canonical target, real indexable content, native SEO
67
+ metadata (never modelled fields, §5b of `bettercms://playbook/schema`), structured data only
68
+ where visible facts support it, and the treatment of filters, search, pagination, drafts,
69
+ gated pages and deleted records. Pages marked noindex are left out of the generated sitemap.
70
+ 7. Validate one representative route per family and the edge cases: empty collection, long
71
+ title, unpublished detail, duplicate slug, moved URL, mobile nav, optional section, locale,
72
+ no-result state. Confirm edit, preview and public render, then the build and binding reports.
73
+
74
+ ## Starter rules
75
+
76
+ - Reference page lists illustrate common shapes; do not create pages the business does not have.
77
+ - Prefer a few stable, human-readable route patterns. The best URL is often the existing one.
78
+ - Keep a slug unique within its route namespace; check static against dynamic paths and locales.
79
+ When a published URL changes, add a redirect (`create_redirect`).
80
+ - Keep a dedicated 404 and access/error pages. Leave drafts, private areas, internal search,
81
+ parameter duplicates and 404s out of the sitemap.
82
+ - Sitemaps hold absolute, canonical, indexable URLs; Google documents a limit of 50,000 URLs /
83
+ 50 MB uncompressed per file. [Google sitemap guide](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap)
84
+ - Content sidebar folders are navigation for editors. Public hierarchy comes from routes and
85
+ internal links, never from folder labels. [Google ecommerce site structure](https://developers.google.com/search/docs/specialty/ecommerce/help-google-understand-your-ecommerce-site-structure)
86
+ - With locale variants, keep explicit equivalent-page mappings and one `hreflang`
87
+ implementation, verified in the rendered head. [Google localized versions](https://developers.google.com/search/docs/specialty/international/localized-versions)
88
+
89
+ ## Deliverables
90
+
91
+ Return a route tree, route matrix, content and model matrix, folder plan, component inventory,
92
+ sitemap and redirect policy, open ownership decisions, and a phased implementation plan. On a
93
+ live project, implement only the scope the user asked for and verify the routes you touched.
94
+
95
+ ## Production shapes every blueprint uses
96
+
97
+ Read this once; every catalog below assumes it. A site is often two types (a SaaS site with a
98
+ blog and docs): open each matching catalog, merge them, and create shared collections (Authors,
99
+ Topics, FAQs, Testimonials) once. Drop any collection the source does not have.
100
+
101
+ ### Foundations
102
+
103
+ | Piece | Where it lives |
104
+ |---|---|
105
+ | Header, footer, announcement bar | Global Layout via `update_layout`. Never a page section. Footer link columns are a repeater of groups; the newsletter box points at a form. A bar or promo that runs for a period takes a `window` on its Section, and a seasonal band one on its page block ([publishing.md](publishing.md) §6) |
106
+ | Company name, address, phone, social profiles | One place in the Global Layout, read by the footer and the contact page. Site JSON-LD repeats it once in `siteSchema` (`update_seo`) |
107
+ | Cookie consent | A third-party consent tool stays code. Report it; do not rebuild it as content |
108
+ | 404, legal, thank-you pages | Static pages in a `System` Content sidebar folder. Thank-you pages are noindex |
109
+ | Meta and structured data | Native per page and entry, site defaults with `update_seo` ([seo.md](seo.md)). Never modelled |
110
+
111
+ ### Field types
112
+
113
+ Field keys below are entry keys in one flat namespace per model. `title` and the slug are on
114
+ every entry and are not repeated in the tables. SEO fields are never listed.
115
+
116
+ | The source shows | Production field |
117
+ |---|---|
118
+ | Short text, long plain text | `text`, `longtext` |
119
+ | The one long body | `document` (one per model, top level). Shorter formatted copy is `richtext` |
120
+ | Image, download | `image`, `file` |
121
+ | Postal address | `group` of `text` children: `"street"`, `"city"`, `"region"`, `"postal_code"`, `"country"` |
122
+ | Map pin | `location` (lat, lng, optional label). Never two text fields |
123
+ | Tags, amenities, bullet lists | `array` with `itemType: "text"`. One choice from a fixed list is `select` |
124
+ | Rows of the same shape (hours, facts, metrics, tickets) | `repeater` of `text`, `number`, `select`, `boolean` children |
125
+ | Mixed ordered blocks in a case study | one component per block shape, placed as sections |
126
+ | One or many other entries | `reference`, `multi-reference`. A missing target is refused on write |
127
+ | Call to action | `button` (label and link in one value). A bare link is `link`; also `email`; a phone number is `text` |
128
+ | Date, date and time | `date`, `datetime`. Keep the zone as a `text` IANA name beside it when the site shows times |
129
+ | A spec or comparison table | `repeater` with one field per column (any field type). There is no separate table field type |
130
+
131
+ An external system's id (`"store_product_id"`, `"ats_job_id"`, `"feed_id"`, `"lms_course_id"`)
132
+ is a `text` field kept as migration metadata and locked: `readOnly: "developer"` with a
133
+ `lockReason` naming the system it comes from. Place it last. A write that changes it without
134
+ schema access is refused (403 FIELD_LOCKED). The lock is enforced on entries only.
135
+
136
+ ### Lists
137
+
138
+ A collection's list is its own dynamic page holding one `collection` block. The block takes
139
+ `limit` (1 to 200, default 20), `order` (`newest` or `oldest`, by creation time), `emptyText`,
140
+ and optional card and detail components. Entry URLs are that page's path plus the entry slug.
141
+
142
+ With `source: { modelId }` the block lists that collection on any hosted page, and takes
143
+ `filters` (max 5; `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `in`), `sort`
144
+ (`-publishedAt,title`), `pinnedIds` (max 10, shown first) and `excludeCurrent`. In an article
145
+ template a filter value may be `{{field}}` of the entry shown. Filter values are stored on the
146
+ block: a visitor cannot change them, and none means "today".
147
+
148
+ Not available: pagination, grouping, visitor-driven filters. Only the hosted renderer draws the
149
+ block; a route built from the repo's code queries the Delivery API with the same filter grammar.
150
+ So for each hub the source has, choose and report one of:
151
+
152
+ | Source list | Production shape |
153
+ |---|---|
154
+ | All entries, newest first | The `collection` block |
155
+ | Filtered by category, location or status | On a hosted page a `collection` block with `source` and `filters`. On a code-built site the site's own code filters the delivered entries |
156
+ | Sorted by a field (A to Z, a date, an order number) | `sort` on a sourced block |
157
+ | Upcoming or past, by today's date | The site's code, or a `"status"` field the block filters on |
158
+ | "Related" row | On a hosted template, a sourced block filtered by a `{{field}}` of the entry shown, with `excludeCurrent` |
159
+ | "Featured" row | `pinnedIds`, a filter on a featured flag, or a short `multi-reference` chosen by hand |
160
+ | Numbered pages, groups under headings, filter chips | The site's code |
161
+ | Counts ("1,240 venues", "42 lessons") | Rendered by code from the entries. Never typed into copy |
162
+
163
+ ### URLs and redirects
164
+
165
+ - Every collection gets a `urlPattern` holding `:slug` exactly once (`/blog/:slug`). A fixed
166
+ multi-segment prefix is valid (`/docs/v2/:slug`).
167
+ - Nested (`/:category/:listing`) and dated (`/2024/05/my-post/`) source paths cannot be
168
+ expressed. Use the flat pattern and add one `create_redirect` (301) per old URL.
169
+ - A slug change on an entry with a pattern 301s by itself.
170
+ - Redirect types are 301, 302, 307, 308 and 410. A retired entry either stays published and
171
+ marked ended, redirects to a successor, or is unpublished and answers 410 (`create_redirect`
172
+ with `redirectType: "410"`, no destination).
173
+
174
+ ### Structured data
175
+
176
+ Order: Automatic, then `schemaType`, then pasted `schema`. `schemaType` accepts `WebPage`,
177
+ `AboutPage`, `ContactPage`, `CollectionPage`, `Blog`, `BlogPosting`, `Article`, `Product`,
178
+ `FAQPage`, `Event`, `Organization`, `LocalBusiness`, `JobPosting`, `Course`, `Recipe`, `HowTo`,
179
+ `Service`, `Person`, `VideoObject`. What the common ones emit from the content:
180
+
181
+ | `schemaType` | Emits |
182
+ |---|---|
183
+ | `BlogPosting`, `Article` | Headline, description, image, published and modified dates, author |
184
+ | `Product` | Name, description, image, SKU, offer |
185
+ | `Event` | Name, description, image, start, end, a location name |
186
+ | `LocalBusiness`, `Organization` | Name, description, URL, image or logo, telephone, email, address as one line |
187
+ | `FAQPage` | The page's question and answer pairs. Never automatic |
188
+
189
+ Anything richer is pasted `schema` built only from facts visible on the page: opening hours, geo
190
+ coordinates, event status and offers, salary, `RealEstateListing`, `Restaurant`,
191
+ `SoftwareApplication`, `NewsArticle`. A hosted page gets its `BreadcrumbList` automatically. Set a
192
+ collection-wide default in `collectionSchemaTypes`. Never a self-serving `AggregateRating`.
193
+
194
+ ### Forms
195
+
196
+ Thirteen field types: `text`, `textarea`, `email`, `phone`, `number`, `url`, `date`, `select`,
197
+ `radio`, `checkbox`, `checkboxes`, `consent`, `hidden`, plus the `step` divider for multi-step forms. No file upload, so CVs, floor plans and
198
+ attachments go to the external system. Payment details never go in a form. Submissions land in
199
+ the inbox; an editor turns one into an entry. Full pipeline:
200
+ [forms-and-interactions.md](forms-and-interactions.md).
201
+
202
+ ### Checklist
203
+
204
+ - Every collection exists in the source, or was dropped on purpose.
205
+ - Shared collections exist once across merged catalogs.
206
+ - Store, ATS, LMS and feed facts are ids, never copies.
207
+ - Every list has `emptyText` and a stated shape for each filter the source had.
208
+ - Every collection has a `urlPattern`; every changed URL has a 301.
209
+ - Structured data is built from visible fields, with the type its catalog names.
210
+
211
+ ### Built-in sections
212
+
213
+ The section library ships 23 platform blueprints in 17 families, so a standard band needs no new
214
+ component: Hero, Feature grid (three, two, bento), Feature rows, Steps, Timeline, Stats, Logo cloud,
215
+ Testimonial (quote, three cards), Team, Case study, Comparison, Pricing, FAQ, Call to action,
216
+ Capture (newsletter band) and Listing (latest, related). Read one with `list_components`; inline its
217
+ `blockJson` as a `section` block for a one-off band, or copy it with `create_component` `from:
218
+ "builtin:<key>"` for a recurring one (see [components.md](components.md)). Two ship unbound on purpose: the Capture form has no form until you
219
+ set its `form` prop, and both Listing layouts list the page's own entries until you give them a
220
+ `source`; each shows its empty text meanwhile. Not available as a section, so build it in the repo
221
+ or leave it out: mega menu, docs sidebar, cookie consent, exit intent, store locator, map, billing
222
+ toggle, countdown and anything commerce.
223
+
224
+ ## Portfolio website
225
+
226
+ Use this when the primary job is to show selected work and make an individual or small studio credible. First determine whether the work is private, client-approved, dated, or still under embargo.
227
+
228
+ ### Route and model starter
229
+
230
+ | Family | Example path | BetterCMS model | Index and sitemap |
231
+ |---|---|---|---|
232
+ | Home | `/` | Static page with selected projects and clear contact action | Yes |
233
+ | Work index | `/work/` | Static index page querying Projects | Yes if substantive |
234
+ | Project detail | `/work/{slug}/` | Projects collection + one dynamic template | Published, approved projects only |
235
+ | About / services | `/about/`, `/services/` | Static pages unless services need independent owners and detail pages | If public and useful |
236
+ | Writing / speaking | `/writing/`, `/writing/{slug}/` | Optional collection and index | Published articles only |
237
+ | Contact | `/contact/` | Static page + form | Usually yes |
238
+ | Password/preview case study | Existing route | Other page or protected runtime route | No |
239
+
240
+ Keep the existing work prefix if it is `/projects/`, `/case-studies/`, or another established route; route changes need a redirect map.
241
+
242
+ ### Editor structure
243
+
244
+ Content sidebar: `Start here` (Home, About, Contact), `Portfolio` (Work index, Projects), `Writing` if used, `Site essentials` (legal/other). Component groups: `Globals`, `Portfolio` (project hero, gallery, outcomes), `Shared sections` (CTA, quote, logo strip). Media folders may separate approved client work, self-initiated work, portraits, and downloadable files. These folders do not determine URLs.
245
+
246
+ Project entry fields: display title, slug, client display name, service/category references, summary, role, dates when relevant, hero image plus alt text, ordered gallery items with captions/alt, challenge, approach, verified outcomes, credits, related projects, SEO, approval/visibility state. Do not require a public client name, metric, or testimonial when consent is absent. Support anonymous case studies without accidentally exposing the original client in image filenames, alt text, schema, or social previews.
247
+
248
+ Use repeatable gallery items rather than a long list of `image1` fields. Give each item a meaningful editor label and image metadata. Keep project-specific storytelling sections as instances; make hero/gallery/quote layouts reusable definitions with instance overrides.
249
+
250
+ ### Validation
251
+
252
+ Check case-study consent, working gallery at different aspect ratios, captions, full-size media performance, downloadable file links, contact submission, related-work references, archived projects, and redirect behavior for renamed slugs. A portfolio with only a few projects does not need auto-generated service × industry pages.
253
+
254
+ ### Production detail
255
+
256
+ Projects collection, `urlPattern` `/work/:slug` (or the established prefix):
257
+
258
+ | Field key | Type | Notes |
259
+ |---|---|---|
260
+ | `"client"` | `reference` to Clients, or `text` | A reference only when clients have a logo or page of their own |
261
+ | `"year"` | `number` | |
262
+ | `"services"` | `multi-reference` to Services, or `array` of text | References when service pages exist |
263
+ | `"cover_image"`, `"excerpt"` | `image`, `longtext` | Words baked into a hero image move into fields |
264
+ | `"modules"` or `"body"` | components, or `document` | one component per block (text, image, gallery, quote, video, stats) for image-led studies; `document` when mostly prose |
265
+ | `"results"` | `repeater`: `"value"`, `"label"` | Only verified outcomes |
266
+ | `"credits"` | `repeater`: `"role"`, `"name"` | |
267
+ | `"testimonial"` | `reference` to Testimonials | |
268
+ | `"live_link"` | `link` | |
269
+ | `"is_featured"` | `boolean` | The home grid is a hand-picked `multi-reference` or code filtering on this |
270
+
271
+ - **Chrome:** a minimal header and a footer with contact details, both in the Global Layout.
272
+ - **Forms:** one enquiry form (name, email, message, consent).
273
+ - **Structured data:** `Article` on projects, `AboutPage` and `ContactPage` on those pages,
274
+ `Organization` or a `Person` node in `siteSchema`.
275
+ - **Pitfalls:** videos without a poster or captions; scroll-driven storytelling flattened into
276
+ one blob (keep each step a `modular` item); downscaled uploads (upload the original once, the
277
+ media pipeline serves sizes); client work under NDA published without confirmed rights (flag
278
+ it in the report and ask).
279
+
280
+ ## Agency website
281
+
282
+ Use when the site must explain services, prove results, and convert qualified leads. The key distinction from a portfolio is a durable offer taxonomy and repeatable proof linked to each offer.
283
+
284
+ ### Route and model starter
285
+
286
+ | Family | Example path | Model and purpose |
287
+ |---|---|---|
288
+ | Home | `/` | Static overview, positioning, primary CTA |
289
+ | Services index/detail | `/services/`, `/services/{slug}/` | Static index + Services collection only if multiple services have owners, proof, and maintained detail content; otherwise static pages |
290
+ | Industries | `/industries/`, `/industries/{slug}/` | Optional collection; publish only distinct industry expertise |
291
+ | Work index/detail | `/work/`, `/work/{slug}/` | Case studies collection and dynamic detail template |
292
+ | Insights | `/insights/`, `/insights/{slug}/` | Editorial collection, authors, categories |
293
+ | Agency | `/about/`, `/team/`, `/careers/` | Static or collection by update cadence |
294
+ | Contact | `/contact/` | Form with routing/consent behavior |
295
+
296
+ Use the established `/case-studies/`, `/blog/`, etc. when the current site already has them. A service × industry matrix should be created only for combinations with distinct evidence, offer, and maintenance owner; otherwise link the existing service and industry pages instead of generating thin duplicates.
297
+
298
+ ### Content relationships and components
299
+
300
+ Cases reference services, industries, client permission state, outcome evidence, and optional quotes. Service pages reference relevant cases, deliverables, process, FAQ, and CTA. Industry pages reference services/cases and sector-specific language. Avoid duplicating client logos or metrics across pages: maintain permission and source context on the case/client record.
301
+
302
+ Component groups: `Globals` (nav/footer), `Proof` (case card, metrics, testimonial), `Offers` (service card, process), `Conversion` (CTA, contact form), `Editorial`. Reuse definitions with page-specific props, and allow section order to vary by page. Editors should see grouped fields such as `Primary button → Label, Link`, `Proof item → Metric, Context, Source`.
303
+
304
+ Content sidebar folders should match editor work: `Core pages`, `Services`, `Industries`, `Case studies`, `Insights`, `Company`. These are dashboard folders, not paths.
305
+
306
+ ### Validation
307
+
308
+ Review lead form destination, attribution and consent, internal links from every offer to relevant proof, case-study rights, schema claims, duplicate service routes, old URL redirects, and mobile CTA visibility. Keep draft or client-confidential work out of sitemaps and public data feeds.
309
+
310
+ ### Production detail
311
+
312
+ | Collection | `urlPattern` | Fields |
313
+ |---|---|---|
314
+ | Case studies | `/work/:slug` | The Projects fields in [Portfolio](#portfolio-website), plus `"industry"` `reference` |
315
+ | Services | `/services/:slug` | `"icon"` `image`, `"excerpt"` `longtext`, `"body"` `document`, `"related_work"` `multi-reference` |
316
+ | Clients | none (no detail page) | `"logo"` `image` with the company name as alt, `"website"` `link` |
317
+ | Team | `/team/:slug`, only when profiles have pages | `"role"` `text`, `"photo"` `image`, `"bio"` `longtext`, `"social_links"` `repeater`: `"network"` `select`, `"url"` `link` |
318
+ | Testimonials | none | `"quote"` `longtext`, `"name"`, `"role"`, `"company"` `text`, `"photo"`, `"logo"` `image` |
319
+
320
+ - **Lists:** "work by service" on a service page is `"related_work"` picked by hand, code
321
+ filtering the delivered case studies, or on a hosted page a `collection` block with `source`
322
+ and a filter on the service.
323
+ - **Forms:** a new-business enquiry with `select` fields for budget and timeline, plus `hidden`
324
+ fields for campaign attribution.
325
+ - **Structured data:** `Organization` in `siteSchema`, `Article` on case studies, `WebPage` on
326
+ services, `FAQPage` only where a service page shows real questions. A `Person` node per team
327
+ page is pasted `schema`.
328
+ - **Pitfalls:** a logo wall with no alt text; client names retyped per page instead of
329
+ referenced; a testimonial with no record of consent (keep a `"consented_on"` `date`).
330
+
331
+ ## Corporate website
332
+
333
+ Use for multiple audiences, brands, regions, business units, approval chains, or regulated statements. The main modeling problem is ownership: who can update a statement, which sites reuse it, and when it expires.
334
+
335
+ ### Route and model starter
336
+
337
+ | Family | Example path | Owner and model |
338
+ |---|---|---|
339
+ | Corporate home | `/` | Global communications; static page |
340
+ | Business units | `/businesses/{slug}/` | Collection if units share a template and lifecycle |
341
+ | Products/services | Existing taxonomy | Collection or static pages by unit ownership |
342
+ | Newsroom | `/news/`, `/news/{slug}/` | News collection, media contact, publish date |
343
+ | Leadership | `/leadership/`, `/leadership/{slug}/` | People collection if profiles recur |
344
+ | Careers | `/careers/` | Landing page; job details may remain in an ATS |
345
+ | Investor / governance | `/investors/`, `/governance/` | Gated approval; files with version/date |
346
+ | Policies and legal | `/privacy/`, `/terms/` | Static pages with accountable owner |
347
+ | Contact / locations | `/contact/`, `/locations/{slug}/` | Form plus locations collection when needed |
348
+
349
+ Do not copy ATS jobs, stock prices, investor filings, or regulated data into BetterCMS unless BetterCMS is deliberately the system of record. Preserve external systems and public links.
350
+
351
+ ### Governance and editor structure
352
+
353
+ Record owner, reviewer, source of claim, effective date, review date, and version where these matter. Separate draft, legal-reviewed, approved, and published states with the project's own editorial board: read its stages and which one is the publish gate with `get_workflow_board` before `move_stage`, because stage keys are per project. Do not claim an approval stage exists if the board has none. Scope access and publishing by the product's real permissions. High-risk copy (financial figures, certifications, compliance claims, regulated products) needs a verified source and designated reviewer.
354
+
355
+ Content sidebar: `Corporate pages`, `Business units`, `Newsroom`, `People`, `Investors`, `Legal`, `Regions` if used. Component groups: `Globals`, `Corporate identity`, `Business unit`, `Newsroom`, `Data/proof`. Distinguish sitewide global identity from unit-specific branding and local navigation. A shared global component should not overwrite an independently branded unit page.
356
+
357
+ ### Regions and languages
358
+
359
+ Choose a deliberate URL strategy, such as `/{locale}/...` or regional domains. Keep locale-to-locale equivalent-page IDs, fallback behavior, canonical target, and language switcher destinations. Check what the connected project models for locales before planning; implement and verify `hreflang` output in the frontend, where the rendered head is decided. Do not auto-redirect by IP or index machine placeholders. Follow [Google's localized-page guidance](https://developers.google.com/search/docs/specialty/international/localized-versions).
360
+
361
+ ### Validation
362
+
363
+ Test unit-specific nav/footers, ownership permissions, embargoed news, expiring files, superseded legal pages, regional links, localization fallbacks, 404/redirects, accessibility, and the published head for every major template. Separate search visibility from access control: `noindex` does not protect private information.
364
+
365
+ ## SaaS, AI, and enterprise technology website
366
+
367
+ Use when a product site must explain a platform to multiple buyers and evaluators. Distinguish product truth, marketing interpretation, developer documentation, and live application behavior.
368
+
369
+ ### Route and model starter
370
+
371
+ | Family | Example path | Suggested ownership |
372
+ |---|---|---|
373
+ | Home | `/` | Static marketing composition |
374
+ | Product/platform | `/product/`, `/features/{slug}/` | Static overview; Features collection only if repeated and maintained |
375
+ | Solutions | `/solutions/{slug}/` | Collection for distinct jobs-to-be-done/use cases |
376
+ | Industries | `/industries/{slug}/` | Collection where sector-specific workflows/proof exist |
377
+ | Integrations | `/integrations/{slug}/` | Collection with provider status, auth, capability, owner |
378
+ | Customers | `/customers/{slug}/` | Case studies with approvals and proof sources |
379
+ | Pricing | `/pricing/` | Static page or runtime-driven product data; one owner for actual pricing |
380
+ | Resources | `/resources/{slug}/`, `/blog/{slug}/` | Editorial collections and their list pages |
381
+ | Glossary | `/glossary/{term}/` | Definition collection with editorial gates |
382
+ | Comparisons / alternatives | `/compare/{slug}/` | Dedicated reviewed model, never blind competitor templating |
383
+ | Trust and legal | `/security/`, `/privacy/`, `/trust/` | Static pages or external trust center |
384
+ | Docs / API | `/docs/...` | Often a separate docs system/repository; link rather than duplicate |
385
+ | Conversion | `/demo/`, `/contact/` | Forms with routing, consent, and success states |
386
+
387
+ These are options, not required pages. Keep existing routes and product naming. For a large enterprise site, consider ownership by product line/market and a shared navigation contract; avoid separate page schemas for identical layouts.
388
+
389
+ ### Model and section kit
390
+
391
+ Entities: Solutions, Industries, Features, Integrations, Cases, Resources, Glossary Terms, and optional Comparisons. Use references across them so an integration or proof item updates once. Each entry needs an owner, lifecycle, canonical URL, native SEO metadata, and sufficient unique content. Integration entries need a clear status (live, beta, planned) and must never imply a planned connector is available.
392
+
393
+ Shared sections: product hero, outcome grid, workflow steps, feature visual, integration grid, proof strip, case excerpt, FAQ, security/trust callout, pricing CTA. Keep a single component definition per visual family and use variants/instance overrides. Give buttons and media grouped editor controls. Expose only supported rich-text headings; a hero title may be plain text rendered as H1 while an article body may allow H2/H3, links, lists, and code if the renderer supports them.
394
+
395
+ Content sidebar: `Core pages`, `Product`, `Solutions`, `Industries`, `Integrations`, `Customers`, `Resources`, `Trust`, `Other`. Component groups should reflect role, not route.
396
+
397
+ ### Search and AEO
398
+
399
+ Build topic hubs and link related solution, industry, integration, case, and glossary pages. Only publish a specific solution × industry page when it adds differentiated workflow, evidence, and value. Answer questions directly with verifiable facts and useful context; do not fabricate FAQ, schema, or “AI answer” pages. Use [programmatic-seo.md](programmatic-seo.md) for generated page families and [Google's AI-search guidance](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide) for the current search-policy boundary.
400
+
401
+ ### Validation
402
+
403
+ Verify product claims against shipped capability, integration statuses, pricing consistency, demo form routing, docs links, internal topic paths, glossary duplicates, comparison accuracy, rendering of code/rich text, and desktop/mobile navigation. Noindex preview, gated assets, and incomplete pages.
404
+
405
+ ### Production detail
406
+
407
+ | Collection | `urlPattern` | Fields |
408
+ |---|---|---|
409
+ | Case studies | `/customers/:slug` | `"customer"` `text`, `"logo"`, `"cover_image"` `image`, `"industry"` `reference`, `"excerpt"` `longtext`, `"metrics"` `repeater` (`"value"`, `"label"`, at most 3 rows), `"quote"` `reference` to Testimonials, `"body"` `document` with challenge, solution, results as H2s, `"facts"` `repeater` (`"label"`, `"value"`) |
410
+ | Testimonials | none | `"quote"`, `"name"`, `"role"`, `"company"`, `"photo"`, `"logo"`, `"source"` `link`, `"consented_on"` `date`. `"rating"` `number` only when the source shows one |
411
+ | Integrations | `/integrations/:slug` | `"logo"`, `"category"` `reference`, `"excerpt"`, `"body"` `document`, `"app_link"` `button`, `"docs_link"` `link`, `"is_native"` `boolean`, `"status"` `select` (live, beta, planned) |
412
+ | Releases | `/changelog/:slug`, or none when releases are anchors on one page | `"version"` `text`, `"published_on"` `datetime`, `"changes"` `repeater` (`"kind"` `select`: new, improved, fixed; `"text"`), `"body"` `richtext` |
413
+ | Comparisons | `/compare/:slug` | `"competitor"` `text`, `"competitor_logo"`, `"intro"` `longtext`, `"matrix"` `repeater` (`"feature"`, `"us"`, `"them"`), `"verified_on"` `date`, `"body"` `document` |
414
+
415
+ - **Static until repeated:** feature and product pages stay static pages until three or more
416
+ share one layout. Pricing tiers stay in the pricing page's fields unless the same plans show
417
+ on two or more pages; then a Plans collection.
418
+ - **Pricing:** store the monthly and the yearly price as two fields read from the source. Never
419
+ derive one from a discount. The full feature grid is a `repeater` whose row holds one value
420
+ per plan; inside a component it can be the `table` prop ([components.md](components.md)).
421
+ - **Lists:** customer and integration hubs are one `collection` block each. Category filter
422
+ chips are code on a code-built site; otherwise leave them out and say so. A home logo wall is
423
+ a `repeater` of images, or a `multi-reference` when logos link to stories.
424
+ - **Forms:** demo request and contact sales with `hidden` attribution fields, and a newsletter
425
+ form with `consent`. A gated asset is a form whose success state links the file.
426
+ - **Structured data:** `Organization` and a `WebSite` node in `siteSchema`, `FAQPage` on the
427
+ pricing page (one per URL), `BlogPosting` on posts. `JobPosting` as the careers collection's
428
+ `schemaType`; `SoftwareApplication` is pasted `schema` from visible facts.
429
+ - **Pitfalls:** "Trusted by 10,000 teams" style counters with no source (report as unverified);
430
+ a feature matrix pasted as an image; a screenshot holding the only copy of a claim.
431
+
432
+ ## Large ecommerce website
433
+
434
+ Use for a catalog-heavy site regardless of commerce engine. The core choice is the boundary between **commerce facts** and **editorial merchandising**.
435
+
436
+ ### Source-of-truth contract
437
+
438
+ Products, variants, SKUs, price, availability, tax, orders, checkout, customer accounts, and fulfillment normally belong to the commerce platform. BetterCMS owns editorial landing pages, category storytelling, buying guides, banners, proof, and selected product-page content blocks. A CMS reference to a product uses a stable external ID plus a verified lookup; don't silently duplicate transactional fields into editable CMS text.
439
+
440
+ ### Route and model starter
441
+
442
+ | Family | Example path | Index policy |
443
+ |---|---|---|
444
+ | Home and campaigns | `/`, `/campaigns/{slug}/` | Public, unique campaigns |
445
+ | Catalog root / category | `/shop/`, `/collections/{slug}/` | Curated category pages with products and links |
446
+ | Product | `/products/{slug}/` | Available public products; decide retired-product policy |
447
+ | Brand | `/brands/{slug}/` | Only substantive brand pages |
448
+ | Buying guides | `/guides/{slug}/` | Unique advice with product references |
449
+ | Editorial | `/stories/{slug}/` | Published articles |
450
+ | Search, cart, account, checkout | Runtime routes | Usually excluded from sitemap; private data protected |
451
+ | Filters and sort | Parameters on category routes | Explicit crawl/index policy; avoid unbounded URL combinations |
452
+
453
+ Keep existing paths if the store already uses another taxonomy. Decide whether unavailable products stay indexable, redirect to a successor, or are unpublished and return 404 or a 410 rule, based on permanence and user value. Do not redirect every missing product to the homepage.
454
+
455
+ ### Editor model and reuse
456
+
457
+ Models: Campaigns, Buying Guides, optional Brand and Category editorial records, plus product references. Category merchandising groups can include hero, intro, featured products, editorial cards, filters, and FAQs. Components: global store nav, search, promo bar, product card, variant selector, add-to-cart, category hero, recommendation rail, cart feedback. Commerce controls remain functional code tied to live data; an editor can change allowed presentation and copy but must not create an unfulfillable cart action.
458
+
459
+ Content sidebar: `Campaigns`, `Collections`, `Guides`, `Stories`, `Store pages`; group media by campaigns/editorial needs. Content sidebar folders never change a URL; URL page folders do, and renaming one 301s its pages, so do not turn category folders into URL folders unless a route change is intended.
460
+
461
+ ### Crawl and scale
462
+
463
+ Provide crawlable category → subcategory → product links and paginated result URLs. Decide which facet combinations deserve dedicated curated landing pages; keep the rest out of the index/crawl space by an intentional policy. Do not rely on canonical alone to control a near-infinite faceted URL space. Keep canonical, internal link, and sitemap URLs consistent. Sitemaps should include only eligible canonical pages and split before protocol limits. Product price/availability markup must match the current displayed commerce data; validate variant markup against Google's current `ProductGroup` guidance. [Google ecommerce structure](https://developers.google.com/search/docs/specialty/ecommerce/help-google-understand-your-ecommerce-site-structure) · [Faceted navigation](https://developers.google.com/crawling/docs/faceted-navigation) · [Product variants](https://developers.google.com/search/docs/appearance/structured-data/product-variants)
464
+
465
+ ### Validation
466
+
467
+ Test a product with several variants, a sold-out product, missing image, empty category, deep pagination, filter combinations, mobile search, currency/market change, cart handoff, and changed price after cache expiry. Verify that sitemap counts match eligible catalog records and that deleted routes/redirects do not strand internal links.
468
+
469
+ ### Production detail
470
+
471
+ Applies when BetterCMS renders product or category pages. A Shopify-backed store follows
472
+ [shopify-headless.md](shopify-headless.md) first.
473
+
474
+ | Collection | `urlPattern` | Fields |
475
+ |---|---|---|
476
+ | Products | `/products/:slug`, slug equal to the store handle | `"store_product_id"` `text` (migration metadata), `"excerpt"` `longtext`, `"story"` `document`, `"gallery"` `repeater` (`"image"`, `"caption"`; editorial images only), `"highlights"` `array` of text, `"specs"` `repeater` (`"label"`, `"value"`), `"care"` `richtext`, `"categories"` `multi-reference`, `"badge"` `select`, `"related"` `multi-reference` (at most 4), `"faqs"` `multi-reference` |
477
+ | Categories | `/collections/:slug` | `"intro"` `longtext`, `"image"`, `"parent"` `reference` to Categories (one level), `"store_collection_id"` `text` |
478
+ | FAQs (when shared) | none | `"question"` `text`, `"answer"` `richtext` |
479
+
480
+ A catalogue with no commerce engine adds `"price"` and `"compare_at_price"` `number`, a
481
+ `"currency"` `select` of ISO codes, `"sku"` `text`, `"is_in_stock"` `boolean`.
482
+
483
+ - **Lists:** a category's product grid is code filtering by the category, or the store's own
484
+ listing. A "Staff picks" row is a `multi-reference`.
485
+ - **Chrome:** header (search, account and cart are app state in code, never content), a promo
486
+ bar, a footer whose payment badges are a fixed set in code.
487
+ - **Forms:** newsletter, contact, wholesale enquiry. Back-in-stock and checkout stay with the
488
+ store.
489
+ - **Structured data:** `Product` only where price and availability are entry fields. When the
490
+ store owns them, the site's code emits `Product` with the live `Offer`; do not paste a frozen
491
+ price. `CollectionPage` on categories. `AggregateRating` only from a connected review source.
492
+ - **Redirects:** facet and sort parameters canonical to the category. Cart, checkout, account
493
+ and search are noindex.
494
+ - **Pitfalls:** price, stock, options or variants copied into content (they drift within a
495
+ day); one static page per product, or one entry per variant; a size guide as an image instead
496
+ of a `repeater`; typed review counts or star scores; editorial images hotlinked from the
497
+ store CDN instead of uploaded.
498
+
499
+ ## Directory or listings website
500
+
501
+ Use when the source has hundreds of same-shaped detail pages (businesses, tools, venues,
502
+ vendors), category and location hubs, filters, a "Submit a listing" form, often a map.
503
+
504
+ ### Routes
505
+
506
+ | Family | Path | Model |
507
+ |---|---|---|
508
+ | Home, submit, featured-listing pricing, about, contact | `/`, `/submit`, ... | Static pages |
509
+ | Listings list and detail | `/listings`, `/listings/:slug` | Listings collection, dynamic page, `urlPattern` `/listings/:slug` |
510
+ | Category hub | `/categories`, `/categories/:slug` | Categories collection |
511
+ | Location hub | `/locations`, `/locations/:slug` | Locations collection (cities or regions) |
512
+
513
+ ### Collections
514
+
515
+ | Collection | Fields |
516
+ |---|---|
517
+ | Listings | `"logo"`, `"cover_image"` `image`; `"excerpt"` `longtext`; `"body"` `document`; `"category"` `reference` (required); `"subcategories"` `multi-reference`; `"place"` `reference` to Locations; `"address"` `group`; `"map_pin"` `location`; `"website"` `link`; `"email"` `email`; `"phone"` `phone`; `"opening_hours"` `repeater` (`"day"` `select`, `"opens"`, `"closes"` `text`, `"is_closed"` `boolean`); `"amenities"` `array` of text, or `multi-reference` to Features when each has a page; `"facts"` `repeater` (`"label"`, `"value"`); `"gallery"` `repeater` (`"image"`, `"caption"`); `"social_links"` `repeater`; `"plan"` `select` (free, featured, premium); `"is_verified"` `boolean`; `"external_id"` `text` |
518
+ | Categories | `"intro"` `longtext`, `"icon"` `image`, `"parent"` `reference` to Categories |
519
+ | Locations | `"region"` `text`, `"country"` `select`, `"map_pin"` `location`, `"intro"` `longtext`, `"cover_image"` `image` |
520
+
521
+ `"plan"`, `"is_verified"` and `"external_id"` are locked: `readOnly: "developer"` with a
522
+ `lockReason`. A write without schema access that changes one is refused.
523
+
524
+ - **Chrome:** header with search, footer, an announcement for featured-listing offers.
525
+ - **Lists:** the all-listings page is a `collection` block. On a hosted page a category or
526
+ location hub is a block with `source` and a filter, A to Z is `sort`, featured first is
527
+ `pinnedIds`, and "related listings" is a filter on a `{{field}}` of the listing shown with
528
+ `excludeCurrent`. Numbered pagination needs the site's code: report the gap. Site search
529
+ uses the hosted index: run `refresh_search_index` after the import.
530
+ - **Forms:** submit a listing, claim a listing, report a problem, contact. A submission never
531
+ publishes itself; an editor creates or updates the entry from the inbox.
532
+ - **Structured data:** `schemaType: "LocalBusiness"` on listings that are places. Paste `schema`
533
+ when the page shows hours or coordinates, or needs a subtype. `CollectionPage` on hubs.
534
+ - **Redirects:** nested source paths (`/:category/:listing`) become `/listings/:slug` plus one
535
+ 301 each.
536
+ - **Pitfalls:** filterable facts stored as free tags (use `select`, `reference`, `number`);
537
+ duplicate listings from several sources (match on website domain, phone or address before
538
+ creating); a one-line address when structured data needs the parts; a paid outbound link with
539
+ no `rel="sponsored"` (the `link` value carries no rel, so the rendering code adds it from
540
+ `"plan"`); hub pages with fewer than three listings left indexable (set noindex until they
541
+ grow); thousands of near-empty pages (run `generate_pages_from_dataset` with `dryRun: true`
542
+ first, and only for rows with real content).
543
+
544
+ ## Blog or publication
545
+
546
+ Use when the source has dated articles with bylines, categories, an index and a feed.
547
+
548
+ ### Routes
549
+
550
+ | Family | Path | Model |
551
+ |---|---|---|
552
+ | Home (or the index itself), about, newsletter, contact | `/`, `/about`, ... | Static pages |
553
+ | Post list and detail | `/blog`, `/blog/:slug` (or the site's own word, `/journal/:slug`) | Posts collection, `urlPattern` `/blog/:slug` |
554
+ | Author | `/authors/:slug` | Authors collection |
555
+ | Category, topic | `/category/:slug`, `/topics/:slug` | One collection per taxonomy the source really has |
556
+
557
+ ### Collections
558
+
559
+ | Collection | Fields |
560
+ |---|---|
561
+ | Posts | `"dek"`, `"excerpt"` `longtext`; `"cover_image"` `image`; `"body"` `document`; `"authors"` `multi-reference` (at least one); `"category"` `reference`; `"topics"` `multi-reference`; `"published_on"`, `"updated_on"` `datetime`; `"series"` `reference`; `"is_featured"` `boolean`; `"related"` `multi-reference` (at most 3) only when the source curates them |
562
+ | Authors | Title is the name. `"role"` `text`, `"photo"` `image`, `"bio"` `longtext`, `"social_links"` `repeater` |
563
+ | Categories, Topics | `"intro"` `longtext` |
564
+
565
+ - **Chrome:** header, footer with the newsletter form, announcement.
566
+ - **Lists:** the index is a `collection` block, `order: "newest"`. It orders by creation time,
567
+ so import posts oldest first, or the list will not follow `"published_on"`. A block with
568
+ `source` takes `sort: "-published_on"` instead. Category, topic and author lists on a hosted
569
+ page are sourced blocks with a filter; numbered pagination needs the site's code. A pinned
570
+ feature slot is `pinnedIds` on a sourced block, or a `reference` on the index page.
571
+ - **Forms:** newsletter with `consent`, contact, tips.
572
+ - **Structured data:** `BlogPosting` or `Article` as the collection default in
573
+ `collectionSchemaTypes`; `Blog` on the index. `NewsArticle` is pasted `schema`; an author page takes
574
+ `schemaType: "Person"`. Turn the feed on in `rssConfig`; item links come from
575
+ the `urlPattern`. A syndicated post keeps its cross-site canonical in the entry's `meta`.
576
+ - **Redirects:** dated WordPress paths (`/2024/05/my-post/`) become `/blog/:slug` with a 301
577
+ per post.
578
+ - **Pitfalls:** share bars, author boxes and related cards inside the body (they are template
579
+ sections); an H1 inside the body, or the cover repeated as the first body image; authors as
580
+ strings; a stored reading time (compute it); WordPress shortcodes (`[gallery]`, `[caption]`)
581
+ left as text (map them to a Portable Text block or a component, report unknown ones); tags
582
+ and categories that duplicate each other.
583
+
584
+ ## Docs or knowledge base
585
+
586
+ Use when the source has a left navigation tree, "On this page" anchors, code blocks,
587
+ admonitions, a version switcher, search, "Was this helpful?".
588
+
589
+ ### Routes
590
+
591
+ | Family | Path | Model |
592
+ |---|---|---|
593
+ | Docs home, support home, contact support | `/docs`, `/support`, ... | Static pages |
594
+ | Doc page | `/docs/:slug` | Docs collection |
595
+ | Versioned docs | `/docs/v2/:slug` | One collection per supported major version, each with its own fixed-prefix `urlPattern` |
596
+ | Knowledge-base article | `/help/:slug` | KB articles collection |
597
+ | Glossary | `/glossary`, `/glossary/:slug` | Terms collection ([programmatic-seo.md](programmatic-seo.md)) |
598
+
599
+ ### Collections
600
+
601
+ | Collection | Fields |
602
+ |---|---|
603
+ | Docs | `"excerpt"` `longtext`; `"body"` `document`; `"section"` `reference` to Doc sections (required); `"parent"` `reference` to Docs (at most 3 levels); `"order"` `number`; `"status"` `select` (new, beta, deprecated); `"last_reviewed_on"` `date` |
604
+ | Doc sections | `"icon"` `image`, `"order"` `number` |
605
+ | KB articles | `"body"` `document`, `"category"` `reference`, `"related"` `multi-reference` |
606
+ | Terms | `"definition"` `richtext`, `"related"` `multi-reference` |
607
+
608
+ - **Chrome:** header with search and footer in the Global Layout. The sidebar tree is rendered
609
+ by code from `"section"`, `"parent"` and `"order"`; previous and next links and "On this page"
610
+ are derived the same way. Curate the tree in the Layout only when the source hand-orders it.
611
+ - **Lists:** a section landing page ordered by `"order"` is a `collection` block with `source`,
612
+ a filter on `"section"` and `sort: "order"` on a hosted page, or the site's code. Run
613
+ `refresh_search_index` after the import.
614
+ - **Forms:** a one-question feedback form (`radio` plus a `hidden` page path), contact support.
615
+ - **Structured data:** `Article` or `WebPage`; `FAQPage` only on real question pages. A deprecated version is noindex or canonicals to the current one.
616
+ - **Redirects:** relative `.md` links in source files resolve to the target entry's URL. A
617
+ nested source path (`/docs/guides/setup/install`) becomes `/docs/:slug` plus a 301.
618
+ - **Pitfalls:** the navigation tree copied by hand (it drifts); admonitions flattened to
619
+ blockquotes with the tone lost (map each to an embedded block that keeps its tone,
620
+ [portable-text.md](portable-text.md)); code as images or highlighted markup (a `bcmsCode`
621
+ block keeps the plain text and a language); MDX components with no
622
+ equivalent (map known ones to components, report the rest as logic left in code); an OpenAPI
623
+ reference converted by hand (it is generated from the spec: link or embed it and report it).
624
+
625
+ ## Local business or restaurant
626
+
627
+ Use when the source shows an address, hours, a phone number, a menu, "Book a table", a map, and
628
+ one or a few locations.
629
+
630
+ ### Routes
631
+
632
+ | Family | Path | Model |
633
+ |---|---|---|
634
+ | One location | `/visit` | Static page; address and hours live once in the Global Layout |
635
+ | Two or more locations | `/locations`, `/locations/:slug` | Locations collection |
636
+ | Menu | `/menu`, or `/menus/:slug` | A page section; a Menus collection when menus differ per location or season |
637
+ | Events, specials | `/events/:slug` | Collections only when dated |
638
+
639
+ ### Collections
640
+
641
+ | Collection | Fields |
642
+ |---|---|
643
+ | Locations | `"address"` `group`; `"map_pin"` `location`; `"phone"` `phone`; `"email"` `email`; `"opening_hours"` `repeater` (`"day"` `select`, `"opens"`, `"closes"` `text`, `"is_closed"` `boolean`); `"special_hours"` `repeater` (`"date"` `date`, `"opens"`, `"closes"`, `"is_closed"`, `"note"`); `"time_zone"` `text`; `"cover_image"` `image`; `"menus"` `multi-reference`; `"booking_provider"` `select`; `"booking_id"` `text` |
644
+ | Menus | Title is "Dinner", "Brunch". `"sections"` `repeater` (`"title"`, `"items"` `repeater`: `"name"`, `"description"`, `"price"` `number`, `"dietary"` `array` of text, `"is_new"` `boolean`); `"notes"` `longtext` (allergen statement); `"pdf"` `file` (a download, never the only copy) |
645
+ | Events, Specials | `"starts_at"`, `"ends_at"` `datetime`; `"place"` `reference` to Locations; `"body"` `richtext`; `"image"` |
646
+
647
+ Give `"opens"` and `"closes"` help text with the format (24-hour `HH:MM`). The currency is a
648
+ site setting in code, not a symbol typed into each price.
649
+
650
+ - **Chrome:** header with a booking button that links to the provider; footer listing
651
+ locations. Hours appear in one source: with a Locations collection the footer renders from
652
+ the entries in code, otherwise from the Global Layout.
653
+ - **Forms:** private-events enquiry, contact, newsletter. Reservations are the provider's
654
+ embed, stored as provider and id only.
655
+ - **Structured data:** `schemaType: "LocalBusiness"` gives name, phone, email and a one-line
656
+ address. Hours, geo, a structured postal address and the `Restaurant` subtype need pasted
657
+ `schema` built from the fields. Same name, address and phone everywhere. `Event` on events.
658
+ - **Redirects:** a single-location site that grows to several moves `/visit` to
659
+ `/locations/:slug` with a 301.
660
+ - **Pitfalls:** a menu as a PDF or image only; hours typed in the footer, the contact page and
661
+ the location page; prices as "$14" strings; "Closed" typed as a time; holiday hours
662
+ forgotten; Google reviews copied as testimonials (connect the source or get consent).
663
+
664
+ ## Events or conference
665
+
666
+ Use when the source shows dates with times, an agenda, speakers, tickets, a venue, sponsors, a
667
+ countdown.
668
+
669
+ ### Routes
670
+
671
+ | Family | Path | Model |
672
+ |---|---|---|
673
+ | Current edition home, tickets, travel, code of conduct, call for papers | `/`, `/tickets`, ... | Static pages |
674
+ | Event | `/events`, `/events/:slug` | Events collection |
675
+ | Session | `/sessions/:slug` | Sessions collection |
676
+ | Speaker | `/speakers`, `/speakers/:slug` | Speakers collection |
677
+ | Venue, sponsor | none unless the source has pages | Venues, Sponsors collections |
678
+
679
+ ### Collections
680
+
681
+ | Collection | Fields |
682
+ |---|---|
683
+ | Events | `"starts_at"`, `"ends_at"` `datetime`; `"time_zone"` `text`; `"status"` `select` (scheduled, postponed, rescheduled, cancelled, moved online); `"attendance"` `select` (in person, online, mixed); `"venue"` `reference`; `"excerpt"`; `"cover_image"`; `"body"` `document`; `"tickets_button"` `button`; `"tickets"` `repeater` (`"name"`, `"price"` `number`, `"currency"` `select`, `"availability"` `select`) only when no ticketing provider owns them; `"speakers"`, `"sponsors"` `multi-reference` |
684
+ | Sessions | `"event"` `reference`; `"starts_at"`, `"ends_at"`; `"room"` `text`; `"track"` `reference` or `select`; `"format"` `select` (talk, workshop, panel, break); `"level"` `select`; `"speakers"` `multi-reference`; `"description"` `richtext`; `"recording_url"` `link` |
685
+ | Speakers | Title is the name. `"role"`, `"company"`, `"photo"`, `"bio"`, `"social_links"` |
686
+ | Venues | `"address"` `group`, `"map_pin"` `location`, `"accessibility"`, `"directions"` `richtext` |
687
+ | Sponsors | `"logo"`, `"tier"` `select`, `"website"` `link` |
688
+
689
+ - **Chrome:** header with a tickets button; an announcement bar whose countdown end is one
690
+ `datetime` with its zone, and whose Section `window` ends with it. Never migrate a countdown
691
+ that resets per visitor.
692
+ - **Lists:** a `collection` block with `source` sorts by `"starts_at"`, but its filter values
693
+ are fixed: none means "today". Upcoming and past lists, and an agenda grouped by day and
694
+ track, are the site's code filtering on `"starts_at"`. State that in the report; do not fake
695
+ it with two hand-kept lists.
696
+ - **Forms:** registration (or the ticketing provider's embed), call for papers (links only, no
697
+ upload), sponsor enquiry, newsletter.
698
+ - **Structured data:** `schemaType: "Event"` gives name, dates and a location name. Event
699
+ status, attendance mode, a `Place` with address or a `VirtualLocation`, offers from visible
700
+ ticket prices and performers need pasted `schema`.
701
+ - **Redirects:** past events keep their URLs and stay published. A yearly path
702
+ (`/2025/schedule/talk-name`) becomes `/sessions/:slug` plus a 301.
703
+ - **Pitfalls:** times with no zone (ask when the site shows none); the agenda as an image or
704
+ PDF only; speakers retyped per session; past events deleted; a sponsor link with no
705
+ `rel="sponsored"` in the rendering code.
706
+
707
+ ## Course or education
708
+
709
+ Use when the source has courses with a curriculum, instructors, levels, durations, enrol
710
+ buttons, cohorts.
711
+
712
+ ### Routes
713
+
714
+ | Family | Path | Model |
715
+ |---|---|---|
716
+ | Home, catalogue hub, pricing, for teams, FAQ | `/`, `/courses`, ... | Static pages; the hub holds the `collection` block |
717
+ | Course | `/courses/:slug` | Courses collection |
718
+ | Instructor | `/instructors/:slug` | Instructors collection |
719
+ | Cohort | none | Cohorts collection, only when dated |
720
+
721
+ Lesson content behind a login belongs to the LMS, not BetterCMS.
722
+
723
+ ### Collections
724
+
725
+ | Collection | Fields |
726
+ |---|---|
727
+ | Courses | `"excerpt"`; `"cover_image"`; `"level"` `select`; `"duration"` `text` (ISO 8601, `PT6H30M`); `"category"` `reference`; `"instructors"` `multi-reference`; `"outcomes"`, `"prerequisites"` `array` of text; `"curriculum"` `repeater` (`"title"`, `"lessons"` `repeater`: `"title"`, `"duration"`, `"is_preview"` `boolean`); `"enrol_button"` `button`; `"lms_course_id"` `text`; `"price"` `number` only when no checkout owns it |
728
+ | Cohorts | `"course"` `reference`; `"starts_at"`, `"ends_at"` `datetime`; `"time_zone"` `text`; `"seats"` `text` only when shown; `"status"` `select` |
729
+ | Instructors | Title is the name. `"role"`, `"photo"`, `"bio"` |
730
+
731
+ - **Chrome:** header with a sign-in link to the LMS, footer.
732
+ - **Lists:** the catalogue is a `collection` block; category and level filter chips need the
733
+ site's code. A fixed category or level list on a hosted page is a block with `source` and a
734
+ filter. An instructor's courses are a `multi-reference` or code.
735
+ - **Forms:** waitlist, team enquiry, newsletter.
736
+ - **Structured data:** `Course` as the collection's `schemaType` (name, description, provider);
737
+ offers and course instances from cohorts need pasted `schema`. `FAQPage` on a course with a real FAQ.
738
+ - **Redirects:** LMS marketing paths (`/p/course-name`) become `/courses/:slug` plus 301s.
739
+ - **Pitfalls:** stored totals ("6 modules, 42 lessons, 8 hours"); lesson videos or paid content
740
+ copied in; cohort dates with no zone; prices copied from the checkout.
741
+
742
+ ## Job board or careers
743
+
744
+ Use when the source lists open roles by department, location tags, Apply buttons, Greenhouse,
745
+ Lever or Ashby links, salary ranges, `JobPosting` JSON-LD.
746
+
747
+ ### Routes
748
+
749
+ | Family | Path | Model |
750
+ |---|---|---|
751
+ | Careers home, benefits, culture, hiring process | `/careers`, ... | Static pages |
752
+ | Job | `/careers/:slug` or `/jobs/:slug` | Jobs collection |
753
+ | Department | `/departments/:slug`, only if the source has them | Departments collection |
754
+ | Employer (multi-company boards) | `/companies/:slug` | Employers collection |
755
+
756
+ ### Collections
757
+
758
+ | Collection | Fields |
759
+ |---|---|
760
+ | Jobs | `"department"` `reference`; `"offices"` `multi-reference` to Locations; `"workplace"` `select` (on site, hybrid, remote); `"employment_type"` `select` (full time, part time, contract, internship); `"salary"` `group` (`"min"`, `"max"` `number`, `"currency"`, `"period"` `select`) only when shown; `"excerpt"`; `"body"` `document`; `"apply_button"` `button` (the ATS URL); `"posted_on"` `date`; `"closes_at"` `datetime`; `"status"` `select` (open, closed); `"ats_job_id"` `text` |
761
+ | Departments | `"intro"` `longtext` |
762
+ | Employers | `"logo"`, `"about"` `richtext`, `"website"` `link` |
763
+
764
+ - **Chrome:** the header carries a "Careers" link; no separate chrome.
765
+ - **Lists:** open roles grouped by department with location filter chips need the site's code.
766
+ On a hosted page a `collection` block with `source` and a filter on `"status"` lists the open
767
+ jobs, one block per department when the source groups them; set `emptyText` for "no open
768
+ roles".
769
+ - **Forms:** applications go to the ATS. Forms have no file upload, so never collect a CV in a
770
+ BetterCMS form. A "Stay in touch" form is fine.
771
+ - **Structured data:** `JobPosting` as the collection's `schemaType`: it fills title,
772
+ description, posted date, valid through, employment type, hiring organisation and job location
773
+ from the entry's fields, and is not emitted without a company and a posting date. A remote type
774
+ with applicant location requirements, or a base salary, needs pasted `schema`, only when
775
+ visible. Unpublish the entry when the job closes.
776
+ - **Redirects:** closed jobs keep their URLs. ATS-hosted paths that move onto the site get a
777
+ 301 where the old host allows one.
778
+ - **Pitfalls:** hand-copying an ATS (sync by `"ats_job_id"` in code or link out, and report
779
+ which); deleting closed jobs; one job duplicated per city; invented salaries.
780
+
781
+ ## Real estate or property listings
782
+
783
+ Use when the source has properties with price, beds, baths, area, status, galleries, floor
784
+ plans, map search, agents, a viewing request form.
785
+
786
+ ### Routes
787
+
788
+ | Family | Path | Model |
789
+ |---|---|---|
790
+ | Home, valuation, sell, let, about, contact | `/`, `/valuation`, ... | Static pages |
791
+ | Property | `/properties`, `/properties/:slug` | Properties collection |
792
+ | Area guide | `/areas/:slug` | Areas collection |
793
+ | Agent | `/agents/:slug` | Agents collection |
794
+ | Office | `/offices/:slug` | Offices collection, fields as Locations in [Local business](#local-business-or-restaurant) |
795
+
796
+ ### Collections
797
+
798
+ | Collection | Fields |
799
+ |---|---|
800
+ | Properties | Title is the display address or name. `"status"` `select` (for sale, for rent, under offer, sold, let); `"listing_type"` `select` (sale, rent); `"price"` `number`; `"currency"` `select`; `"price_period"` `select` (month, week); `"price_qualifier"` `select` (guide price, offers over, price on application); `"address"` `group`; `"map_pin"` `location`; `"property_type"` `select`; `"bedrooms"`, `"bathrooms"`, `"receptions"`, `"area"` `number`; `"area_unit"` `select`; `"features"` `array` of text; `"body"` `document`; `"gallery"` `repeater` (`"image"`, `"caption"`; at least one); `"floor_plans"` `repeater` of images with alt; `"virtual_tour"` `link`; `"energy_rating"` `select`; `"facts"` `repeater` (`"label"`, `"value"`); `"agent"`, `"office"`, `"area_guide"` `reference`; `"available_from"` `date`; `"feed_id"` `text` |
801
+ | Agents | Title is the name. `"role"`, `"photo"`, `"phone"` `phone`, `"email"` `email`, `"office"` `reference` |
802
+ | Areas | `"intro"`, `"cover_image"`, `"body"` `document`, `"map_pin"` `location` |
803
+
804
+ - **Chrome:** header with a valuation button, footer listing offices.
805
+ - **Lists:** search by area, type, price band and bedrooms, the map view and "similar
806
+ properties" need the site's code or the CRM's search. On a hosted page a `collection` block
807
+ with `source` gives a fixed list (one area, one type), never a visitor's search.
808
+ - **Forms:** viewing request with the property passed in a `hidden` field, valuation request,
809
+ contact.
810
+ - **Structured data:** `RealEstateListing` with an `Offer` is pasted `schema` from visible
811
+ facts. `schemaType: "LocalBusiness"` on offices. A portal copy keeps the canonical on this
812
+ site.
813
+ - **Redirects:** sold and let properties keep their URLs and stay published. Portal-style
814
+ paths (`/for-sale/london/3-bed-flat-12345`) become `/properties/:slug` plus 301s.
815
+ - **Pitfalls:** prices as strings ("£450,000"); status typed into the title ("SOLD - 3
816
+ bed..."); listings hand-imported when a CRM feed owns them (sync by `"feed_id"` in code and
817
+ report it); images hotlinked from the feed host; floor plans with no alt text; sold
818
+ properties deleted.
819
+
820
+ ## Nonprofit
821
+
822
+ Use when the source has a Donate button, programs, impact numbers, stories, annual reports,
823
+ volunteers, a charity number.
824
+
825
+ ### Routes
826
+
827
+ | Family | Path | Model |
828
+ |---|---|---|
829
+ | Home, mission, impact, donate, get involved, contact | `/`, `/donate`, ... | Static pages |
830
+ | Program or campaign | `/programs/:slug` | Programs collection |
831
+ | Story, news | `/stories/:slug` | Stories collection, post fields from [Blog](#blog-or-publication) |
832
+ | Report | `/reports/:slug` | Reports collection |
833
+ | Events | `/events/:slug` | As [Events](#events-or-conference) |
834
+ | Team and board, partners | none unless the source has pages | People, Partners collections |
835
+
836
+ ### Collections
837
+
838
+ | Collection | Fields |
839
+ |---|---|
840
+ | Programs | `"excerpt"`; `"cover_image"`; `"body"` `document`; `"impact"` `repeater` (`"value"`, `"label"`, `"as_of"` `date`, `"source"` `link`); `"place"` `reference`; `"is_active"` `boolean`; `"donate_button"` `button` |
841
+ | Stories | Post fields, plus `"consented_on"` `date` for any person shown |
842
+ | Reports | `"year"` `number`; `"file"` `file`; `"cover_image"`; `"summary"` `richtext` holding the key facts as text |
843
+ | Partners | `"logo"`, `"tier"` `select`, `"website"` `link` |
844
+ | People | `"role"`, `"photo"`, `"bio"`, `"team"` `select` (staff, board, advisors) |
845
+
846
+ - **Chrome:** header with a Donate button; footer with the registered charity number, kept
847
+ once in the Global Layout.
848
+ - **Donations:** the payment provider (Stripe, Donorbox, Classy, GiveWP) owns amounts,
849
+ recurring options and payment fields. Store the provider and its id, plus the copy around
850
+ the widget. The widget script stays code.
851
+ - **Forms:** volunteer sign-up, newsletter, contact, event registration. Never payment details.
852
+ - **Structured data:** `Organization` in `siteSchema` (`NGO` when it fits) with the registered
853
+ number, `Article` on stories, `Event` on events.
854
+ - **Redirects:** campaign microsite URLs that fold into the main site get 301s to the program.
855
+ - **Pitfalls:** impact numbers with no date or source (fill `"as_of"` and `"source"`, report
856
+ the rest as unverified); beneficiary photos and stories with no recorded consent (report as
857
+ rights unconfirmed); annual reports only as PDFs.
858
+
859
+ ## Multi-brand and multi-language
860
+
861
+ Use when the source has several brands on one design system, `hreflang` alternates, `/de/` and
862
+ `/fr/` paths or country domains, or a language switcher.
863
+
864
+ ### Multi-brand
865
+
866
+ | Decision | Rule |
867
+ |---|---|
868
+ | Project boundary | One project per brand and domain. Each has its own brand kit, tokens, Global Layout and SEO settings |
869
+ | Starting a new brand | Build one template project, then `clone_project` per brand. The clone copies pages, models, entries, components, media, forms and SEO settings; it leaves out submissions, analytics, domains and secrets |
870
+ | Shared sections | The cloned components. After the clone they are separate copies: a later change is applied per project |
871
+ | Shared content | Projects cannot reference each other's entries. Copy it and note where it came from, or serve it from one project that every brand's code reads. Report the choice |
872
+ | Routes | Each brand keeps its own routes and `urlPattern` values; identical patterns across brands are fine |
873
+ | Structured data | One `Organization` per brand in its own `siteSchema`. A parent company is a `parentOrganization` property, not a shared node |
874
+
875
+ ### Multi-language
876
+
877
+ Full method: [localization.md](localization.md). The decisions that shape a blueprint:
878
+
879
+ - A translation is its own page or entry carrying `locale` and `translationOf` (the source id).
880
+ Per-field localization does not exist. Never model `"title_en"` beside `"title_fr"`.
881
+ - When crawls disagree on the default locale, take the one the root path serves and report
882
+ it; do not ask. The default fills the source rows; every other locale links back to them.
883
+ - Match sections across locales by position and structure, never by text.
884
+ - Never translate during a conversion. Copy what each locale publishes; report missing
885
+ translations. `translate_entry` creates drafts, costs AI credits and runs only on request.
886
+ - Slugs are per locale; keep each locale's URLs. Alt text is copy and is translated; the image
887
+ is shared unless a locale shows a different one.
888
+ - `hreflang` is never a field. The sitemap emits it from `locale` and `translationOf`; the
889
+ hosted head does not, so a site that needs it in the head renders the links in its own code.
890
+ - A market page with a different structure (a US-only pricing page) is a separate page, not a
891
+ translation. A per-market header is a Layout locale variant, not a translation of copy.
892
+ - **Structured data:** one canonical per locale. Never canonical every locale to the default.
893
+ - **Redirects:** no IP-based redirects. A retired locale 301s to the default locale's page.
894
+ - **Pitfalls:** machine translation during import; one model with a field per language; a
895
+ switcher linking to a page that exists in one locale only.