title | description | type | service | i18nReady |
---|---|---|---|---|
Contentful 与 Astro |
使用 Contentful 作为 CMS 向你的 Astro 项目添加内容 |
cms |
Contentful |
true |
import { FileTree } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; import { Steps } from '@astrojs/starlight/components';
Contentful 是一个无头(headless) CMS,允许你管理内容,集成其他服务并发布到多个平台。
在本节中,我们将使用 Contentful SDK 来连接你的 Contentful 空间与 Astro,实现零客户端 JavaScript。
首先,你需要以下内容:
-
一个 Astro 项目 - 如果你还没有 Astro 项目,我们的安装指南会帮助你迅速上手。
-
Contentful 账号和 Contentful 空间。如果你还没有账号,可以注册一个免费账号并创建一个新的 Contentful 空间。如果你已经有一个空间,也可以使用现有空间。
-
Contentful 凭证 - 你可以在 Contentful 仪表板中的设置 > API 密钥中找到以下凭证。如果你还没有 API 密钥,请选择添加 API 密钥。
- Contentful space ID - 你的Contentful 空间 的 ID。
- Contentful delivery access token - 用于从你的 Contentful 空间获取已发布内容的访问令牌。
- Contentful preview access token - 用于从你的 Contentful 空间获取未发布内容的访问令牌。
要将你的 Contentful 空间凭证添加到 Astro 中,在项目根目录中创建一个名为.env
的文件,并添加以下变量:
CONTENTFUL_SPACE_ID=YOUR_SPACE_ID
CONTENTFUL_DELIVERY_TOKEN=YOUR_DELIVERY_TOKEN
CONTENTFUL_PREVIEW_TOKEN=YOUR_PREVIEW_TOKEN
现在,你可以在项目中使用这些环境变量。
如果你希望为 Contentful 环境变量启用智能感知,你可以在src/
目录中创建一个名为env.d.ts
的文件,并像这样配置ImportMetaEnv
:
interface ImportMetaEnv {
readonly CONTENTFUL_SPACE_ID: string;
readonly CONTENTFUL_DELIVERY_TOKEN: string;
readonly CONTENTFUL_PREVIEW_TOKEN: string;
}
:::tip
了解更多关于使用环境变量和 Astro 中的 .env
文件的信息。
:::
你的根目录现在应该包含这些新文件:
- src/ - **env.d.ts** - **.env** - astro.config.mjs - package.json要连接到你的 Contentful 空间,请使用下面的命令使用你首选的包管理器同时安装以下两个包:
contentful.js
,官方 Contentful JavaScript SDKrich-text-html-renderer
,用于将 Contentful 的富文本字段渲染为 HTML 的包。
接下来,在你的项目的 src/lib/
目录中创建一个名为 contentful.ts
的新文件。
import contentful from "contentful";
export const contentfulClient = contentful.createClient({
space: import.meta.env.CONTENTFUL_SPACE_ID,
accessToken: import.meta.env.DEV
? import.meta.env.CONTENTFUL_PREVIEW_TOKEN
: import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
});
上面的代码片段创建了一个新的 Contentful 客户端,将.env
文件中的凭证传递进去。
:::caution 在开发模式下,你的内容将从 Contentful 预览 API获取。这意味着你将能够从 Contentful Web 应用程序中查看未发布的内容。
在构建时,你的内容将从 Contentful 交付 API 获取。这意味着在构建时只有已发布的内容可用。 :::
最后,你的根目录现在应该包含这些新文件:
- src/ - env.d.ts - lib/ - **contentful.ts** - .env - astro.config.mjs - package.jsonAstro 组件可以通过使用 contentfulClient
并指定 content_type
从你的 Contentful 账号中获取数据。
例如,如果你有一个名为 "blogPost" 的内容类型,其中包含一个用于标题的文本字段和一个用于内容的富文本字段,你的组件可能如下所示:
---
import { contentfulClient } from "../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { EntryFieldTypes } from "contentful";
interface BlogPost {
contentTypeId: "blogPost",
fields: {
title: EntryFieldTypes.Text
content: EntryFieldTypes.RichText,
}
}
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
---
<body>
{entries.items.map((item) => (
<section>
<h2>{item.fields.title}</h2>
<article set:html={documentToHtmlString(item.fields.content)}></article>
</section>
))}
</body>
:::tip 如果你有一个空的 Contentful 空间,请查看 设置 Contentful 模型 以了解如何为你的内容创建基本的博客模型。 :::
你可以在 Contentful 文档 中找到更多的查询选项。
通过上述设置,你现在可以创建一个使用 Contentful 作为 CMS 的博客。
- 一个 Contentful 空间 - 对于本教程,我们建议从一个空的空间开始。如果你已经有一个内容模型,请随意使用它,但你需要修改我们的代码片段以与你的内容模型匹配。
- 集成了 Contentful SDK 的 Astro 项目 - 有关如何在 Astro 项目中设置 Contentful 的详细信息,请参阅 与 Astro 集成。
在你的 Contentful 空间中,在 内容模型 部分,创建一个新的内容模型,并设置以下字段和值:
- Name: 博客文章
- API identifier:
blogPost
- Description: 此内容类型用于博客文章
在你新创建的内容类型中,使用添加字段按钮添加5个新字段,具体参数如下:
- 文本字段
- Name: title
- API identifier:
title
(将其他参数保持默认)
- 日期和时间字段
- Name: date
- API identifier:
date
- 文本字段
- Name: slug
- API identifier:
slug
(将其他参数保持默认)
- 文本字段
- Name: description
- API identifier:
description
- 富文本字段
- Name: content
- API identifier:
content
单击保存以保存你的更改。
在你的 Contentful 空间的内容部分,点击添加条目按钮创建一个新条目。然后,填写字段:
- Title:
Astro 真是太棒了!
- Slug:
astro-is-amazing
- Description:
Astro 是一个全新的静态站点生成器,速度快,易于使用。
- Date:
2022-10-05
- Content:
这是我的第一篇博客文章!
点击Publish以保存你的条目。你刚刚创建了你的第一篇博客文章。
随意添加你想要的博客文章,然后切换到你喜欢的代码编辑器,开始使用 Astro 进行开发!
创建一个名为 BlogPost
的新接口,并将其添加到位于 src/lib/
下的 contentful.ts
文件中。此接口将与你在 Contentful 中的博客文章内容类型的字段相匹配。你将使用它来对博客文章条目的响应进行类型定义。
import contentful, { EntryFieldTypes } from "contentful";
export interface BlogPost {
contentTypeId: "blogPost",
fields: {
title: EntryFieldTypes.Text
content: EntryFieldTypes.RichText,
date: EntryFieldTypes.Date,
description: EntryFieldTypes.Text,
slug: EntryFieldTypes.Text
}
}
export const contentfulClient = contentful.createClient({
space: import.meta.env.CONTENTFUL_SPACE_ID,
accessToken: import.meta.env.DEV
? import.meta.env.CONTENTFUL_PREVIEW_TOKEN
: import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
});
接下来,转到你将从 Contentful 获取数据的 Astro 页面。在本示例中,我们将使用位于 src/pages/
下的主页 index.astro
。
从 src/lib/contentful.ts
中导入 BlogPost
接口和 contentfulClient
。
通过传递 BlogPost
接口来从 Contentful 获取所有带有内容类型为 blogPost
的条目。
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
---
此获取调用将在 entries.items
处返回一个博客文章数组。你可以使用 map()
创建一个新数组 (posts
),以格式化返回的数据。
下面的示例从我们的内容模型中返回 items.fields
属性,以创建博客文章预览,并同时将日期重新格式化为更易读的格式。
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const posts = entries.items.map((item) => {
const { title, date, description, slug } = item.fields;
return {
title,
slug,
description,
date: new Date(date).toLocaleDateString()
};
});
---
最后,你可以在模板中使用 posts
来显示每篇博客文章的预览。
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const posts = entries.items.map((item) => {
const { title, date, description, slug } = item.fields;
return {
title,
slug,
description,
date: new Date(date).toLocaleDateString()
};
});
---
<html lang="en">
<head>
<title>My Blog</title>
</head>
<body>
<h1>My Blog</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/posts/${post.slug}/`}>
<h2>{post.title}</h2>
</a>
<time>{post.date}</time>
<p>{post.description}</p>
</li>
))}
</ul>
</body>
</html>
使用与上述相同的方法从 Contentful 获取数据,但这次在将为每篇博客文章创建一个唯一的页面路由。
如果你使用的是 Astro 的默认静态模式,你将使用 动态路由 和 getStaticPaths()
函数。此函数将在构建时调用,以生成成为页面的路径列表。
在 src/pages/posts/
中创建一个名为 [slug].astro
的新文件。
与 index.astro
上所做的一样,从 src/lib/contentful.ts
导入 BlogPost
接口和 contentfulClient
。
这次,在 getStaticPaths()
函数中获取数据。
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
}
---
然后,将每个条目映射到一个带有 params
和 props
属性的对象。params
属性将用于生成页面的 URL,props
属性将作为属性传递给页面组件。
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const pages = entries.items.map((item) => ({
params: { slug: item.fields.slug },
props: {
title: item.fields.title,
content: documentToHtmlString(item.fields.content),
date: new Date(item.fields.date).toLocaleDateString(),
},
}));
return pages;
}
---
params
内的属性必须与动态路由的名称匹配。由于我们的文件名是 [slug].astro
,因此我们使用了 slug
。
在我们的示例中,props
对象将三个属性传递给页面:
- title(字符串)
- content(将文档转换为 HTML 的富文本文档)
- date(使用
Date
构造函数进行格式化)
最后,你可以使用页面 props
来显示博客文章。
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const pages = entries.items.map((item) => ({
params: { slug: item.fields.slug },
props: {
title: item.fields.title,
content: documentToHtmlString(item.fields.content),
date: new Date(item.fields.date).toLocaleDateString(),
},
}));
return pages;
}
const { content, title, date } = Astro.props;
---
<html lang="en">
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<time>{date}</time>
<article set:html={content} />
</body>
</html>
在浏览器中导航到 http://localhost:4321/, 然后点击其中一篇文章,以确保你的动态路由正常工作!
如果你已经 选择使用 SSR 模式,你将使用一个使用 slug
参数从 Contentful 获取数据的动态路由。
在 src/pages/posts
中创建一个 [slug].astro
页面。使用 Astro.params
来从 URL 中获取 slug,然后将其传递给 getEntries
:
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
const { slug } = Astro.params;
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
---
如果找不到条目,你可以使用 Astro.redirect
将用户重定向到 404 页面。
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
const { slug } = Astro.params;
try {
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
} catch (error) {
return Astro.redirect("/404");
}
---
要将文章数据传递到模板部分,你可以在 try/catch
块外创建一个 post
对象。
使用 documentToHtmlString
将 content
从文档转换为 HTML,并使用 Date 构造函数格式化日期。title
可以保持原样。然后,将这些属性添加到你的 post
对象中。
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
let post;
const { slug } = Astro.params;
try {
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
const { title, date, content } = data.items[0].fields;
post = {
title,
date: new Date(date).toLocaleDateString(),
content: documentToHtmlString(content),
};
} catch (error) {
return Astro.redirect("/404");
}
---
最后,你可以在模板部分引用 post
来显示博客文章。
---
import Layout from "../../layouts/Layout.astro";
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";
let post;
const { slug } = Astro.params;
try {
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
const { title, date, content } = data.items[0].fields;
post = {
title,
date: new Date(date).toLocaleDateString(),
content: documentToHtmlString(content),
};
} catch (error) {
return Astro.redirect("/404");
}
---
<html lang="en">
<head>
<title>{post?.title}</title>
</head>
<body>
<h1>{post?.title}</h1>
<time>{post?.date}</time>
<article set:html={post?.content} />
</body>
</html>
要部署你的网站,请访问我们的部署指南,并按照你首选的托管提供商的说明操作。
如果你的项目使用的是 Astro 的默认静态模式,你需要设置一个 Webhook,在内容更改时触发新的构建。如果你的托管提供商是 Netlify 或 Vercel,你可以使用其 Webhook 功能从 Contentful 事件中触发新的构建。
要在 Netlify 中设置 Webhook:
1. 转到你的站点仪表板,点击 **Build & deploy**。-
在 Continuous Deployment 选项卡下,找到 Build hooks 部分,然后点击 Add build hook。
-
为你的 Webhook 提供一个名称,选择要在其上触发构建的分支。点击 Save,然后复制生成的 URL。
要在 Vercel 中设置 Webhook:
1. 转到你的项目仪表板,点击 **Settings**。-
在 Git 选项卡下,找到 Deploy Hooks 部分。
-
为你的 Webhook 提供一个名称,选择要在其上触发构建的分支。点击 Add,然后复制生成的 URL。
在你的 Contentful 空间的设置中,点击 Webhooks 选项卡,然后通过点击 Add Webhook 按钮创建一个新的 Webhook。为你的 Webhook 提供一个名称,并粘贴你在上一节中复制的 Webhook URL。最后,点击 Save 创建 Webhook。
现在,每当你在 Contentful 中发布新的博客文章时,都会触发新的构建,并更新你的博客。