@bettercms-ai/mcp 0.68.2 → 0.69.0

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Site blueprints"
3
- description: "Route, collection and section plans per business type: portfolio, agency, corporate, SaaS/AI and ecommerce."
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
4
  ---
5
5
 
6
6
  # Site blueprints by business type
@@ -15,6 +15,8 @@ working site's taxonomy. The platform rules behind each layer are in
15
15
  - [Common decision process](#common-decision-process)
16
16
  - [Starter rules](#starter-rules)
17
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
18
20
  - Catalogs, read only the one that matches (a mixed site can use two, but give each page family
19
21
  one route owner):
20
22
  - [Portfolio](#portfolio-website): a person or studio presents selected work, services and a
@@ -26,6 +28,18 @@ working site's taxonomy. The platform rules behind each layer are in
26
28
  solutions, industries, integrations, trust and education need separate models
27
29
  - [Large ecommerce](#large-ecommerce-website): catalog, categories, products and shopping
28
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
29
43
 
30
44
  Data-generated page families (glossary, integrations, locations): [programmatic-seo.md](programmatic-seo.md).
31
45
 
@@ -78,6 +92,122 @@ Return a route tree, route matrix, content and model matrix, folder plan, compon
78
92
  sitemap and redirect policy, open ownership decisions, and a phased implementation plan. On a
79
93
  live project, implement only the scope the user asked for and verify the routes you touched.
80
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 |
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 | `modular` |
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`, `phone` |
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` of label and value rows. Models have no typed table field |
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`. 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
+
81
211
  ## Portfolio website
82
212
 
83
213
  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.
@@ -108,6 +238,32 @@ Use repeatable gallery items rather than a long list of `image1` fields. Give ea
108
238
 
109
239
  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.
110
240
 
241
+ ### Production detail
242
+
243
+ Projects collection, `urlPattern` `/work/:slug` (or the established prefix):
244
+
245
+ | Field key | Type | Notes |
246
+ |---|---|---|
247
+ | `"client"` | `reference` to Clients, or `text` | A reference only when clients have a logo or page of their own |
248
+ | `"year"` | `number` | |
249
+ | `"services"` | `multi-reference` to Services, or `array` of text | References when service pages exist |
250
+ | `"cover_image"`, `"excerpt"` | `image`, `longtext` | Words baked into a hero image move into fields |
251
+ | `"modules"` or `"body"` | `modular`, or `document` | `modular` (text, image, gallery, quote, video, stats blocks) for image-led studies; `document` when mostly prose |
252
+ | `"results"` | `repeater`: `"value"`, `"label"` | Only verified outcomes |
253
+ | `"credits"` | `repeater`: `"role"`, `"name"` | |
254
+ | `"testimonial"` | `reference` to Testimonials | |
255
+ | `"live_link"` | `link` | |
256
+ | `"is_featured"` | `boolean` | The home grid is a hand-picked `multi-reference` or code filtering on this |
257
+
258
+ - **Chrome:** a minimal header and a footer with contact details, both in the Global Layout.
259
+ - **Forms:** one enquiry form (name, email, message, consent).
260
+ - **Structured data:** `Article` on projects, `AboutPage` and `ContactPage` on those pages,
261
+ `Organization` or a `Person` node in `siteSchema`.
262
+ - **Pitfalls:** videos without a poster or captions; scroll-driven storytelling flattened into
263
+ one blob (keep each step a `modular` item); downscaled uploads (upload the original once, the
264
+ media pipeline serves sizes); client work under NDA published without confirmed rights (flag
265
+ it in the report and ask).
266
+
111
267
  ## Agency website
112
268
 
113
269
  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.
@@ -138,6 +294,27 @@ Content sidebar folders should match editor work: `Core pages`, `Services`, `Ind
138
294
 
139
295
  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.
140
296
 
297
+ ### Production detail
298
+
299
+ | Collection | `urlPattern` | Fields |
300
+ |---|---|---|
301
+ | Case studies | `/work/:slug` | The Projects fields in [Portfolio](#portfolio-website), plus `"industry"` `reference` |
302
+ | Services | `/services/:slug` | `"icon"` `image`, `"excerpt"` `longtext`, `"body"` `document`, `"related_work"` `multi-reference` |
303
+ | Clients | none (no detail page) | `"logo"` `image` with the company name as alt, `"website"` `link` |
304
+ | Team | `/team/:slug`, only when profiles have pages | `"role"` `text`, `"photo"` `image`, `"bio"` `longtext`, `"social_links"` `repeater`: `"network"` `select`, `"url"` `link` |
305
+ | Testimonials | none | `"quote"` `longtext`, `"name"`, `"role"`, `"company"` `text`, `"photo"`, `"logo"` `image` |
306
+
307
+ - **Lists:** "work by service" on a service page is `"related_work"` picked by hand, code
308
+ filtering the delivered case studies, or on a hosted page a `collection` block with `source`
309
+ and a filter on the service.
310
+ - **Forms:** a new-business enquiry with `select` fields for budget and timeline, plus `hidden`
311
+ fields for campaign attribution.
312
+ - **Structured data:** `Organization` in `siteSchema`, `Article` on case studies, `WebPage` on
313
+ services, `FAQPage` only where a service page shows real questions. A `Person` node per team
314
+ page is pasted `schema`.
315
+ - **Pitfalls:** a logo wall with no alt text; client names retyped per page instead of
316
+ referenced; a testimonial with no record of consent (keep a `"consented_on"` `date`).
317
+
141
318
  ## Corporate website
142
319
 
143
320
  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.
@@ -212,6 +389,33 @@ Build topic hubs and link related solution, industry, integration, case, and glo
212
389
 
213
390
  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.
214
391
 
392
+ ### Production detail
393
+
394
+ | Collection | `urlPattern` | Fields |
395
+ |---|---|---|
396
+ | 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"`) |
397
+ | Testimonials | none | `"quote"`, `"name"`, `"role"`, `"company"`, `"photo"`, `"logo"`, `"source"` `link`, `"consented_on"` `date`. `"rating"` `number` only when the source shows one |
398
+ | Integrations | `/integrations/:slug` | `"logo"`, `"category"` `reference`, `"excerpt"`, `"body"` `document`, `"app_link"` `button`, `"docs_link"` `link`, `"is_native"` `boolean`, `"status"` `select` (live, beta, planned) |
399
+ | 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` |
400
+ | Comparisons | `/compare/:slug` | `"competitor"` `text`, `"competitor_logo"`, `"intro"` `longtext`, `"matrix"` `repeater` (`"feature"`, `"us"`, `"them"`), `"verified_on"` `date`, `"body"` `document` |
401
+
402
+ - **Static until repeated:** feature and product pages stay static pages until three or more
403
+ share one layout. Pricing tiers stay in the pricing page's fields unless the same plans show
404
+ on two or more pages; then a Plans collection.
405
+ - **Pricing:** store the monthly and the yearly price as two fields read from the source. Never
406
+ derive one from a discount. The full feature grid is a `repeater` whose row holds one value
407
+ per plan; inside a component it can be the `table` prop ([components.md](components.md)).
408
+ - **Lists:** customer and integration hubs are one `collection` block each. Category filter
409
+ chips are code on a code-built site; otherwise leave them out and say so. A home logo wall is
410
+ a `repeater` of images, or a `multi-reference` when logos link to stories.
411
+ - **Forms:** demo request and contact sales with `hidden` attribution fields, and a newsletter
412
+ form with `consent`. A gated asset is a form whose success state links the file.
413
+ - **Structured data:** `Organization` and a `WebSite` node in `siteSchema`, `FAQPage` on the
414
+ pricing page (one per URL), `BlogPosting` on posts. `JobPosting` as the careers collection's
415
+ `schemaType`; `SoftwareApplication` is pasted `schema` from visible facts.
416
+ - **Pitfalls:** "Trusted by 10,000 teams" style counters with no source (report as unverified);
417
+ a feature matrix pasted as an image; a screenshot holding the only copy of a claim.
418
+
215
419
  ## Large ecommerce website
216
420
 
217
421
  Use for a catalog-heavy site regardless of commerce engine. The core choice is the boundary between **commerce facts** and **editorial merchandising**.
@@ -233,7 +437,7 @@ Products, variants, SKUs, price, availability, tax, orders, checkout, customer a
233
437
  | Search, cart, account, checkout | Runtime routes | Usually excluded from sitemap; private data protected |
234
438
  | Filters and sort | Parameters on category routes | Explicit crawl/index policy; avoid unbounded URL combinations |
235
439
 
236
- Keep existing paths if the store already uses another taxonomy. Decide whether unavailable products stay indexable, redirect to a successor, or return 404/410 based on permanence and user value. Do not redirect every missing product to the homepage.
440
+ 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.
237
441
 
238
442
  ### Editor model and reuse
239
443
 
@@ -248,3 +452,430 @@ Provide crawlable category → subcategory → product links and paginated resul
248
452
  ### Validation
249
453
 
250
454
  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.
455
+
456
+ ### Production detail
457
+
458
+ Applies when BetterCMS renders product or category pages. A Shopify-backed store follows
459
+ [shopify-headless.md](shopify-headless.md) first.
460
+
461
+ | Collection | `urlPattern` | Fields |
462
+ |---|---|---|
463
+ | 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` |
464
+ | Categories | `/collections/:slug` | `"intro"` `longtext`, `"image"`, `"parent"` `reference` to Categories (one level), `"store_collection_id"` `text` |
465
+ | FAQs (when shared) | none | `"question"` `text`, `"answer"` `richtext` |
466
+
467
+ A catalogue with no commerce engine adds `"price"` and `"compare_at_price"` `number`, a
468
+ `"currency"` `select` of ISO codes, `"sku"` `text`, `"is_in_stock"` `boolean`.
469
+
470
+ - **Lists:** a category's product grid is code filtering by the category, or the store's own
471
+ listing. A "Staff picks" row is a `multi-reference`.
472
+ - **Chrome:** header (search, account and cart are app state in code, never content), a promo
473
+ bar, a footer whose payment badges are a fixed set in code.
474
+ - **Forms:** newsletter, contact, wholesale enquiry. Back-in-stock and checkout stay with the
475
+ store.
476
+ - **Structured data:** `Product` only where price and availability are entry fields. When the
477
+ store owns them, the site's code emits `Product` with the live `Offer`; do not paste a frozen
478
+ price. `CollectionPage` on categories. `AggregateRating` only from a connected review source.
479
+ - **Redirects:** facet and sort parameters canonical to the category. Cart, checkout, account
480
+ and search are noindex.
481
+ - **Pitfalls:** price, stock, options or variants copied into content (they drift within a
482
+ day); one static page per product, or one entry per variant; a size guide as an image instead
483
+ of a `repeater`; typed review counts or star scores; editorial images hotlinked from the
484
+ store CDN instead of uploaded.
485
+
486
+ ## Directory or listings website
487
+
488
+ Use when the source has hundreds of same-shaped detail pages (businesses, tools, venues,
489
+ vendors), category and location hubs, filters, a "Submit a listing" form, often a map.
490
+
491
+ ### Routes
492
+
493
+ | Family | Path | Model |
494
+ |---|---|---|
495
+ | Home, submit, featured-listing pricing, about, contact | `/`, `/submit`, ... | Static pages |
496
+ | Listings list and detail | `/listings`, `/listings/:slug` | Listings collection, dynamic page, `urlPattern` `/listings/:slug` |
497
+ | Category hub | `/categories`, `/categories/:slug` | Categories collection |
498
+ | Location hub | `/locations`, `/locations/:slug` | Locations collection (cities or regions) |
499
+
500
+ ### Collections
501
+
502
+ | Collection | Fields |
503
+ |---|---|
504
+ | 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` |
505
+ | Categories | `"intro"` `longtext`, `"icon"` `image`, `"parent"` `reference` to Categories |
506
+ | Locations | `"region"` `text`, `"country"` `select`, `"map_pin"` `location`, `"intro"` `longtext`, `"cover_image"` `image` |
507
+
508
+ `"plan"`, `"is_verified"` and `"external_id"` are locked: `readOnly: "developer"` with a
509
+ `lockReason`. A write without schema access that changes one is refused.
510
+
511
+ - **Chrome:** header with search, footer, an announcement for featured-listing offers.
512
+ - **Lists:** the all-listings page is a `collection` block. On a hosted page a category or
513
+ location hub is a block with `source` and a filter, A to Z is `sort`, featured first is
514
+ `pinnedIds`, and "related listings" is a filter on a `{{field}}` of the listing shown with
515
+ `excludeCurrent`. Numbered pagination needs the site's code: report the gap. Site search
516
+ uses the hosted index: run `refresh_search_index` after the import.
517
+ - **Forms:** submit a listing, claim a listing, report a problem, contact. A submission never
518
+ publishes itself; an editor creates or updates the entry from the inbox.
519
+ - **Structured data:** `schemaType: "LocalBusiness"` on listings that are places. Paste `schema`
520
+ when the page shows hours or coordinates, or needs a subtype. `CollectionPage` on hubs.
521
+ - **Redirects:** nested source paths (`/:category/:listing`) become `/listings/:slug` plus one
522
+ 301 each.
523
+ - **Pitfalls:** filterable facts stored as free tags (use `select`, `reference`, `number`);
524
+ duplicate listings from several sources (match on website domain, phone or address before
525
+ creating); a one-line address when structured data needs the parts; a paid outbound link with
526
+ no `rel="sponsored"` (the `link` value carries no rel, so the rendering code adds it from
527
+ `"plan"`); hub pages with fewer than three listings left indexable (set noindex until they
528
+ grow); thousands of near-empty pages (run `generate_pages_from_dataset` with `dryRun: true`
529
+ first, and only for rows with real content).
530
+
531
+ ## Blog or publication
532
+
533
+ Use when the source has dated articles with bylines, categories, an index and a feed.
534
+
535
+ ### Routes
536
+
537
+ | Family | Path | Model |
538
+ |---|---|---|
539
+ | Home (or the index itself), about, newsletter, contact | `/`, `/about`, ... | Static pages |
540
+ | Post list and detail | `/blog`, `/blog/:slug` (or the site's own word, `/journal/:slug`) | Posts collection, `urlPattern` `/blog/:slug` |
541
+ | Author | `/authors/:slug` | Authors collection |
542
+ | Category, topic | `/category/:slug`, `/topics/:slug` | One collection per taxonomy the source really has |
543
+
544
+ ### Collections
545
+
546
+ | Collection | Fields |
547
+ |---|---|
548
+ | 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 |
549
+ | Authors | Title is the name. `"role"` `text`, `"photo"` `image`, `"bio"` `longtext`, `"social_links"` `repeater` |
550
+ | Categories, Topics | `"intro"` `longtext` |
551
+
552
+ - **Chrome:** header, footer with the newsletter form, announcement.
553
+ - **Lists:** the index is a `collection` block, `order: "newest"`. It orders by creation time,
554
+ so import posts oldest first, or the list will not follow `"published_on"`. A block with
555
+ `source` takes `sort: "-published_on"` instead. Category, topic and author lists on a hosted
556
+ page are sourced blocks with a filter; numbered pagination needs the site's code. A pinned
557
+ feature slot is `pinnedIds` on a sourced block, or a `reference` on the index page.
558
+ - **Forms:** newsletter with `consent`, contact, tips.
559
+ - **Structured data:** `BlogPosting` or `Article` as the collection default in
560
+ `collectionSchemaTypes`; `Blog` on the index. `NewsArticle` is pasted `schema`; an author page takes
561
+ `schemaType: "Person"`. Turn the feed on in `rssConfig`; item links come from
562
+ the `urlPattern`. A syndicated post keeps its cross-site canonical in the entry's `meta`.
563
+ - **Redirects:** dated WordPress paths (`/2024/05/my-post/`) become `/blog/:slug` with a 301
564
+ per post.
565
+ - **Pitfalls:** share bars, author boxes and related cards inside the body (they are template
566
+ sections); an H1 inside the body, or the cover repeated as the first body image; authors as
567
+ strings; a stored reading time (compute it); WordPress shortcodes (`[gallery]`, `[caption]`)
568
+ left as text (map them to a Portable Text block or a component, report unknown ones); tags
569
+ and categories that duplicate each other.
570
+
571
+ ## Docs or knowledge base
572
+
573
+ Use when the source has a left navigation tree, "On this page" anchors, code blocks,
574
+ admonitions, a version switcher, search, "Was this helpful?".
575
+
576
+ ### Routes
577
+
578
+ | Family | Path | Model |
579
+ |---|---|---|
580
+ | Docs home, support home, contact support | `/docs`, `/support`, ... | Static pages |
581
+ | Doc page | `/docs/:slug` | Docs collection |
582
+ | Versioned docs | `/docs/v2/:slug` | One collection per supported major version, each with its own fixed-prefix `urlPattern` |
583
+ | Knowledge-base article | `/help/:slug` | KB articles collection |
584
+ | Glossary | `/glossary`, `/glossary/:slug` | Terms collection ([programmatic-seo.md](programmatic-seo.md)) |
585
+
586
+ ### Collections
587
+
588
+ | Collection | Fields |
589
+ |---|---|
590
+ | 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` |
591
+ | Doc sections | `"icon"` `image`, `"order"` `number` |
592
+ | KB articles | `"body"` `document`, `"category"` `reference`, `"related"` `multi-reference` |
593
+ | Terms | `"definition"` `richtext`, `"related"` `multi-reference` |
594
+
595
+ - **Chrome:** header with search and footer in the Global Layout. The sidebar tree is rendered
596
+ by code from `"section"`, `"parent"` and `"order"`; previous and next links and "On this page"
597
+ are derived the same way. Curate the tree in the Layout only when the source hand-orders it.
598
+ - **Lists:** a section landing page ordered by `"order"` is a `collection` block with `source`,
599
+ a filter on `"section"` and `sort: "order"` on a hosted page, or the site's code. Run
600
+ `refresh_search_index` after the import.
601
+ - **Forms:** a one-question feedback form (`radio` plus a `hidden` page path), contact support.
602
+ - **Structured data:** `Article` or `WebPage`; `FAQPage` only on real question pages. A deprecated version is noindex or canonicals to the current one.
603
+ - **Redirects:** relative `.md` links in source files resolve to the target entry's URL. A
604
+ nested source path (`/docs/guides/setup/install`) becomes `/docs/:slug` plus a 301.
605
+ - **Pitfalls:** the navigation tree copied by hand (it drifts); admonitions flattened to
606
+ blockquotes with the tone lost (map each to an embedded block that keeps its tone,
607
+ [portable-text.md](portable-text.md)); code as images or highlighted markup (a `bcmsCode`
608
+ block keeps the plain text and a language); MDX components with no
609
+ equivalent (map known ones to components, report the rest as logic left in code); an OpenAPI
610
+ reference converted by hand (it is generated from the spec: link or embed it and report it).
611
+
612
+ ## Local business or restaurant
613
+
614
+ Use when the source shows an address, hours, a phone number, a menu, "Book a table", a map, and
615
+ one or a few locations.
616
+
617
+ ### Routes
618
+
619
+ | Family | Path | Model |
620
+ |---|---|---|
621
+ | One location | `/visit` | Static page; address and hours live once in the Global Layout |
622
+ | Two or more locations | `/locations`, `/locations/:slug` | Locations collection |
623
+ | Menu | `/menu`, or `/menus/:slug` | A page section; a Menus collection when menus differ per location or season |
624
+ | Events, specials | `/events/:slug` | Collections only when dated |
625
+
626
+ ### Collections
627
+
628
+ | Collection | Fields |
629
+ |---|---|
630
+ | 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` |
631
+ | 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) |
632
+ | Events, Specials | `"starts_at"`, `"ends_at"` `datetime`; `"place"` `reference` to Locations; `"body"` `richtext`; `"image"` |
633
+
634
+ Give `"opens"` and `"closes"` help text with the format (24-hour `HH:MM`). The currency is a
635
+ site setting in code, not a symbol typed into each price.
636
+
637
+ - **Chrome:** header with a booking button that links to the provider; footer listing
638
+ locations. Hours appear in one source: with a Locations collection the footer renders from
639
+ the entries in code, otherwise from the Global Layout.
640
+ - **Forms:** private-events enquiry, contact, newsletter. Reservations are the provider's
641
+ embed, stored as provider and id only.
642
+ - **Structured data:** `schemaType: "LocalBusiness"` gives name, phone, email and a one-line
643
+ address. Hours, geo, a structured postal address and the `Restaurant` subtype need pasted
644
+ `schema` built from the fields. Same name, address and phone everywhere. `Event` on events.
645
+ - **Redirects:** a single-location site that grows to several moves `/visit` to
646
+ `/locations/:slug` with a 301.
647
+ - **Pitfalls:** a menu as a PDF or image only; hours typed in the footer, the contact page and
648
+ the location page; prices as "$14" strings; "Closed" typed as a time; holiday hours
649
+ forgotten; Google reviews copied as testimonials (connect the source or get consent).
650
+
651
+ ## Events or conference
652
+
653
+ Use when the source shows dates with times, an agenda, speakers, tickets, a venue, sponsors, a
654
+ countdown.
655
+
656
+ ### Routes
657
+
658
+ | Family | Path | Model |
659
+ |---|---|---|
660
+ | Current edition home, tickets, travel, code of conduct, call for papers | `/`, `/tickets`, ... | Static pages |
661
+ | Event | `/events`, `/events/:slug` | Events collection |
662
+ | Session | `/sessions/:slug` | Sessions collection |
663
+ | Speaker | `/speakers`, `/speakers/:slug` | Speakers collection |
664
+ | Venue, sponsor | none unless the source has pages | Venues, Sponsors collections |
665
+
666
+ ### Collections
667
+
668
+ | Collection | Fields |
669
+ |---|---|
670
+ | 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` |
671
+ | 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` |
672
+ | Speakers | Title is the name. `"role"`, `"company"`, `"photo"`, `"bio"`, `"social_links"` |
673
+ | Venues | `"address"` `group`, `"map_pin"` `location`, `"accessibility"`, `"directions"` `richtext` |
674
+ | Sponsors | `"logo"`, `"tier"` `select`, `"website"` `link` |
675
+
676
+ - **Chrome:** header with a tickets button; an announcement bar whose countdown end is one
677
+ `datetime` with its zone. Never migrate a countdown that resets per visitor.
678
+ - **Lists:** a `collection` block with `source` sorts by `"starts_at"`, but its filter values
679
+ are fixed: none means "today". Upcoming and past lists, and an agenda grouped by day and
680
+ track, are the site's code filtering on `"starts_at"`. State that in the report; do not fake
681
+ it with two hand-kept lists.
682
+ - **Forms:** registration (or the ticketing provider's embed), call for papers (links only, no
683
+ upload), sponsor enquiry, newsletter.
684
+ - **Structured data:** `schemaType: "Event"` gives name, dates and a location name. Event
685
+ status, attendance mode, a `Place` with address or a `VirtualLocation`, offers from visible
686
+ ticket prices and performers need pasted `schema`.
687
+ - **Redirects:** past events keep their URLs and stay published. A yearly path
688
+ (`/2025/schedule/talk-name`) becomes `/sessions/:slug` plus a 301.
689
+ - **Pitfalls:** times with no zone (ask when the site shows none); the agenda as an image or
690
+ PDF only; speakers retyped per session; past events deleted; a sponsor link with no
691
+ `rel="sponsored"` in the rendering code.
692
+
693
+ ## Course or education
694
+
695
+ Use when the source has courses with a curriculum, instructors, levels, durations, enrol
696
+ buttons, cohorts.
697
+
698
+ ### Routes
699
+
700
+ | Family | Path | Model |
701
+ |---|---|---|
702
+ | Home, catalogue hub, pricing, for teams, FAQ | `/`, `/courses`, ... | Static pages; the hub holds the `collection` block |
703
+ | Course | `/courses/:slug` | Courses collection |
704
+ | Instructor | `/instructors/:slug` | Instructors collection |
705
+ | Cohort | none | Cohorts collection, only when dated |
706
+
707
+ Lesson content behind a login belongs to the LMS, not BetterCMS.
708
+
709
+ ### Collections
710
+
711
+ | Collection | Fields |
712
+ |---|---|
713
+ | 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 |
714
+ | Cohorts | `"course"` `reference`; `"starts_at"`, `"ends_at"` `datetime`; `"time_zone"` `text`; `"seats"` `text` only when shown; `"status"` `select` |
715
+ | Instructors | Title is the name. `"role"`, `"photo"`, `"bio"` |
716
+
717
+ - **Chrome:** header with a sign-in link to the LMS, footer.
718
+ - **Lists:** the catalogue is a `collection` block; category and level filter chips need the
719
+ site's code. A fixed category or level list on a hosted page is a block with `source` and a
720
+ filter. An instructor's courses are a `multi-reference` or code.
721
+ - **Forms:** waitlist, team enquiry, newsletter.
722
+ - **Structured data:** `Course` as the collection's `schemaType` (name, description, provider);
723
+ offers and course instances from cohorts need pasted `schema`. `FAQPage` on a course with a real FAQ.
724
+ - **Redirects:** LMS marketing paths (`/p/course-name`) become `/courses/:slug` plus 301s.
725
+ - **Pitfalls:** stored totals ("6 modules, 42 lessons, 8 hours"); lesson videos or paid content
726
+ copied in; cohort dates with no zone; prices copied from the checkout.
727
+
728
+ ## Job board or careers
729
+
730
+ Use when the source lists open roles by department, location tags, Apply buttons, Greenhouse,
731
+ Lever or Ashby links, salary ranges, `JobPosting` JSON-LD.
732
+
733
+ ### Routes
734
+
735
+ | Family | Path | Model |
736
+ |---|---|---|
737
+ | Careers home, benefits, culture, hiring process | `/careers`, ... | Static pages |
738
+ | Job | `/careers/:slug` or `/jobs/:slug` | Jobs collection |
739
+ | Department | `/departments/:slug`, only if the source has them | Departments collection |
740
+ | Employer (multi-company boards) | `/companies/:slug` | Employers collection |
741
+
742
+ ### Collections
743
+
744
+ | Collection | Fields |
745
+ |---|---|
746
+ | 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` |
747
+ | Departments | `"intro"` `longtext` |
748
+ | Employers | `"logo"`, `"about"` `richtext`, `"website"` `link` |
749
+
750
+ - **Chrome:** the header carries a "Careers" link; no separate chrome.
751
+ - **Lists:** open roles grouped by department with location filter chips need the site's code.
752
+ On a hosted page a `collection` block with `source` and a filter on `"status"` lists the open
753
+ jobs, one block per department when the source groups them; set `emptyText` for "no open
754
+ roles".
755
+ - **Forms:** applications go to the ATS. Forms have no file upload, so never collect a CV in a
756
+ BetterCMS form. A "Stay in touch" form is fine.
757
+ - **Structured data:** `JobPosting` as the collection's `schemaType`: it fills title,
758
+ description, posted date, valid through, employment type, hiring organisation and job location
759
+ from the entry's fields, and is not emitted without a company and a posting date. A remote type
760
+ with applicant location requirements, or a base salary, needs pasted `schema`, only when
761
+ visible. Unpublish the entry when the job closes.
762
+ - **Redirects:** closed jobs keep their URLs. ATS-hosted paths that move onto the site get a
763
+ 301 where the old host allows one.
764
+ - **Pitfalls:** hand-copying an ATS (sync by `"ats_job_id"` in code or link out, and report
765
+ which); deleting closed jobs; one job duplicated per city; invented salaries.
766
+
767
+ ## Real estate or property listings
768
+
769
+ Use when the source has properties with price, beds, baths, area, status, galleries, floor
770
+ plans, map search, agents, a viewing request form.
771
+
772
+ ### Routes
773
+
774
+ | Family | Path | Model |
775
+ |---|---|---|
776
+ | Home, valuation, sell, let, about, contact | `/`, `/valuation`, ... | Static pages |
777
+ | Property | `/properties`, `/properties/:slug` | Properties collection |
778
+ | Area guide | `/areas/:slug` | Areas collection |
779
+ | Agent | `/agents/:slug` | Agents collection |
780
+ | Office | `/offices/:slug` | Offices collection, fields as Locations in [Local business](#local-business-or-restaurant) |
781
+
782
+ ### Collections
783
+
784
+ | Collection | Fields |
785
+ |---|---|
786
+ | 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` |
787
+ | Agents | Title is the name. `"role"`, `"photo"`, `"phone"` `phone`, `"email"` `email`, `"office"` `reference` |
788
+ | Areas | `"intro"`, `"cover_image"`, `"body"` `document`, `"map_pin"` `location` |
789
+
790
+ - **Chrome:** header with a valuation button, footer listing offices.
791
+ - **Lists:** search by area, type, price band and bedrooms, the map view and "similar
792
+ properties" need the site's code or the CRM's search. On a hosted page a `collection` block
793
+ with `source` gives a fixed list (one area, one type), never a visitor's search.
794
+ - **Forms:** viewing request with the property passed in a `hidden` field, valuation request,
795
+ contact.
796
+ - **Structured data:** `RealEstateListing` with an `Offer` is pasted `schema` from visible
797
+ facts. `schemaType: "LocalBusiness"` on offices. A portal copy keeps the canonical on this
798
+ site.
799
+ - **Redirects:** sold and let properties keep their URLs and stay published. Portal-style
800
+ paths (`/for-sale/london/3-bed-flat-12345`) become `/properties/:slug` plus 301s.
801
+ - **Pitfalls:** prices as strings ("£450,000"); status typed into the title ("SOLD - 3
802
+ bed..."); listings hand-imported when a CRM feed owns them (sync by `"feed_id"` in code and
803
+ report it); images hotlinked from the feed host; floor plans with no alt text; sold
804
+ properties deleted.
805
+
806
+ ## Nonprofit
807
+
808
+ Use when the source has a Donate button, programs, impact numbers, stories, annual reports,
809
+ volunteers, a charity number.
810
+
811
+ ### Routes
812
+
813
+ | Family | Path | Model |
814
+ |---|---|---|
815
+ | Home, mission, impact, donate, get involved, contact | `/`, `/donate`, ... | Static pages |
816
+ | Program or campaign | `/programs/:slug` | Programs collection |
817
+ | Story, news | `/stories/:slug` | Stories collection, post fields from [Blog](#blog-or-publication) |
818
+ | Report | `/reports/:slug` | Reports collection |
819
+ | Events | `/events/:slug` | As [Events](#events-or-conference) |
820
+ | Team and board, partners | none unless the source has pages | People, Partners collections |
821
+
822
+ ### Collections
823
+
824
+ | Collection | Fields |
825
+ |---|---|
826
+ | Programs | `"excerpt"`; `"cover_image"`; `"body"` `document`; `"impact"` `repeater` (`"value"`, `"label"`, `"as_of"` `date`, `"source"` `link`); `"place"` `reference`; `"is_active"` `boolean`; `"donate_button"` `button` |
827
+ | Stories | Post fields, plus `"consented_on"` `date` for any person shown |
828
+ | Reports | `"year"` `number`; `"file"` `file`; `"cover_image"`; `"summary"` `richtext` holding the key facts as text |
829
+ | Partners | `"logo"`, `"tier"` `select`, `"website"` `link` |
830
+ | People | `"role"`, `"photo"`, `"bio"`, `"team"` `select` (staff, board, advisors) |
831
+
832
+ - **Chrome:** header with a Donate button; footer with the registered charity number, kept
833
+ once in the Global Layout.
834
+ - **Donations:** the payment provider (Stripe, Donorbox, Classy, GiveWP) owns amounts,
835
+ recurring options and payment fields. Store the provider and its id, plus the copy around
836
+ the widget. The widget script stays code.
837
+ - **Forms:** volunteer sign-up, newsletter, contact, event registration. Never payment details.
838
+ - **Structured data:** `Organization` in `siteSchema` (`NGO` when it fits) with the registered
839
+ number, `Article` on stories, `Event` on events.
840
+ - **Redirects:** campaign microsite URLs that fold into the main site get 301s to the program.
841
+ - **Pitfalls:** impact numbers with no date or source (fill `"as_of"` and `"source"`, report
842
+ the rest as unverified); beneficiary photos and stories with no recorded consent (report as
843
+ rights unconfirmed); annual reports only as PDFs.
844
+
845
+ ## Multi-brand and multi-language
846
+
847
+ Use when the source has several brands on one design system, `hreflang` alternates, `/de/` and
848
+ `/fr/` paths or country domains, or a language switcher.
849
+
850
+ ### Multi-brand
851
+
852
+ | Decision | Rule |
853
+ |---|---|
854
+ | Project boundary | One project per brand and domain. Each has its own brand kit, tokens, Global Layout and SEO settings |
855
+ | 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 |
856
+ | Shared sections | The cloned components. After the clone they are separate copies: a later change is applied per project |
857
+ | 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 |
858
+ | Routes | Each brand keeps its own routes and `urlPattern` values; identical patterns across brands are fine |
859
+ | Structured data | One `Organization` per brand in its own `siteSchema`. A parent company is a `parentOrganization` property, not a shared node |
860
+
861
+ ### Multi-language
862
+
863
+ Full method: [localization.md](localization.md). The decisions that shape a blueprint:
864
+
865
+ - A translation is its own page or entry carrying `locale` and `translationOf` (the source id).
866
+ Per-field localization does not exist. Never model `"title_en"` beside `"title_fr"`.
867
+ - When crawls disagree on the default locale, take the one the root path serves and report
868
+ it; do not ask. The default fills the source rows; every other locale links back to them.
869
+ - Match sections across locales by position and structure, never by text.
870
+ - Never translate during a conversion. Copy what each locale publishes; report missing
871
+ translations. `translate_entry` creates drafts, costs AI credits and runs only on request.
872
+ - Slugs are per locale; keep each locale's URLs. Alt text is copy and is translated; the image
873
+ is shared unless a locale shows a different one.
874
+ - `hreflang` is never a field. The sitemap emits it from `locale` and `translationOf`; the
875
+ hosted head does not, so a site that needs it in the head renders the links in its own code.
876
+ - A market page with a different structure (a US-only pricing page) is a separate page, not a
877
+ translation. A per-market header is a Layout locale variant, not a translation of copy.
878
+ - **Structured data:** one canonical per locale. Never canonical every locale to the default.
879
+ - **Redirects:** no IP-based redirects. A retired locale 301s to the default locale's page.
880
+ - **Pitfalls:** machine translation during import; one model with a field per language; a
881
+ switcher linking to a page that exists in one locale only.