Storyblok & Astro
Storyblok 是一个基于组件的无头(Headless)CMS,它允许你使用名为 Bloks 的可重用组件来管理你的内容。
与 Astro 集成
标题为“与 Astro 集成”的部分在本节中,你将使用 Storyblok 集成 将 Storyblok 连接到 Astro。
先决条件
标题为“先决条件”的部分要开始,你需要具备以下条件
-
一个 Astro 项目 - 如果你还没有 Astro 项目,我们的安装指南将帮助你快速启动并运行。
-
一个 Storyblok 账户和空间 - 如果你还没有账户,可以免费注册并创建一个新空间。
-
Storyblok 预览令牌(Preview token) - 此令牌将用于获取内容的草稿和已发布版本。你可以在 Storyblok 空间设置的 Access Tokens 选项卡中找到并生成你的 API 令牌。
设置凭据
标题为“设置凭据”的部分要将你的 Storyblok 凭据添加到 Astro,请在项目根目录中创建一个 .env
文件,并包含以下变量:
STORYBLOK_TOKEN=YOUR_PREVIEW_TOKEN
现在,你应该可以在你的项目中使用这些环境变量了。
你的根目录现在应包含这个新文件:
目录src/
- …
- .env
- astro.config.mjs
- package.json
安装依赖
标题为“安装依赖项”的部分要将 Astro 与你的 Storyblok 空间连接,请使用下面适用于你首选包管理器的命令来安装官方 Storyblok 集成:
npm install @storyblok/astro vite
pnpm add @storyblok/astro vite
yarn add @storyblok/astro vite
配置 Storyblok
标题为“配置 Storyblok”的部分修改你的 Astro 配置文件以包含 Storyblok 集成:
import { defineConfig } from 'astro/config';import { storyblok } from '@storyblok/astro';import { loadEnv } from 'vite';
const env = loadEnv("", process.cwd(), 'STORYBLOK');
export default defineConfig({ integrations: [ storyblok({ accessToken: env.STORYBLOK_TOKEN, components: { // Add your components here }, apiOptions: { // Choose your Storyblok space region region: 'us', // optional, or 'eu' (default) }, }) ],});
Storyblok 集成需要一个包含以下属性的对象:
-
accessToken
- 引用你在上一步中添加的 Storyblok API 令牌。由于 Astro 配置文件通常不支持环境变量,请使用 Vite 的
loadEnv
函数来加载它们。 -
components
- 一个将 Storyblok 组件名称映射到本地组件路径的对象。这是在 Astro 中渲染 Storyblok Bloks 所必需的。组件路径是相对于
src
目录的。例如,如果你的组件位于src/storyblok/MyComponent.astro
,那么路径就是storyblok/MyComponent
(不带.astro
扩展名)。 -
apiOptions
- 包含 Storyblok API 选项的对象。默认情况下,区域是
eu
。如果你的 Storyblok 空间是在美国(US)区域创建的,你需要将区域设置为us
。
将 Bloks 连接到 Astro 组件
标题为“将 Bloks 连接到 Astro 组件”的部分要将你的 Bloks 连接到 Astro,请在 src
目录中创建一个名为 storyblok
的新文件夹。该文件夹将包含所有与你 Storyblok Blok 库中的 Bloks 相匹配的 Astro 组件。
在此示例中,你的 Storyblok 库中有一个 blogPost
Blok 内容类型,它具有以下字段:
title
- 文本字段description
- 文本字段content
- 富文本字段
我们的目标是创建等效的 Astro 组件,该组件将使用这些字段来渲染其内容。为此,请在 src/storyblok
内创建一个名为 BlogPost.astro
的新文件,内容如下:
---import { storyblokEditable, renderRichText } from '@storyblok/astro'
const { blok } = Astro.propsconst content = renderRichText(blok.content)---
<article {...storyblokEditable(blok)}> <h1>{blok.title}</h1> <p>{blok.description}</p> <Fragment set:html={content} /></article>
blok
属性包含你将从 Storyblok 收到的数据。它还包含在 Storyblok 中 blogPost
内容类型 Blok 中定义的字段。
为了渲染我们的内容,该集成提供了如下工具函数:
storyblokEditable
- 它为元素添加必要的属性,以便你可以在 Storyblok 中编辑它们。renderRichText
- 它将富文本字段转换为 HTML。
你的根目录现在应包含这个新文件:
目录src/
目录storyblok/
- BlogPost.astro
- .env
- astro.config.mjs
- package.json
最后,要将 blogPost
Blok 连接到 BlogPost
组件,请在 Astro 配置文件中的 components 对象中添加一个新属性。
- 键(key)是 Storyblok 中 Blok 的名称。在本例中,它是
blogPost
。 - 值(value)是组件的路径。在本例中,它是
storyblok/BlogPost
。
key
必须与 Storyblok 中的 Blok 名称完全匹配才能被正确引用。如果它们不匹配,或者你试图引用一个在 Storyblok 中不存在的组件,你将会收到一个错误。
import { defineConfig } from 'astro/config';import { storyblok } from '@storyblok/astro';import { loadEnv } from 'vite';
const env = loadEnv("", process.cwd(), 'STORYBLOK');
export default defineConfig({ integrations: [ storyblok({ accessToken: env.STORYBLOK_TOKEN, components: { blogPost: 'storyblok/BlogPost', }, apiOptions: { region: 'us', }, }) ],});
获取数据
标题为“获取数据”的部分为了测试设置,请在 Storyblok 中使用 blogPost
内容类型创建一个名为 test-post
的新 story。在 Astro 中,请在 src/pages/
目录中创建一个名为 test-post.astro
的新页面,内容如下:
---import { useStoryblokApi } from '@storyblok/astro'import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'
const storyblokApi = useStoryblokApi()
const { data } = await storyblokApi.get("cdn/stories/test-post", { version: import.meta.env.DEV ? "draft" : "published",});
const content = data.story.content;---<StoryblokComponent blok={content} />
要查询你的数据,请使用 useStoryblokApi
钩子(hook)。这将使用你的集成配置来初始化一个新的客户端实例。
要渲染你的内容,请将 Story 的 content
属性作为 blok
prop 传递给 StoryblokComponent
。此组件将渲染定义在 content
属性内部的 Bloks。在本例中,它将渲染 BlogPost
组件。
使用 Astro 和 Storyblok 制作博客
标题为“使用 Astro 和 Storyblok 制作博客”的部分集成设置完成后,你现在可以开始使用 Astro 和 Storyblok 创建一个博客了。
先决条件
标题为“先决条件”的部分-
一个 Storyblok 空间 - 对于本教程,我们建议使用一个新空间。如果你已经有一个包含 Bloks 的空间,可以随时使用它们,但你需要修改代码以匹配 Blok 名称和内容类型。
-
一个与 Storyblok 集成的 Astro 项目 - 请参阅与 Astro 集成部分,了解如何设置集成的说明。
创建 blok 库
标题为“创建 blok 库”的部分要创建 Bloks,请前往 Storyblok 应用并点击 Block Library 选项卡。点击 + New blok 按钮并创建以下 Bloks:
-
blogPost
- 一个内容类型 Blok,具有以下字段:title
- 文本字段description
- 文本字段content
- 富文本字段
-
blogPostList
- 一个空的可嵌套(nestable) Blok -
page
- 一个内容类型 Blok,具有以下字段:body
- 一个可嵌套(nestable) Blok
创建内容
标题为“创建内容”的部分要添加新内容,请点击 Content 选项卡进入内容部分。使用你在上一步创建的 Blok 库,创建以下 stories:
-
home
- 一个使用page
Blok 的内容类型 story。在body
字段内,添加一个blogPostList
Blok。 -
blog/no-javascript
- 一个位于 blog 文件夹内,使用blogPost
内容类型的 story。title: No JavaScriptdescription: A sample blog postcontent: Hi there! This blog post doesn't use JavaScript. -
blog/astro-is-amazing
- 一个位于 blog 文件夹内,使用blogPost
内容类型的 story。title: Astro is amazingdescription: We love Astrocontent: Hi there! This blog post was build with Astro.
现在你的内容已经准备好了,返回到你的 Astro 项目并开始构建你的博客。
将 Bloks 连接到组件
标题为“将 Bloks 连接到组件”的部分要将你新创建的 Bloks 连接到 Astro 组件,请在 src
目录中创建一个名为 storyblok
的新文件夹,并添加以下文件:
Page.astro
是一个可嵌套的 Blok 内容类型组件,它将递归地渲染 page
Blok 的 body
属性内的所有 Bloks。它还会将 storyblokEditable
属性添加到父元素,这将允许我们在 Storyblok 中编辑页面。
---import { storyblokEditable } from '@storyblok/astro'import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";const { blok } = Astro.props---
<main {...storyblokEditable(blok)}> { blok.body?.map((blok) => { return <StoryblokComponent blok={blok} /> }) }</main>
BlogPost.astro
将渲染 blogPost
Blok 的 title
、description
和 content
属性。
要将 content
属性从富文本字段转换为 HTML,你可以使用 renderRichText
辅助函数。
---import { storyblokEditable, renderRichText } from '@storyblok/astro'const { blok } = Astro.propsconst content = renderRichText(blok.content)---<article {...storyblokEditable(blok)}> <h1>{blok.title}</h1> <p>{blok.description}</p> <Fragment set:html={content} /></article>
BlogPostList.astro
是一个可嵌套的 Blok 内容类型组件,它将渲染一个博客文章预览列表。
它使用 useStoryblokApi
钩子来获取所有内容类型为 blogPost
的 stories。它使用 version
查询参数,在开发模式下获取 stories 的草稿版本,在生产构建时获取已发布版本。
Astro.props
用于在 Storyblok 中设置编辑器。如果需要,也可以在这里向你的组件传递额外的 props。
---import { storyblokEditable } from '@storyblok/astro'import { useStoryblokApi } from '@storyblok/astro'
const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.get('cdn/stories', { version: import.meta.env.DEV ? "draft" : "published", content_type: 'blogPost',})
const posts = data.stories.map(story => { return { title: story.content.title, date: new Date(story.published_at).toLocaleDateString("en-US", {dateStyle: "full"}), description: story.content.description, slug: story.full_slug, }})
const { blok } = Astro.props---
<ul {...storyblokEditable(blok)}> {posts.map(post => ( <li> <time>{post.date}</time> <a href={post.slug}>{post.title}</a> <p>{post.description}</p> </li> ))}</ul>
最后,将你的组件添加到 astro.config.mjs
中 storyblok
配置对象的 components
属性中。键是 Storyblok 中 Blok 的名称,值是相对于 src
的组件路径。
import { defineConfig } from 'astro/config';import { storyblok } from '@storyblok/astro';import { loadEnv } from 'vite';
const env = loadEnv("", process.cwd(), 'STORYBLOK');
export default defineConfig({ integrations: [ storyblok({ accessToken: env.STORYBLOK_TOKEN, components: { blogPost: 'storyblok/BlogPost', blogPostList: 'storyblok/BlogPostList', page: 'storyblok/Page', }, apiOptions: { region: 'us', }, }) ],});
生成页面
标题为“生成页面”的部分要为特定的 page
创建路由,你可以直接从 Storyblok API 获取其内容,并将其传递给 StoryblokComponent
组件。请记住确保你已经将 Page
组件添加到了你的 astro.config.mjs 中。
在 src/pages/
中创建一个 index.astro
文件来渲染 home
页面:
---import { useStoryblokApi } from '@storyblok/astro'import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'import BaseLayout from '../layouts/BaseLayout.astro'
const storyblokApi = useStoryblokApi();const { data } = await storyblokApi.get('cdn/stories/home', { version: import.meta.env.DEV ? "draft" : "published",});const content = data.story.content;---<html lang="en"> <head> <title>Storyblok & Astro</title> </head> <body> <StoryblokComponent blok={content} /> </body></html>
要为所有博客文章生成页面,请创建一个 .astro
页面来创建动态路由。这种方法根据你的路由是预渲染(Astro 中的默认设置)还是按需渲染而有所不同。
静态站点生成
标题为“静态站点生成”的部分如果你正在使用 Astro 的默认静态站点生成模式,你将使用动态路由和 getStaticPaths
函数来生成你的项目页面。
创建一个新目录 src/pages/blog/
并添加一个名为 [...slug].astro
的新文件,代码如下:
---import { useStoryblokApi } from '@storyblok/astro'import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'
export async function getStaticPaths() { const sbApi = useStoryblokApi();
const { data } = await sbApi.get("cdn/stories", { content_type: "blogPost", version: import.meta.env.DEV ? "draft" : "published", });
const stories = Object.values(data.stories);
return stories.map((story) => { return { params: { slug: story.slug }, }; });}
const sbApi = useStoryblokApi();const { slug } = Astro.params;const { data } = await sbApi.get(`cdn/stories/blog/${slug}`, { version: import.meta.env.DEV ? "draft" : "published",});
const story = data.story;---
<html lang="en"> <head> <title>Storyblok & Astro</title> </head> <body> <StoryblokComponent blok={story.content} /> </body></html>
此文件将为每个 story 生成一个页面,其 slug 和内容从 Storyblok API 获取。
在 Storyblok 内部添加文件夹时,与 Storyblok API 交互时请在 slug 中包含它们。例如,在上面的 GET 请求中,我们可以使用 cdn/stories/blog,其中包含一个 blog 文件夹,而不是在根目录使用它们。
按需渲染
标题为“按需渲染”的部分如果你正在使用适配器按需渲染你的路由,你将使用动态路由从 Storyblok 获取页面数据。
创建一个新目录 src/pages/blog/
并添加一个名为 [...slug].astro
的新文件,代码如下:
---import { useStoryblokApi } from '@storyblok/astro'import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'const storyblokApi = useStoryblokApi()const slug = Astro.params.slug;let content;try { const { data } = await storyblokApi.get(`cdn/stories/blog/${slug}`, { version: import.meta.env.DEV ? "draft" : "published", }); content = data.story.content} catch (error) { return Astro.redirect('/404')}---<html lang="en"> <head> <title>Storyblok & Astro</title> </head> <body> <StoryblokComponent blok={content} /> </body></html>
此文件将从 Storyblok 获取并渲染与动态 slug
参数匹配的页面数据。
由于你使用了到 /404
的重定向,请在 src/pages
中创建一个 404 页面:
<html lang="en"> <head> <title>Not found</title> </head> <body> <p>Sorry, this page does not exist.</p> </body></html>
如果找不到 story,请求将被重定向到 404 页面。
发布你的站点
标题为“发布你的站点”的部分要部署你的网站,请访问我们的部署指南并按照你偏好的托管提供商的说明进行操作。
在 Storyblok 内容变更时重新构建
标题为“在 Storyblok 内容变更时重新构建”的部分如果你的项目使用 Astro 的默认静态模式,你需要设置一个 webhook,以便在内容发生变化时触发新的构建。如果你使用 Netlify 或 Vercel 作为托管提供商,你可以使用它们的 webhook 功能来通过 Storyblok 事件触发新的构建。
Netlify
标题为“Netlify”的部分在 Netlify 中设置 webhook
-
转到你的站点仪表盘,然后点击 Build & deploy。
-
在 Continuous Deployment 选项卡下,找到 Build hooks 部分,然后点击 Add build hook。
-
为你的 webhook 提供一个名称,并选择你想要触发构建的分支。点击 Save 并复制生成的 URL。
Vercel
标题为“Vercel”的部分在 Vercel 中设置 webhook
-
转到你的项目仪表盘,然后点击 Settings。
-
在 Git 选项卡下,找到 Deploy Hooks 部分。
-
为你的 webhook 提供一个名称,并选择你想要触发构建的分支。点击 Add 并复制生成的 URL。
向 Storyblok 添加 webhook
标题为“向 Storyblok 添加 webhook”的部分在你的 Storyblok 空间的 Settings 中,点击 Webhooks 选项卡。将你复制的 webhook URL 粘贴到 Story published & unpublished 字段中,然后点击 Save 创建一个 webhook。
现在,每当你发布一个新的 story,就会触发一次新的构建,你的博客将会被更新。
官方资源
标题为“官方资源”的部分- Storyblok 提供了一个 Astro 集成,可用于将 Storyblok 添加到你的项目中。
社区资源
标题为“社区资源”的部分- Sandra Rodgers 的 让 Storyblok + Astro 的可视化编辑器正常工作
- Jonas Gierer 的 Astro + Storyblok:用于即时可视化编辑的 SSR 预览
- Sandra Rodgers 的 使用 Netlify 的分支部署功能实现 Astro-Storyblok 预览站点