Storyblok 与 Astro

Storyblok 是一个基于组件的无头(headless) CMS,允许你使用可重用的组件(称为 Bloks)管理内容。

在本小节中,你将使用 Storyblok 集成 来将 Storyblok 连接到 Astro。

开始前,你需要具备以下条件:

  1. Astro 项目 - 如果你还没有 Astro 项目,我们的安装指南将帮助你快速启动;
  2. Storyblok 帐号和空间 - 如果你还没有帐号,可以免费注册并创建一个新的空间;
  3. Storyblok 预览令牌 - 这个令牌将用于获取你的草稿内容和已发布版本。你可以在 Storyblok 空间设置的访问令牌选项卡中找到并生成你的 API 令牌。

为了将你的 Storyblok 凭据添加到 Astro,你可以在项目的根目录中创建一个名为 .env 的文件,并添加以下的变量:

.env
STORYBLOK_TOKEN=YOUR_PREVIEW_TOKEN

现在,你应该可以在项目中使用该环境变量了。

并且,你的根目录现在应该包含这一新文件:

  • 目录src/
  • .env
  • astro.config.mjs
  • package.json

为了将 Astro 连接到你的 Storyblok 空间,使用以下命令安装官方的 Storyblok 集成,可以用你偏好的包管理器执行相应命令:

终端窗口
npm install @storyblok/astro vite

修改 Astro 配置文件以包含 Storyblok 集成:

astro.config.mjs
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: {
// 在此处添加你的组件
},
apiOptions: {
// 选择你的 Storyblock 空间区域
region: 'us', // 可选项,默认为 'eu'
},
})
],
});

Storyblok 集成需要一个包含以下属性的对象:

  1. accessToken - 这是引用你在上一步中添加的 Storyblok API 令牌;

  2. components - 一个将 Storyblok 组件名称映射到本地组件路径的对象。这是必需的,为了能在 Astro 中渲染你的 Storyblok Bloks;

  3. apiOptions - 一个包含 Storyblok API 选项 的对象。

将 Bloks 连接到 Astro 组件

标题部分 将 Bloks 连接到 Astro 组件

要将你的 Bloks 连接到 Astro,请在 src 目录下创建一个名为 storyblok 的新文件夹。该文件夹将包含与你的 Storyblok Blok 库中的 Bloks 所匹配的所有 Astro 组件。

在这个例子中,假设你的 Storyblok Blok 库中有一个名为 blogPost 的 Blok 内容类型,其具有以下字段:

  • title - 一个文本字段
  • description - 一个文本字段
  • content - 一个富文本字段

我们的目标是创建一个同等的 Astro 组件,使用这些字段来渲染其内容。为此,在 src/storyblok 中创建一个名为 BlogPost.astro 的新文件,并添加以下内容:

src/storyblok/BlogPost.astro
---
import { storyblokEditable, renderRichText } from '@storyblok/astro'
const { blok } = Astro.props
const 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 配置文件的组件对象中添加一个新属性。

  • 键是 Storyblok 中的 Blok 名称。在这种情况下,它是 blogPost
  • 值是组件的路径。在这里,它是 storyblok/BlogPost
astro.config.mjs
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 的新故事。

在 Astro 中,在 src/pages/ 目录下创建一个名为 test-post.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 钩子函数。这将使用你的集成配置初始化一个新的客户端实例。

为了渲染你的内容,请将 Story 的 content 属性作为 blok 属性传递给 StoryblokComponent。该组件将渲染在 content 属性中定义的 Bloks。在这种情况下,它将会渲染 BlogPost 组件。

使用 Astro 和 Storyblok 创建博客

标题部分 使用 Astro 和 Storyblok 创建博客

通过设置集成,你现在可以使用 Astro 和 Storyblok 创建博客。

  1. Storyblok 空间 - 对于本教程,我们建议使用一个新的空间。如果你已经有一个包含 Blok 的空间,可以使用它们,但你需要修改代码以匹配 Blok 的名称和内容类型。

  2. 集成了 Storyblok 的 Astro 项目 - 参考与 Astro 集成中的说明来设置集成。

要创建 Blok,请转到 Storyblok 应用程序,然后点击 Block Library 选项卡。点击 + New blok 按钮,然后创建以下 Bloks:

  1. blogPost - 包含以下字段的内容类型 Blok:

    • title - 文本字段
    • description - 文本字段
    • content - 富文本字段
  2. blogPostList - 空的可嵌套 Blok

  3. page - 包含以下字段的内容类型 Blok:

    • body - 可嵌套 Blok

要添加新的内容,请点击 Content 选项卡,进入内容部分。请使用在上一步中创建的 Blok 库创建以下故事:

  1. home - 使用 page Blok 的内容类型故事。在 body 字段中添加一个 blogPostList Blok。

  2. blog/no-javascript - 位于博客文件夹中,使用 blogPost 内容类型的故事。

    title: 再见吧!JavaScript
    description: 一个博客文章样例
    content: 嗨!这是一个没有使用 JavaScript 的博客文章
  3. blog/astro-is-amazing - 位于博客文件夹中,使用 blogPost 内容类型的故事。

    title: 令人惊奇的 Astro
    description: 我们热爱 Astro
    content: 嗨!这是一个基于 Astro 而打造的博客文章

现在准备好了你的内容,然后返回到 Astro 项目中,开始构建你的博客。

为了将你新创建的 Bloks 连接到 Astro 组件,创建一个名为 storyblok 的新文件夹,放置到你的 src 目录中,并添加以下文件:

Page.astro 是一个可嵌套的 Block 类型组件,它会递归地渲染 page Blok 的 body 属性中的所有 Blok。它还会给父元素添加 storyblokEditable 属性,这样我们就可以在 Storyblok 中编辑页面。

src/storyblok/Page.astro
---
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 的 titledescriptioncontent 属性。

要将 content 属性从富文本字段转换为 HTML,您可以使用 renderRichText 辅助函数。

src/storyblok/BlogPost.astro
---
import { storyblokEditable, renderRichText } from '@storyblok/astro'
const { blok } = Astro.props
const 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 的文章。在开发模式下,它使用 version 查询参数来获取文章的草稿版本,在生产环境构建时则获取已发布的版本。

Astro.props 用于在 Storyblok 中设置编辑器,还可以在此处根据需要传递其他属性给该组件。

src/storyblok/BlogPostList.astro
---
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.mjsstoryblok 配置对象的 components 属性中,将你的组件添加进去。键是 Storyblok 中 Blok 的名称,值是相对于 src 的组件路径。

astro.config.mjs
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 组件。请确保在你的 astro.config.mjs 中添加了 Page 组件。

现在在 src/pages/ 目录下创建一个 index.astro 文件,用于渲染 home 页面:

src/pages/index.astro
---
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 的默认静态站点生成功能,你将使用动态路由getStaticPaths 函数来生成你的项目页面。

创建一个名为 src/pages/blog/ 的新目录,并添加一个名为 [...slug].astro 的新文件,其中包含以下代码:

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/${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>

这个文件将为每个故事生成一个页面,页面的 slug 和内容都从 Storyblok API 中获取。

如果你已经启用了 SSR 模式,你将使用动态路由从 Storyblok 获取页面数据。

src/pages/blog/ 目录下创建一个名为 [...slug].astro 的新文件,并添加以下代码:

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/${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 页面:

src/pages/404.astro
<html lang="en">
<head>
<title>Not found</title>
</head>
<body>
<p>Sorry, this page does not exist.</p>
</body>
</html>

如果找不到故事,请求将被重定向到 404 页面。

要部署你的网站,请访问我们的部署指南,并按照你所偏好的托管提供商的步骤说明进行操作。

当 Storyblok 发生更改时重新构建

标题部分 当 Storyblok 发生更改时重新构建

如果你的项目使用 Astro 的默认静态模式,你需要设置一个 Webhook,在内容更改时触发新的构建。如果你的托管提供商是 Netlify 或 Vercel,则可以使用其 Webhook 功能来根据 Storyblok 事件触发新的构建。

在 Netlify 中设置 Webhook 的步骤如下:

  1. 进入你的站点仪表板,点击 Build & deploy
  2. Continuous Deployment 选项卡下,找到 Build hooks 部分,点击 Add build hook
  3. 为你的 Webhook 提供一个名称,并选择要触发构建的分支。点击 Save 并复制生成的 URL。

在 Vercel 中设置 Webhook 的步骤如下:

  1. 进入你的项目仪表板,点击 Settings
  2. Git 选项卡下,找到 Deploy Hooks 部分;
  3. 为你的 Webhook 提供一个名称和要触发构建的分支。点击 Add 并复制生成的 URL。
将 Webhook 添加到 Storyblok
标题部分 将 Webhook 添加到 Storyblok

在 Storyblok 空间的 Settings 中,点击 Webhooks 选项卡,将你复制的 Webhook URL 粘贴到 Story published & unpublished 字段中,然后点击 Save 创建 Webhook。

现在,每当你发布一个新的文章,都将触发一个新的构建,并更新你的博客。

  • Storyblok 提供了一个 Astro 集成,可以将 Storyblok 添加到你的项目中。

更多 CMS 指南