@ bettercms-ai/ next (英语)
BetterCMS 适配器用于 下一个.js (App路由器). 输入,缓存 - 意识内容读取,
预览草稿,以及 llms.txt ——CMS下的定型地层.
与 @bettercms-ai/codegen: 生成 BetterCMSSchema 从你的
内容模型,然后获得完整类型 getEntry编号 :listEntries 自动完成.
安装
npm install @bettercms-ai/next
# peers: next >= 14, react >= 18
快开始
// lib/cms.ts
import { createBetterCMS } from "@bettercms-ai/next";
import type { BetterCMSSchema } from "./bettercms.generated"; // from @bettercms-ai/codegen
export const cms = createBetterCMS<BetterCMSSchema>({
workspace: "acme",
apiKey: process.env.BETTERCMS_API_KEY,
// baseUrl defaults to https://api.bettercms.ai/api/v1/delivery
revalidate: 60, // default ISR window (seconds); false = always fresh
});
// app/blog/[slug]/page.tsx
import { cms } from "@/lib/cms";
import { notFound } from "next/navigation";
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const entry = await cms.getEntry<{ title: string; body: string }>(slug, {
revalidate: 300,
// The exact tags the revalidation webhook sends on publish (see "Caching & revalidation").
tags: ["bcms:acme:all", `bcms:acme:entry:${slug}`],
});
if (!entry) notFound();
return <article><h1>{entry.fields.title}</h1>{/* ... */}</article>;
}
// app/blog/page.tsx — list, typed by your model registry
const { items, hasNextPage } = await cms.listEntries("blog", { page: 1, perPage: 20 });
环境变量
createBetterCMS, (中文(简体) ), createDraftModeRoute, (中文(简体) ), createRevalidateRoute 财务报告和审定财务报表 <VisualEditing> 读取
没有 环境变量。 你传递每个值作为选项, 所以这些例子中的名称
只是一个公约。 套件在两个地方读出:
| 变量 | 阅读者 | 含义 |
|---|---|---|
BETTERCMS_API_KEY | bettercms-snapshot 国 际 | 能够读取模型和条目的工程密钥( 需要) |
BETTERCMS_WORKSPACE | bettercms-snapshot 国 际 | 工作空间弹片( 需要) |
BETTERCMS_API_URL | bettercms-snapshot 国 际 | API 基础, 默认 https://api.bettercms.ai/api/v1 |
BETTERCMS_PREVIEW | getContent() | 1 读取活字代替 bcms-content.json (审查楼) |
复次密语. 该值是项目重新验证的 Webhook 秘密(项目 −)
设置 – 调整 。 存储为 BCMS_REVALIDATE_SECRET数字 : createRevalidateRoute() 读取
当没有时 secret 通过。 没有两者之一,该路线接受未经签名的请求.
更好的CMS生成的部署工作流程 (连通回波). 重案组的秘密是 BCMS_API_KEY
(只给内容-牵引步骤,从不给构建) 工作空间是repo
变量 BCMS_WORKSPACE_SLUG。建设步骤得到 PUBLIC_BCMS_API_URL, (中文(简体) ),
PUBLIC_BCMS_WORKSPACE, (中文(简体) ), PUBLIC_BCMS_PROJECT_ID 财务报告和审定财务报表 PUBLIC_SITE_URL此软件包为零
他们为你。 让他们进去 createBetterCMS 你自己来 除非
你用一个 NEXT_PUBLIC_ 前缀.
API 密码
createBetterCMS<Schema>(config)
workspace——工作空间弹(需要)apiKey——交付 API 密钥(按:Authorization: Bearer(中文(简体) )baseUrl编号 :previewBaseUrl——覆盖 API 基础revalidate——默认的IRS窗口(秒)或false
getEntry<TFields>(slug, opts?) → BetterCMSEntry<TFields> | null
由内容 swit 单独发布的条目. null 404号公路.
选项 : revalidate, (中文(简体) ), tags, (中文(简体) ), depth (0至2回水合), select (外地预测)
结果是 Partial<TFields>, (中文(简体) ), preview + 键 previewToken。 。 。 .
listEntries(model, opts?) → EntryList<Fields>
发布一个模型的条目,被标注. 掷出 BetterCMSError (CONTENT NOT FOUND) (法语)
对于未知的工作空间/模型;空模型返回空页.
选项 : page, (中文(简体) ), perPage, (中文(简体) ), revalidate, (中文(简体) ), tags, (中文(简体) ), depth, (中文(简体) ), select。 。 。 .
缓存重新验证( R)
读取流经 fetch,下一个补丁到数据缓存中。 过 tags 打电话
revalidateTag(tag) 发表. revalidate: false 与 标记 = 缓存-直到-tag;
无标签 = no-store (总是新鲜的).
重新验证的 Webhook 为每个已公布或未公布的资源发送了两个标记,
财务报告和审定财务报表 createRevalidateRoute 电话 revalidateTag 各页:
bcms:{workspace}:all: 工作空间中的一切bcms:{workspace}:{type}:{slug}时type实值entry或者说page
标记用这些字符串读取( T)bcms:acme:entry:${slug}, (中文(简体) ), bcms:acme:page:${slug}, (中文(简体) ),
bcms:acme:all 页:1 客户端不为您添加, 以及其它形状的标签
绝不作废.
以“ 重新验证” 模式部署的目标( 在其他地方托管的网站) 可能指向同一路线: 它 刷新调用名称的路径和标记, 或整个站点没有命名时的标记 .
转换的页面的值, live (bcmsPage(中文(简体) )
一个网站 @bettercms-ai/convert 转换读取每页的值 bcms-content/<slug>.json。 。 。 .
bcmsPage 读同页直播,形状相同,所以帮助者 bcms() 原文不变:
// app/page.tsx (Server Component)
import { bcmsPage } from "@bettercms-ai/next";
import { BcmsPage } from "@bettercms-ai/next/page";
import bcmsHomeSnapshot from "../bcms-content/home.json";
export default async function Page() {
const bcmsHome = await bcmsPage("home", bcmsHomeSnapshot);
return <BcmsPage value={bcmsHome}>{/* … */}</BcmsPage>;
}
// a "use client" file under it
import { useBcmsPage } from "@bettercms-ai/next/page";
const page = useBcmsPage();
在“草稿模式”中,它读取预览(/api/v1/preview/<slug>,无斑. 否则它会读到
从送出时已公布行, 缓存到公布名称(bcms:{ws}:all, (中文(简体) ),
bcms:{ws}:entry:{slug}, (中文(简体) ), bcms:{ws}:page:{slug}),因此页面保持静态
createRevalidateRoute 刷新它。 当读取失败( 离线建筑) 时, 它会倒回
已发布的内容文件( e)BCMS_CONTENT_FILE),然后到你通过的快照。 客户端组件获得
同一对象通过 useBcmsPage(),因此服务器 HTML 和水合页面显示相同的副本.
CMS的图像(next.config(中文(简体) )
CMS 图像为远程 URL cdn.bettercms.ai,哪个 next/image 拒绝直到
images.remotePatterns 允许它。 环绕配置 :
// next.config.ts
import { withBetterCMS } from "@bettercms-ai/next/config";
export default withBetterCMS(nextConfig);
它增加了媒体主持人(和 NEXT_PUBLIC_BCMS_MEDIA_URL,当网站服务媒体从它的
之后的图案,一次, 并留下其余的配置是.
预览草稿
草稿只来自签名的预览端点,通过 getEntry数字 :
// app/api/draft/route.ts — validates the dashboard's preview token, enables Draft Mode,
// stores the token in a cookie, then redirects
import { createDraftModeRoute } from "@bettercms-ai/next";
export const { GET } = createDraftModeRoute({ apiUrl: "https://api.bettercms.ai" });
import { isDraftEnabled, getPreviewToken } from "@bettercms-ai/next";
const previewToken = (await isDraftEnabled()) ? await getPreviewToken() : null;
const entry = previewToken
? await cms.getEntry(slug, { preview: true, previewToken }) // uncached
: await cms.getEntry(slug, { tags: [`bcms:acme:entry:${slug}`] });
getPage 已经 无选项草稿。它总是返回已公布的页面。 显示草稿
标记范围 getEntry 预览见上文。 对于视觉编辑器画布, 包裹
页面的块 <BcmsDraftBridge> (单位:千美元)@bettercms-ai/next/draft-bridge改为
<BcmsBlocks>编辑将块树草稿推向它 postMessage,所以页面
什么都拿不出来 打开 <BcmsDraftMarker /> 在“草稿模式”的根布局中.
静态导出( N)output: "export"(中文(简体) )
运行 bettercms-snapshot 在此之前 next build,并读取内容 getContent(config)数字 :
{ "scripts": { "build": "bettercms-snapshot && next build" } }
无 bcms-content.json,读取回 活 no-store 获取。 这条路就是这样
动态,以及 output: "export" 悄悄地放下它们,留下一空 out/。 。 。 . getContent 转弯
声入人心 SNAPSHOT_MISSING 构建错误。 设定 BETTERCMS_PREVIEW=1 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}读到生活
相反,预览构建.
llms.txt
// app/llms.txt/route.ts
import { llmsTxtRoute } from "@bettercms-ai/next";
export const GET = llmsTxtRoute({ models, title: "Acme", workspace: "acme" });
generateLlmsTxt(models, opts) (纯)和 fetchModels({ apiUrl, apiKey }) 也被导出.
作者控制的值被逃脱,所以内容不能伪造文档结构.
表单
在内容快照中填表飞船( E)bcms-content.json → forms,由建筑物写成
行动。 土生土长的 <BcmsForm> ——真正的输入,没有iframe,你自己的风格.
它处理有条件 showIf 字段、 URL- query 预填充、 蜂蜜壶、 可选
旋转部件、 校验错误和提交 。 没有 API 密钥( 端点是
转弯相接.
// app/contact/page.tsx — a Server Component reads the Action's snapshot...
import { getForm } from "@bettercms-ai/next";
import { ContactForm } from "./contact-form";
export default function ContactPage() {
const form = getForm("Contact"); // by name or id, from bcms-content.json
return form ? <ContactForm form={form} /> : null;
}
// ./contact-form.tsx — <BcmsForm> ships from its own "use client" entry.
import { BcmsForm } from "@bettercms-ai/next/form";
import type { DeliveryForm } from "@bettercms-ai/sdk";
export function ContactForm({ form }: { form: DeliveryForm }) {
return <BcmsForm form={form} turnstileSiteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} />;
}
不在反应上? 从任何地方提交框架不可知核心:
import { submitForm } from "@bettercms-ai/sdk";
await submitForm({ formId, data: { email: "[email protected]" }, turnstileToken });
仪表板的复制版嵌入片段仍然是非CMS站点的零积分倒计时.
页块( E)<BcmsBlocks>——表格放入页面构建器
当你在BetterCMS仪表板上建出一个页面并放下一个 窗体块 打开它
表格将它完全排入您放置的位置。 调整页面的块
<BcmsBlocks> 并将表格从 readForms() a 包括: form 块解析其
formId 对付他们,使他们 <BcmsForm> 给你的 其它区块类型
(标题、文字、图像、按钮、空间器、视频、列)
标记可以样式.
🔴 过
components={readComponents()}我也一样. 以章节形式撰写的页面—— 仪表板的"添加一款"采取器和AI两种产品——都是列表component块. A 类component未交付定义的块 无,所以一个页面可以 建立绿色并返回时只显示您的版面导航和页脚。 两个读者都从 同样的bcms-content.json所以这需要多打一个电话.
// app/[slug]/page.tsx
import { createBetterCMS, readForms, readComponents } from "@bettercms-ai/next";
import { BcmsBlocks } from "@bettercms-ai/next/blocks"; // its own "use client" entry
const cms = createBetterCMS({ workspace: "my-workspace", apiKey: process.env.BETTERCMS_API_KEY! });
export default async function Page({ params }: { params: { slug: string } }) {
// The page's blocks come from the delivery API (`entry.blocks`); forms from the snapshot.
const page = await cms.getPage(params.slug);
const { forms, turnstileSiteKey } = readForms();
return (
<BcmsBlocks
blocks={page.blocks}
forms={forms}
// 🔴 NOT optional in practice. A page built out of SECTIONS is a list of `component`
// blocks, and a `component` block with no matching definition renders NOTHING. Omit
// this and such a page builds green, returns 200, and shows only your nav and footer.
components={readComponents()}
turnstileSiteKey={turnstileSiteKey ?? undefined}
/>
);
}
CMS顺序中的章节( :<BcmsSections>(中文(简体) )
页面的章节被写在自己的文件中,使其按其CMS页面列表的顺序排列 他们。 服务器组件( 没有客户端 JavaScript), 包含自己的条目 :
import { sectionsFor } from "@bettercms-ai/next";
import { BcmsSections, BcmsSection } from "@bettercms-ai/next/sections";
export default function Home() {
return (
<main>
<BcmsSections {...sectionsFor("home")}>
<BcmsSection bind="hero">{(root) => <Hero rootProps={root} />}</BcmsSection>
<BcmsSection bind="code:sec1"><LogoStrip /></BcmsSection>
<BcmsSection bind="features"><section className="features">…</section></BcmsSection>
</BcmsSections>
</main>
);
}
- 一个被捆绑在一区块的区块 使它在区块的位置。 一个DOM元素得到
data-bcms-block, (中文(简体) ),data-bcms-style和块的风格克隆到它。 一个组成部分的孩子是: 给定函数代替: 展开root在它制造的元素。 没有函数 a 组件被标记在一个框上:display:contents街坊没有风格,有一个display:flow-root框中,如果样式已经存在,则随行(在弹性或网格母框中,区域是 (然后是一个项目). - 编辑器中添加的一节( 一个块,没有绑定)通过
<BcmsBlocks>。 。 。 . - 无块名称的一节 :
code:<bind>总是让你写的地方。 一个赤裸的<bind>已经 直到列表中指定页面的一栏;然后是已删除的一栏. blocks无效( 无快照 ) , 使每个孩子都处于源头顺序, 不受影响 .
页面路由器:页面也在浏览器中制作,所以列表必须是页面道具:
export const getStaticProps = sectionsStaticProps("home"),则
<BcmsSections {...bcmsSections}> 在页中需要 { bcmsSections }。带有数据的页面
其功能通过: sectionsStaticProps("home", pageStaticProps) (或
sectionsServerSideProps) 调用并添加 bcmsSections 对道具 它回答.
npx @bettercms-ai/convert --sections 写这个包裹给你.
带有国家代码拾取器的电话字段
作者启用的电话字段 国家代码拾取器 在建设者中使一个国家
控制在号码旁边 不管访客选什么 球场还是呈递 一个值, (中文(简体) ),
e.164(在自己的密钥下)+14155550123)——没有第二把钥匙可以处理.
两个班是你的风格:
| 类 | 元素 |
|---|---|
bcms-phone | 国家控制周围的包装和数字 |
bcms-phone-country | 国家控制自己 |
@bettercms-ai/next 格式化输入的数字。 主题及其电话元数据
(~80KB)生活在一个 单独块, 只有在表单有取取器时才装入 a 包括:
没有一艘飞船 直到那个块到达 田地是平地 tel 投入
里面一样 bcms-phone 包装,所以你的CSS应用前后.
SEO——每页 <head> 与 generateMetadata
getPage() 返回页面 metaTitle, (中文(简体) ), metaDescription有钱人 metaJson
(OG / Twitter / canonical / JSON-LD)在仪表板的SEO面板上编辑. 让他们进入
路线 <head> 与 buildMetadata – 网页默认值的每页值层
(同一页面翻页优先 *.bettercms.site 渲染器用途:
// app/[slug]/page.tsx
import type { Metadata } from "next";
import { createBetterCMS, buildMetadata, resolveSeo, type SiteSeoDefaults } from "@bettercms-ai/next";
const cms = createBetterCMS({ workspace: "my-workspace", apiKey: process.env.BETTERCMS_API_KEY! });
// Your project-wide fallbacks (optional). Per-page meta always wins.
const siteDefaults: SiteSeoDefaults = {
metaDescription: "Selected work, experience, and contact information.",
ogImage: "https://example.com/og-default.png",
twitterHandle: "@acme",
};
export async function generateMetadata(
{ params }: { params: { slug: string } },
): Promise<Metadata> {
const page = await cms.getPage(params.slug);
if (!page) return {};
return buildMetadata(page, siteDefaults);
}
buildMetadata 覆盖 <title>、描述、神话、开放图和推特.
JSON -LD不是下一个的一部分 Metadata,所以从页面组件中用
resolveSeo(...).jsonLd数字 :
export default async function Page({ params }: { params: { slug: string } }) {
const page = await cms.getPage(params.slug);
if (!page) return null;
const { jsonLd } = resolveSeo(page, siteDefaults);
return (
<>
{jsonLd.map((node, i) => (
<script
key={i}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(node) }}
/>
))}
{/* …render page.blocks… */}
</>
);
}
在发布时保持 SSO 新鲜
阅读内容为:revalidate,默认为60s),所以在窗口后出现 SEO 编辑
e. 对于 即时 发布后刷新, 配置工程的 重新验证
网点 (项目 —— 设置) createRevalidateRoute 处理器,通过
网络隐藏为 secret标记您的读取 bcms:{workspace}:… 标签来源
(原始内容存档于2012-10-10). Caching & revalidation. cms.getPage(slug, { tags: [`bcms:acme:page:${slug}`] }) 这是
页面表单。 没有了网页浏览器,编辑仍然在传播,但是在IRS的间隔上而不是
马上.
许可证
UNLICENSED——内部BetterCMS包.