@bettercms-ai/astro 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -227
- package/components/BcmsBlocks.astro +640 -640
- package/components/BcmsButton.astro +44 -44
- package/components/BcmsField.astro +39 -30
- package/components/BcmsForm.astro +360 -360
- package/components/BcmsImage.astro +131 -112
- package/components/BcmsLive.astro +66 -66
- package/components/BcmsLiveBlocks.astro +72 -72
- package/components/BcmsSearch.astro +23 -23
- package/components/BcmsSections.astro +88 -0
- package/components/BcmsVisualEditing.astro +26 -26
- package/dist/bindings.d.ts +17 -1
- package/dist/bindings.js +63 -1
- package/dist/bindings.js.map +1 -1
- package/dist/config-CU7bp5dd.d.ts +68 -0
- package/dist/index.d.ts +6 -96
- package/dist/index.js +50 -11
- package/dist/index.js.map +1 -1
- package/dist/middleware-entry.js.map +1 -1
- package/dist/public-config.d.ts +1 -0
- package/dist/public-config.js +42 -0
- package/dist/public-config.js.map +1 -0
- package/dist/routes/draft-disable.js.map +1 -1
- package/dist/routes/draft-enable.js.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/section-root-JrXFxiBm.d.ts +53 -0
- package/dist/sections.d.ts +65 -0
- package/dist/sections.js +181 -0
- package/dist/sections.js.map +1 -0
- package/env.d.ts +35 -35
- package/package.json +92 -86
package/README.md
CHANGED
|
@@ -1,227 +1,259 @@
|
|
|
1
|
-
# @bettercms-ai/astro
|
|
2
|
-
|
|
3
|
-
The BetterCMS adapter for Astro — a `bettercms()` integration, a
|
|
4
|
-
`bettercms:client` virtual module, typed content loaders, draft preview, and
|
|
5
|
-
native `.astro` rendering components. The Astro equivalent of `@sanity/astro`.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @bettercms-ai/astro
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Setup
|
|
14
|
-
|
|
15
|
-
```js
|
|
16
|
-
// astro.config.mjs
|
|
17
|
-
import { defineConfig } from "astro/config";
|
|
18
|
-
import bettercms from "@bettercms-ai/astro";
|
|
19
|
-
|
|
20
|
-
export default defineConfig({
|
|
21
|
-
integrations: [
|
|
22
|
-
bettercms({
|
|
23
|
-
apiUrl: "https://api.bettercms.ai", // or PUBLIC_BCMS_API_URL
|
|
24
|
-
workspace: "my-workspace", // or PUBLIC_BCMS_WORKSPACE
|
|
25
|
-
// projectId, studioUrl, mediaUrl optional
|
|
26
|
-
}),
|
|
27
|
-
],
|
|
28
|
-
output: "server", // required for draft mode + live reads
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Environment variables
|
|
33
|
-
|
|
34
|
-
Every public value resolves in this order: `bettercms({ ... })` option, then env, then the
|
|
35
|
-
`bcms-content.json` snapshot the deploy workflow writes before the build.
|
|
36
|
-
|
|
37
|
-
| Variable | Option | Read | Default / notes |
|
|
38
|
-
| --- | --- | --- | --- |
|
|
39
|
-
| `PUBLIC_BCMS_API_URL` | `apiUrl` | config time | Required (option, env or snapshot) |
|
|
40
|
-
| `PUBLIC_BCMS_WORKSPACE` | `workspace` | config time | Required (option, env or snapshot) |
|
|
41
|
-
| `PUBLIC_BCMS_PROJECT_ID` | `projectId` | config time | Optional project scope for forms |
|
|
42
|
-
| `PUBLIC_BCMS_MEDIA_URL` | `mediaUrl` | config time | `https://cdn.bettercms.ai` |
|
|
43
|
-
| `PUBLIC_BCMS_STUDIO_URL` | `studioUrl` | config time | Unset. `<BcmsVisualEditing>` falls back to `https://bettercms.ai` |
|
|
44
|
-
| `BCMS_API_KEY` | none | server runtime | `content:read`, or `content:read:draft` for draft previews. Never bundled |
|
|
45
|
-
| `BCMS_DRAFT_SECRET` | none | server runtime | Signs the draft cookie. Runtime `process.env` wins over a build-time value. Without it `/api/bcms/draft/enable` returns 500 |
|
|
46
|
-
|
|
47
|
-
The BetterCMS-generated deploy workflow sets `PUBLIC_BCMS_API_URL`, `PUBLIC_BCMS_WORKSPACE`
|
|
48
|
-
and `PUBLIC_BCMS_PROJECT_ID` for the build. Its repo secret `BCMS_API_KEY` reaches only the
|
|
49
|
-
content-fetch step, never the build. On your own host, set the two server variables yourself.
|
|
50
|
-
|
|
51
|
-
This package ships no revalidation route. Point the project's revalidation webhook
|
|
52
|
-
(Project → Settings) at your rebuild hook. Its secret (the dashboard calls it
|
|
53
|
-
`BETTERCMS_WEBHOOK_SECRET`) signs the body as `x-bettercms-signature: sha256=<hmac>`.
|
|
54
|
-
|
|
55
|
-
Add the virtual-module + `Astro.locals` types to `src/env.d.ts`:
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
/// <reference types="@bettercms-ai/astro/env" />
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Reading content
|
|
62
|
-
|
|
63
|
-
```astro
|
|
64
|
-
---
|
|
65
|
-
import { loadPage, loadForms } from "bettercms:client";
|
|
66
|
-
import BcmsBlocks from "@bettercms-ai/astro/components/BcmsBlocks.astro";
|
|
67
|
-
|
|
68
|
-
const page = await loadPage(Astro, "home");
|
|
69
|
-
const { items: forms, turnstileSiteKey } = await loadForms(Astro);
|
|
70
|
-
---
|
|
71
|
-
{page && <BcmsBlocks blocks={page.blocks} forms={forms} turnstileSiteKey={turnstileSiteKey} />}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Loaders take `Astro` so they automatically read drafts when draft mode is on:
|
|
75
|
-
`loadPage`, `loadEntry`, `loadEntries`, `loadForms`. The raw `client` and
|
|
76
|
-
`getClient(Astro)` are exported too.
|
|
77
|
-
|
|
78
|
-
## SEO — per-page `<head>`
|
|
79
|
-
|
|
80
|
-
`loadPage()` returns the page's `metaTitle`, `metaDescription`, and the rich `metaJson`
|
|
81
|
-
(OG / Twitter / canonical / JSON-LD) edited in the dashboard's SEO panel. Resolve them
|
|
82
|
-
page-over-site with `resolveSeo` and map the result into your `<head>` (same precedence
|
|
83
|
-
as the live `*.bettercms.site` renderer):
|
|
84
|
-
|
|
85
|
-
```astro
|
|
86
|
-
---
|
|
87
|
-
import { loadPage } from "bettercms:client";
|
|
88
|
-
import { resolveSeo, type SiteSeoDefaults } from "@bettercms-ai/astro";
|
|
89
|
-
|
|
90
|
-
const { slug } = Astro.params;
|
|
91
|
-
const page = await loadPage(Astro, slug ?? "home");
|
|
92
|
-
|
|
93
|
-
const siteDefaults: SiteSeoDefaults = {
|
|
94
|
-
metaDescription: "Selected work, experience, and contact information.",
|
|
95
|
-
ogImage: "https://example.com/og-default.png",
|
|
96
|
-
twitterHandle: "@acme",
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const seo = page ? resolveSeo(page, siteDefaults) : null;
|
|
100
|
-
---
|
|
101
|
-
{seo && (
|
|
102
|
-
<Fragment slot="head">
|
|
103
|
-
<title>{seo.title}</title>
|
|
104
|
-
{seo.description && <meta name="description" content={seo.description} />}
|
|
105
|
-
{seo.canonical && <link rel="canonical" href={seo.canonical} />}
|
|
106
|
-
{seo.og.title && <meta property="og:title" content={seo.og.title} />}
|
|
107
|
-
{seo.og.description && <meta property="og:description" content={seo.og.description} />}
|
|
108
|
-
{seo.og.image && <meta property="og:image" content={seo.og.image} />}
|
|
109
|
-
<meta property="og:type" content={seo.og.type} />
|
|
110
|
-
<meta name="twitter:card" content={seo.twitter.card} />
|
|
111
|
-
{seo.twitter.image && <meta name="twitter:image" content={seo.twitter.image} />}
|
|
112
|
-
{seo.twitter.site && <meta name="twitter:site" content={seo.twitter.site} />}
|
|
113
|
-
{seo.jsonLd.map((node) => (
|
|
114
|
-
<script type="application/ld+json" set:html={JSON.stringify(node)} />
|
|
115
|
-
))}
|
|
116
|
-
</Fragment>
|
|
117
|
-
)}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Reads are cached; a SEO edit appears on the next fetch. For instant refresh on publish,
|
|
121
|
-
configure the project's revalidation webhook (Project → Settings) against your rebuild
|
|
122
|
-
or on-demand-revalidation hook.
|
|
123
|
-
|
|
124
|
-
## Components
|
|
125
|
-
|
|
126
|
-
| Component | Purpose |
|
|
127
|
-
| --- | --- |
|
|
128
|
-
| `@bettercms-ai/astro/components/BcmsBlocks.astro` | Render a page's `blockJson` — every block type the builder can author (heading, text/richtext, image, button, spacer, video, columns, form, section, navbar, footer, slider, tabs, component), with block style tokens applied. |
|
|
129
|
-
| `@bettercms-ai/astro/components/BcmsForm.astro` | Render + submit a form (conditional fields, honeypot, Turnstile). |
|
|
130
|
-
| `@bettercms-ai/astro/components/BcmsImage.astro` | Optimized image with a 1x/2x srcset via the media transform endpoint. |
|
|
131
|
-
| `@bettercms-ai/astro/components/BcmsLiveBlocks.astro` | `BcmsBlocks` as a hydrated island that lets the Visual Editor re-render your DRAFTS with your own components and CSS. See below. |
|
|
132
|
-
|
|
133
|
-
Markup is class-driven and unstyled — you own the CSS.
|
|
134
|
-
|
|
135
|
-
### Phone fields with a country code picker
|
|
136
|
-
|
|
137
|
-
A phone field whose author enabled **Country code picker** in the builder renders a country
|
|
138
|
-
control beside the number. Whatever the visitor picks, the field still submits **one value**,
|
|
139
|
-
under its own key, in E.164 (`+14155550123`) — there is no second key to handle.
|
|
140
|
-
|
|
141
|
-
Two classes are yours to style:
|
|
142
|
-
|
|
143
|
-
| Class | Element |
|
|
144
|
-
|---|---|
|
|
145
|
-
| `bcms-phone` | the wrapper around the country control and the number |
|
|
146
|
-
| `bcms-phone-country` | the country control itself |
|
|
147
|
-
|
|
148
|
-
The country control is a native `<select>` built at build/SSR time, so **no phone library
|
|
149
|
-
reaches the visitor's browser**. The trade is that the number is not reformatted as it is
|
|
150
|
-
typed — the value is still composed correctly on submit. `@bettercms-ai/next` makes the other
|
|
151
|
-
choice and ships a formatter in a lazy chunk.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
##
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
1
|
+
# @bettercms-ai/astro
|
|
2
|
+
|
|
3
|
+
The BetterCMS adapter for Astro — a `bettercms()` integration, a
|
|
4
|
+
`bettercms:client` virtual module, typed content loaders, draft preview, and
|
|
5
|
+
native `.astro` rendering components. The Astro equivalent of `@sanity/astro`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @bettercms-ai/astro
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// astro.config.mjs
|
|
17
|
+
import { defineConfig } from "astro/config";
|
|
18
|
+
import bettercms from "@bettercms-ai/astro";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
integrations: [
|
|
22
|
+
bettercms({
|
|
23
|
+
apiUrl: "https://api.bettercms.ai", // or PUBLIC_BCMS_API_URL
|
|
24
|
+
workspace: "my-workspace", // or PUBLIC_BCMS_WORKSPACE
|
|
25
|
+
// projectId, studioUrl, mediaUrl optional
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
output: "server", // required for draft mode + live reads
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Environment variables
|
|
33
|
+
|
|
34
|
+
Every public value resolves in this order: `bettercms({ ... })` option, then env, then the
|
|
35
|
+
`bcms-content.json` snapshot the deploy workflow writes before the build.
|
|
36
|
+
|
|
37
|
+
| Variable | Option | Read | Default / notes |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| `PUBLIC_BCMS_API_URL` | `apiUrl` | config time | Required (option, env or snapshot) |
|
|
40
|
+
| `PUBLIC_BCMS_WORKSPACE` | `workspace` | config time | Required (option, env or snapshot) |
|
|
41
|
+
| `PUBLIC_BCMS_PROJECT_ID` | `projectId` | config time | Optional project scope for forms |
|
|
42
|
+
| `PUBLIC_BCMS_MEDIA_URL` | `mediaUrl` | config time | `https://cdn.bettercms.ai` |
|
|
43
|
+
| `PUBLIC_BCMS_STUDIO_URL` | `studioUrl` | config time | Unset. `<BcmsVisualEditing>` falls back to `https://bettercms.ai` |
|
|
44
|
+
| `BCMS_API_KEY` | none | server runtime | `content:read`, or `content:read:draft` for draft previews. Never bundled |
|
|
45
|
+
| `BCMS_DRAFT_SECRET` | none | server runtime | Signs the draft cookie. Runtime `process.env` wins over a build-time value. Without it `/api/bcms/draft/enable` returns 500 |
|
|
46
|
+
|
|
47
|
+
The BetterCMS-generated deploy workflow sets `PUBLIC_BCMS_API_URL`, `PUBLIC_BCMS_WORKSPACE`
|
|
48
|
+
and `PUBLIC_BCMS_PROJECT_ID` for the build. Its repo secret `BCMS_API_KEY` reaches only the
|
|
49
|
+
content-fetch step, never the build. On your own host, set the two server variables yourself.
|
|
50
|
+
|
|
51
|
+
This package ships no revalidation route. Point the project's revalidation webhook
|
|
52
|
+
(Project → Settings) at your rebuild hook. Its secret (the dashboard calls it
|
|
53
|
+
`BETTERCMS_WEBHOOK_SECRET`) signs the body as `x-bettercms-signature: sha256=<hmac>`.
|
|
54
|
+
|
|
55
|
+
Add the virtual-module + `Astro.locals` types to `src/env.d.ts`:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
/// <reference types="@bettercms-ai/astro/env" />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Reading content
|
|
62
|
+
|
|
63
|
+
```astro
|
|
64
|
+
---
|
|
65
|
+
import { loadPage, loadForms } from "bettercms:client";
|
|
66
|
+
import BcmsBlocks from "@bettercms-ai/astro/components/BcmsBlocks.astro";
|
|
67
|
+
|
|
68
|
+
const page = await loadPage(Astro, "home");
|
|
69
|
+
const { items: forms, turnstileSiteKey } = await loadForms(Astro);
|
|
70
|
+
---
|
|
71
|
+
{page && <BcmsBlocks blocks={page.blocks} forms={forms} turnstileSiteKey={turnstileSiteKey} />}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Loaders take `Astro` so they automatically read drafts when draft mode is on:
|
|
75
|
+
`loadPage`, `loadEntry`, `loadEntries`, `loadForms`. The raw `client` and
|
|
76
|
+
`getClient(Astro)` are exported too.
|
|
77
|
+
|
|
78
|
+
## SEO — per-page `<head>`
|
|
79
|
+
|
|
80
|
+
`loadPage()` returns the page's `metaTitle`, `metaDescription`, and the rich `metaJson`
|
|
81
|
+
(OG / Twitter / canonical / JSON-LD) edited in the dashboard's SEO panel. Resolve them
|
|
82
|
+
page-over-site with `resolveSeo` and map the result into your `<head>` (same precedence
|
|
83
|
+
as the live `*.bettercms.site` renderer):
|
|
84
|
+
|
|
85
|
+
```astro
|
|
86
|
+
---
|
|
87
|
+
import { loadPage } from "bettercms:client";
|
|
88
|
+
import { resolveSeo, type SiteSeoDefaults } from "@bettercms-ai/astro";
|
|
89
|
+
|
|
90
|
+
const { slug } = Astro.params;
|
|
91
|
+
const page = await loadPage(Astro, slug ?? "home");
|
|
92
|
+
|
|
93
|
+
const siteDefaults: SiteSeoDefaults = {
|
|
94
|
+
metaDescription: "Selected work, experience, and contact information.",
|
|
95
|
+
ogImage: "https://example.com/og-default.png",
|
|
96
|
+
twitterHandle: "@acme",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const seo = page ? resolveSeo(page, siteDefaults) : null;
|
|
100
|
+
---
|
|
101
|
+
{seo && (
|
|
102
|
+
<Fragment slot="head">
|
|
103
|
+
<title>{seo.title}</title>
|
|
104
|
+
{seo.description && <meta name="description" content={seo.description} />}
|
|
105
|
+
{seo.canonical && <link rel="canonical" href={seo.canonical} />}
|
|
106
|
+
{seo.og.title && <meta property="og:title" content={seo.og.title} />}
|
|
107
|
+
{seo.og.description && <meta property="og:description" content={seo.og.description} />}
|
|
108
|
+
{seo.og.image && <meta property="og:image" content={seo.og.image} />}
|
|
109
|
+
<meta property="og:type" content={seo.og.type} />
|
|
110
|
+
<meta name="twitter:card" content={seo.twitter.card} />
|
|
111
|
+
{seo.twitter.image && <meta name="twitter:image" content={seo.twitter.image} />}
|
|
112
|
+
{seo.twitter.site && <meta name="twitter:site" content={seo.twitter.site} />}
|
|
113
|
+
{seo.jsonLd.map((node) => (
|
|
114
|
+
<script type="application/ld+json" set:html={JSON.stringify(node)} />
|
|
115
|
+
))}
|
|
116
|
+
</Fragment>
|
|
117
|
+
)}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Reads are cached; a SEO edit appears on the next fetch. For instant refresh on publish,
|
|
121
|
+
configure the project's revalidation webhook (Project → Settings) against your rebuild
|
|
122
|
+
or on-demand-revalidation hook.
|
|
123
|
+
|
|
124
|
+
## Components
|
|
125
|
+
|
|
126
|
+
| Component | Purpose |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `@bettercms-ai/astro/components/BcmsBlocks.astro` | Render a page's `blockJson` — every block type the builder can author (heading, text/richtext, image, button, spacer, video, columns, form, section, navbar, footer, slider, tabs, component), with block style tokens applied. |
|
|
129
|
+
| `@bettercms-ai/astro/components/BcmsForm.astro` | Render + submit a form (conditional fields, honeypot, Turnstile). |
|
|
130
|
+
| `@bettercms-ai/astro/components/BcmsImage.astro` | Optimized image with a 1x/2x srcset via the media transform endpoint. |
|
|
131
|
+
| `@bettercms-ai/astro/components/BcmsLiveBlocks.astro` | `BcmsBlocks` as a hydrated island that lets the Visual Editor re-render your DRAFTS with your own components and CSS. See below. |
|
|
132
|
+
|
|
133
|
+
Markup is class-driven and unstyled — you own the CSS.
|
|
134
|
+
|
|
135
|
+
### Phone fields with a country code picker
|
|
136
|
+
|
|
137
|
+
A phone field whose author enabled **Country code picker** in the builder renders a country
|
|
138
|
+
control beside the number. Whatever the visitor picks, the field still submits **one value**,
|
|
139
|
+
under its own key, in E.164 (`+14155550123`) — there is no second key to handle.
|
|
140
|
+
|
|
141
|
+
Two classes are yours to style:
|
|
142
|
+
|
|
143
|
+
| Class | Element |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `bcms-phone` | the wrapper around the country control and the number |
|
|
146
|
+
| `bcms-phone-country` | the country control itself |
|
|
147
|
+
|
|
148
|
+
The country control is a native `<select>` built at build/SSR time, so **no phone library
|
|
149
|
+
reaches the visitor's browser**. The trade is that the number is not reformatted as it is
|
|
150
|
+
typed — the value is still composed correctly on submit. `@bettercms-ai/next` makes the other
|
|
151
|
+
choice and ships a formatter in a lazy chunk.
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## Sections in the CMS order: `<BcmsSections>`
|
|
155
|
+
|
|
156
|
+
A page whose sections are written in its `.astro` file renders them in the order its CMS page
|
|
157
|
+
lists them. Each section goes in a named slot, named by its placement's bind; the markup inside is
|
|
158
|
+
yours and is rendered as written.
|
|
159
|
+
|
|
160
|
+
```astro
|
|
161
|
+
---
|
|
162
|
+
import BcmsSections from "@bettercms-ai/astro/components/BcmsSections.astro";
|
|
163
|
+
---
|
|
164
|
+
<Layout>
|
|
165
|
+
<BcmsSections page="home">
|
|
166
|
+
<Fragment slot="hero"><Hero /></Fragment>
|
|
167
|
+
<Fragment slot="code:sec1"><LogoStrip /></Fragment>
|
|
168
|
+
<Fragment slot="features"><Features /></Fragment>
|
|
169
|
+
</BcmsSections>
|
|
170
|
+
</Layout>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- A block bound to a slot renders that slot at the block's position. Its root element gets
|
|
174
|
+
`data-bcms-block`, `data-bcms-style` and the block's style; no element is added around it.
|
|
175
|
+
- A section added in the editor (a block with no bind) is drawn by `<BcmsBlocks>`, which
|
|
176
|
+
`<BcmsSections>` imports itself. It needs no `bettercms()` on the build: without the integration
|
|
177
|
+
the components read `PUBLIC_BCMS_*` and the content snapshot. `Blocks` swaps in another renderer.
|
|
178
|
+
- A slot no block names: `code:<bind>` always renders where you wrote it. A bare `<bind>` does too
|
|
179
|
+
until the list names one of the page's slots; after that it is a removed section.
|
|
180
|
+
- With no snapshot (a local build, the API down) every slot renders in source order, untouched.
|
|
181
|
+
|
|
182
|
+
The block list is the published snapshot's (`bcms-content.json`), or the draft's when the Visual
|
|
183
|
+
Editor frames a server-rendered page in draft mode. Pass `blocks` to give it yourself.
|
|
184
|
+
`npx @bettercms-ai/convert --sections` writes this wrap for you.
|
|
185
|
+
|
|
186
|
+
## Live editing on a static site — `<BcmsLiveBlocks>`
|
|
187
|
+
|
|
188
|
+
When the Visual Editor frames a static build it can only show what was built, so editing a
|
|
189
|
+
draft falls back to the platform's approximate renderer and the canvas stops looking like your
|
|
190
|
+
site. `<BcmsLiveBlocks>` fixes that for pages whose body is a block tree: it renders the same
|
|
191
|
+
markup `<BcmsBlocks>` does, but as a `client:load` island that accepts draft documents from the
|
|
192
|
+
editor over `postMessage` (same-origin, parent frame only, and only after the editor's own
|
|
193
|
+
handshake — a third-party page that iframes your site is on a foreign origin and gets nothing).
|
|
194
|
+
Nothing is fetched by the page and no credentials enter it.
|
|
195
|
+
|
|
196
|
+
It needs a JSX renderer, which a component cannot add for you:
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
npm i @astrojs/preact preact
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
// astro.config.mjs
|
|
204
|
+
import preact from "@astrojs/preact";
|
|
205
|
+
export default defineConfig({ integrations: [bettercms(), preact({ compat: true })] });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```astro
|
|
209
|
+
---
|
|
210
|
+
import BcmsLiveBlocks from "@bettercms-ai/astro/components/BcmsLiveBlocks.astro";
|
|
211
|
+
const page = await getPage(Astro.params.slug);
|
|
212
|
+
const { forms } = await readForms();
|
|
213
|
+
---
|
|
214
|
+
<BcmsLiveBlocks slug={page.slug} blocks={page.blocks} forms={forms} />
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`compat: true` is required — the island reuses the React `<BcmsDraftBridge>` from
|
|
218
|
+
`@bettercms-ai/next` so both SDKs speak one protocol. Roughly 10 kB of Preact plus the block
|
|
219
|
+
renderer; `@astrojs/react` works too if your project already uses it.
|
|
220
|
+
|
|
221
|
+
⚠️ **It emits the React class names, not this package's.** `<BcmsBlocks>` from
|
|
222
|
+
[`@bettercms-ai/next`](https://www.npmjs.com/package/@bettercms-ai/next) classes its output
|
|
223
|
+
`bcms-block bcms-block--text`, `bcms-button--primary`, `bcms-column`; `BcmsBlocks.astro` above
|
|
224
|
+
classes the same tree `bcms-text`, `bcms-btn-primary`, `bcms-cols`. They are NOT interchangeable: a
|
|
225
|
+
stylesheet written for the `.astro` grammar styles nothing on this island, so swapping a page over
|
|
226
|
+
unstyles it. Cover both spellings — the starters wrap them in one `:is()` selector for exactly this
|
|
227
|
+
reason — or take the class list from `@bettercms-ai/next`'s `src/blocks.tsx`.
|
|
228
|
+
|
|
229
|
+
**It only helps pages you render through `BcmsBlocks`.** A hand-written `.astro` layout, or
|
|
230
|
+
content pulled into your own components, is invisible to the bridge — for those, structural
|
|
231
|
+
draft preview needs a server-rendered draft route instead (`output: 'server'` + the draft-mode
|
|
232
|
+
routes below).
|
|
233
|
+
|
|
234
|
+
## Draft mode
|
|
235
|
+
|
|
236
|
+
The integration injects `/api/bcms/draft/enable?token=<jwt>&redirect=/path` and
|
|
237
|
+
`/api/bcms/draft/disable`. Generate the preview-token link from the dashboard;
|
|
238
|
+
visiting `enable` validates the token against the backend, sets a signed cookie,
|
|
239
|
+
and subsequent loads return draft content. Disable with the `disable` route.
|
|
240
|
+
|
|
241
|
+
### Two draft lanes
|
|
242
|
+
|
|
243
|
+
| Lane | Build | How the canvas shows a draft |
|
|
244
|
+
| --- | --- | --- |
|
|
245
|
+
| SSR | `output: "server"` + the draft routes above + `BCMS_DRAFT_SECRET` + a `content:read:draft` `BCMS_API_KEY` | The loaders read the draft on every request, so hand-written layouts preview too |
|
|
246
|
+
| Static | Any static build, with the page body rendered through `<BcmsLiveBlocks>` | The editor pushes the draft block tree to the island over `postMessage`. No fetch, no key in the page. Only the `BcmsLiveBlocks` region updates |
|
|
247
|
+
|
|
248
|
+
## Headless projects (your own host)
|
|
249
|
+
|
|
250
|
+
BetterCMS never builds, releases or deploys a headless project. Any build, artifact or deploy
|
|
251
|
+
request for one returns **409 `HEADLESS_NO_BUILD`**.
|
|
252
|
+
|
|
253
|
+
- **Deploy:** push to your repository. Your provider (Vercel, Netlify or Cloudflare Pages)
|
|
254
|
+
builds it. For fresh content on publish, point the revalidation webhook at the provider's
|
|
255
|
+
rebuild hook.
|
|
256
|
+
- **Draft:** use your framework's draft route on the provider's **preview** deployment URL.
|
|
257
|
+
For Astro that is the SSR lane above: `output: "server"`, the injected
|
|
258
|
+
`/api/bcms/draft/*` routes, `BCMS_DRAFT_SECRET`, and a `BCMS_API_KEY` carrying
|
|
259
|
+
`content:read:draft`. Keep the draft key off production.
|