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