@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.
- package/dist/index.js +129 -45
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/SKILL.md +8 -4
- package/dist/skills/bettercms/references/accessibility.md +171 -0
- package/dist/skills/bettercms/references/components.md +20 -4
- package/dist/skills/bettercms/references/content-modeling.md +153 -24
- package/dist/skills/bettercms/references/detection.md +450 -0
- package/dist/skills/bettercms/references/media-and-brand.md +2 -1
- package/dist/skills/bettercms/references/publishing.md +7 -4
- package/dist/skills/bettercms/references/seo.md +226 -26
- package/dist/skills/bettercms/references/site-blueprints.md +633 -2
- package/dist/skills/bettercms/references/site-review.md +2 -1
- package/dist/skills/bettercms/references/worked-examples-advanced.md +495 -0
- package/dist/skills/bettercms/references/worked-examples.md +725 -0
- package/dist/skills/bettercms/references/write-limits.md +138 -4
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Site blueprints"
|
|
3
|
-
description: "Route, collection and
|
|
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
|
|
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.
|