@bettercms-ai/astro

中文文档
0.33.0发布于 2026/11/05TypeScriptES Module近 30 天下载 4361

The BetterCMS adapter for Astro — a bettercms() integration, a bettercms:client virtual module, typed content loaders, draft preview, and native .astro rendering components.

原始使用文档
文档目录12 节
  1. Install
  2. Setup
  3. Environment variables
  4. Reading content
  5. SEO — per-page <head>
  6. Components
  7. Phone fields with a country code picker
  8. Sections in the CMS order: <BcmsSections>
  9. Live editing on a static site — <BcmsLiveBlocks>
  10. Draft mode
  11. Two draft lanes
  12. Headless projects (your own host)

@bettercms-ai/astro

The BetterCMS adapter for Astro — a bettercms() integration, a bettercms:client virtual module, typed content loaders, draft preview, and native .astro rendering components. The Astro equivalent of @sanity/astro.

Install

npm install @bettercms-ai/astro

Setup

// astro.config.mjs
import { defineConfig } from "astro/config";
import bettercms from "@bettercms-ai/astro";

export default defineConfig({
  integrations: [
    bettercms({
      apiUrl: "https://api.bettercms.ai", // or PUBLIC_BCMS_API_URL
      workspace: "my-workspace",          // or PUBLIC_BCMS_WORKSPACE
      // projectId, studioUrl, mediaUrl optional
    }),
  ],
  output: "server", // required for draft mode + live reads
});

Environment variables

Every public value resolves in this order: bettercms({ ... }) option, then env, then the bcms-content.json snapshot the deploy workflow writes before the build.

VariableOptionReadDefault / notes
PUBLIC_BCMS_API_URLapiUrlconfig timeRequired (option, env or snapshot)
PUBLIC_BCMS_WORKSPACEworkspaceconfig timeRequired (option, env or snapshot)
PUBLIC_BCMS_PROJECT_IDprojectIdconfig timeOptional project scope for forms
PUBLIC_BCMS_MEDIA_URLmediaUrlconfig timehttps://cdn.bettercms.ai
PUBLIC_BCMS_STUDIO_URLstudioUrlconfig timeUnset. <BcmsVisualEditing> falls back to https://bettercms.ai
BCMS_API_KEYnoneserver runtimecontent:read, or content:read:draft for draft previews. Never bundled
BCMS_DRAFT_SECRETnoneserver runtimeSigns the draft cookie. Runtime process.env wins over a build-time value. Without it /api/bcms/draft/enable returns 500

The BetterCMS-generated deploy workflow sets PUBLIC_BCMS_API_URL, PUBLIC_BCMS_WORKSPACE and PUBLIC_BCMS_PROJECT_ID for the build. Its repo secret BCMS_API_KEY reaches only the content-fetch step, never the build. On your own host, set the two server variables yourself.

This package ships no revalidation route. Point the project's revalidation webhook (Project → Settings) at your rebuild hook. Its secret (stored as BCMS_REVALIDATE_SECRET) signs the body as x-bettercms-signature: sha256=<hmac>.

Add the virtual-module + Astro.locals types to src/env.d.ts:

/// <reference types="@bettercms-ai/astro/env" />

Reading content

---
import { loadPage, loadForms } from "bettercms:client";
import BcmsBlocks from "@bettercms-ai/astro/components/BcmsBlocks.astro";

const page = await loadPage(Astro, "home");
const { items: forms, turnstileSiteKey } = await loadForms(Astro);
---
{page && <BcmsBlocks blocks={page.blocks} forms={forms} turnstileSiteKey={turnstileSiteKey} />}

Loaders take Astro so they automatically read drafts when draft mode is on: loadPage, loadEntry, loadEntries, loadForms. The raw client and getClient(Astro) are exported too.

SEO — per-page <head>

loadPage() returns the page's metaTitle, metaDescription, and the rich metaJson (OG / Twitter / canonical / JSON-LD) edited in the dashboard's SEO panel. Resolve them page-over-site with resolveSeo and map the result into your <head> (same precedence as the live *.bettercms.site renderer):

---
import { loadPage } from "bettercms:client";
import { resolveSeo, type SiteSeoDefaults } from "@bettercms-ai/astro";

const { slug } = Astro.params;
const page = await loadPage(Astro, slug ?? "home");

const siteDefaults: SiteSeoDefaults = {
  metaDescription: "Selected work, experience, and contact information.",
  ogImage: "https://example.com/og-default.png",
  twitterHandle: "@acme",
};

const seo = page ? resolveSeo(page, siteDefaults) : null;
---
{seo && (
  <Fragment slot="head">
    <title>{seo.title}</title>
    {seo.description && <meta name="description" content={seo.description} />}
    {seo.canonical && <link rel="canonical" href={seo.canonical} />}
    {seo.og.title && <meta property="og:title" content={seo.og.title} />}
    {seo.og.description && <meta property="og:description" content={seo.og.description} />}
    {seo.og.image && <meta property="og:image" content={seo.og.image} />}
    <meta property="og:type" content={seo.og.type} />
    <meta name="twitter:card" content={seo.twitter.card} />
    {seo.twitter.image && <meta name="twitter:image" content={seo.twitter.image} />}
    {seo.twitter.site && <meta name="twitter:site" content={seo.twitter.site} />}
    {seo.jsonLd.map((node) => (
      <script type="application/ld+json" set:html={JSON.stringify(node)} />
    ))}
  </Fragment>
)}

Reads are cached; a SEO edit appears on the next fetch. For instant refresh on publish, configure the project's revalidation webhook (Project → Settings) against your rebuild or on-demand-revalidation hook.

Components

ComponentPurpose
@bettercms-ai/astro/components/BcmsBlocks.astroRender 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.
@bettercms-ai/astro/components/BcmsForm.astroRender + submit a form (conditional fields, honeypot, Turnstile).
@bettercms-ai/astro/components/BcmsImage.astroOptimized image with a 1x/2x srcset via the media transform endpoint.
@bettercms-ai/astro/components/BcmsLiveBlocks.astroBcmsBlocks as a hydrated island that lets the Visual Editor re-render your DRAFTS with your own components and CSS. See below.

Markup is class-driven and unstyled — you own the CSS.

Phone fields with a country code picker

A phone field whose author enabled Country code picker in the builder renders a country control beside the number. Whatever the visitor picks, the field still submits one value, under its own key, in E.164 (+14155550123) — there is no second key to handle.

Two classes are yours to style:

ClassElement
bcms-phonethe wrapper around the country control and the number
bcms-phone-countrythe country control itself

The country control is a native <select> built at build/SSR time, so no phone library reaches the visitor's browser. The trade is that the number is not reformatted as it is typed — the value is still composed correctly on submit. @bettercms-ai/next makes the other choice and ships a formatter in a lazy chunk.

Sections in the CMS order: <BcmsSections>

A page whose sections are written in its .astro file renders them in the order its CMS page lists them. Each section goes in a named slot, named by its placement's bind; the markup inside is yours and is rendered as written.

---
import BcmsSections from "@bettercms-ai/astro/components/BcmsSections.astro";
---
<Layout>
  <BcmsSections page="home">
    <Fragment slot="hero"><Hero /></Fragment>
    <Fragment slot="code:sec1"><LogoStrip /></Fragment>
    <Fragment slot="features"><Features /></Fragment>
  </BcmsSections>
</Layout>
  • A block bound to a slot renders that slot at the block's position. Its root element gets data-bcms-block, data-bcms-style and the block's style; no element is added around it.
  • A section added in the editor (a block with no bind) is drawn by <BcmsBlocks>, which <BcmsSections> imports itself. It needs no bettercms() on the build: without the integration the components read PUBLIC_BCMS_* and the content snapshot. Blocks swaps in another renderer.
  • A slot no block names: code:<bind> always renders where you wrote it. A bare <bind> does too until the list names one of the page's slots; after that it is a removed section.
  • With no snapshot (a local build, the API down) every slot renders in source order, untouched.

The block list is the published snapshot's (bcms-content.json), or the draft's when the Visual Editor frames a server-rendered page in draft mode. Pass blocks to give it yourself. npx @bettercms-ai/convert --sections writes this wrap for you.

Live editing on a static site — <BcmsLiveBlocks>

When the Visual Editor frames a static build it can only show what was built, so editing a draft falls back to the platform's approximate renderer and the canvas stops looking like your site. <BcmsLiveBlocks> fixes that for pages whose body is a block tree: it renders the same markup <BcmsBlocks> does, but as a client:load island that accepts draft documents from the editor over postMessage (same-origin, parent frame only, and only after the editor's own handshake — a third-party page that iframes your site is on a foreign origin and gets nothing). Nothing is fetched by the page and no credentials enter it.

It needs a JSX renderer, which a component cannot add for you:

npm i @astrojs/preact preact
// astro.config.mjs
import preact from "@astrojs/preact";
export default defineConfig({ integrations: [bettercms(), preact({ compat: true })] });
---
import BcmsLiveBlocks from "@bettercms-ai/astro/components/BcmsLiveBlocks.astro";
const page = await getPage(Astro.params.slug);
const { forms } = await readForms();
---
<BcmsLiveBlocks slug={page.slug} blocks={page.blocks} forms={forms} />

compat: true is required — the island reuses the React <BcmsDraftBridge> from @bettercms-ai/next so both SDKs speak one protocol. Roughly 10 kB of Preact plus the block renderer; @astrojs/react works too if your project already uses it.

⚠️ It emits the React class names, not this package's. <BcmsBlocks> from @bettercms-ai/next classes its output bcms-block bcms-block--text, bcms-button--primary, bcms-column; BcmsBlocks.astro above classes the same tree bcms-text, bcms-btn-primary, bcms-cols. They are NOT interchangeable: a stylesheet written for the .astro grammar styles nothing on this island, so swapping a page over unstyles it. Cover both spellings — the starters wrap them in one :is() selector for exactly this reason — or take the class list from @bettercms-ai/next's src/blocks.tsx.

It only helps pages you render through BcmsBlocks. A hand-written .astro layout, or content pulled into your own components, is invisible to the bridge — for those, structural draft preview needs a server-rendered draft route instead (output: 'server' + the draft-mode routes below).

Draft mode

The integration injects /api/bcms/draft/enable?token=<jwt>&redirect=/path and /api/bcms/draft/disable. Generate the preview-token link from the dashboard; visiting enable validates the token against the backend, sets a signed cookie, and subsequent loads return draft content. Disable with the disable route.

Two draft lanes

LaneBuildHow the canvas shows a draft
SSRoutput: "server" + the draft routes above + BCMS_DRAFT_SECRET + a content:read:draft BCMS_API_KEYThe loaders read the draft on every request, so hand-written layouts preview too
StaticAny 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

Headless projects (your own host)

BetterCMS never builds, releases or deploys a headless project. Any build, artifact or deploy request for one returns 409 HEADLESS_NO_BUILD.

  • Deploy: push to your repository. Your provider (Vercel, Netlify or Cloudflare Pages) builds it. For fresh content on publish, point the revalidation webhook at the provider's rebuild hook.
  • Draft: use your framework's draft route on the provider's preview deployment URL. For Astro that is the SSR lane above: output: "server", the injected /api/bcms/draft/* routes, BCMS_DRAFT_SECRET, and a BCMS_API_KEY carrying content:read:draft. Keep the draft key off production.

版本与发布记录

@bettercms-ai/astro 共发布 63 个版本,首次发布于 2026/06/30;过去 12 个月发布了 63 个版本。

版本发布日期距今
0.33.0latest2026/11/052 天前
0.32.22026/11/026 天前
0.32.12026/10/318 天前
0.32.02026/10/318 天前
0.31.22026/10/2712 天前
0.31.12026/10/2613 天前

关于 @bettercms-ai/astro 的常见问题

@bettercms-ai/astro 是什么?

The BetterCMS adapter for Astro — a bettercms() integration, a bettercms:client virtual module, typed content loaders, draft preview, and native .astro rendering components.

如何安装 @bettercms-ai/astro?

在项目目录运行 npm install @bettercms-ai/astro,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:astro(>=4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bettercms-ai/astro 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bettercms-ai/astro 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bettercms-ai/astro 可以商用吗?

@bettercms-ai/astro 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@bettercms-ai/astro 的下载量有多少?

根据 npm 官方下载统计,@bettercms-ai/astro 在 2026/10/08 – 2026/11/06 这 30 天内被下载 4,361 次,日均约 145 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bettercms-ai/astro 还在维护吗?

最新版本 0.33.0 发布于 2026/11/05(2 天前);至今共发布 63 个版本,其中过去 12 个月发布了 63 个;首次发布于 2026/06/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。