Skip to content

内容丰富且 Astro

内容丰富 是一个无头 CMS,允许你管理内容、与其他服务集成以及发布到多个平台。

¥Contentful is a headless CMS that allows you to manage content, integrate with other services, and publish to multiple platforms.

¥Integrating with Astro

在本节中,我们将使用 内容丰富的 SDK 通过零客户端 JavaScript 将你的 Contentful 空间连接到 Astro。

¥In this section, we’ll use the Contentful SDK to connect your Contentful space to Astro with zero client-side JavaScript.

¥Prerequisites

首先,你需要具备以下条件:

¥To get started, you will need to have the following:

  1. Astro 项目 - 如果你还没有 Astro 项目,我们的 安装指南 将立即帮助你启动并运行。

  2. Contentful 账户和一个 Contentful 空间。如果你没有账户,你可以 报名 获取免费账户并创建一个新的 Contentful 空间。如果你有现有空间,也可以使用。

  3. 内容丰富的凭证 - 你可以在内容丰富的仪表板设置 > API 密钥中找到以下凭据。如果你没有任何 API 密钥,请选择添加 API 密钥创建一个。

    • 内容空间 ID - 你的 Contentful 空间的 ID。

    • 内容交付访问令牌 - 用于从 Contentful 空间使用已发布内容的访问令牌。

    • 内容预览访问令牌 - 用于从 Contentful 空间使用未发布内容的访问令牌。

¥Setting up credentials

要将 Contentful 空间的凭据添加到 Astro,请使用以下变量在项目的根目录中创建 .env 文件:

¥To add your Contentful space’s credentials to Astro, create an .env file in the root of your project with the following variables:

.env
CONTENTFUL_SPACE_ID=YOUR_SPACE_ID
CONTENTFUL_DELIVERY_TOKEN=YOUR_DELIVERY_TOKEN
CONTENTFUL_PREVIEW_TOKEN=YOUR_PREVIEW_TOKEN

现在,你可以在项目中使用这些环境变量。

¥Now, you can use these environment variables in your project.

如果你希望为你的 Contentful 环境变量提供 IntelliSense,你可以在 src/ 目录中创建一个 env.d.ts 文件并配置 ImportMetaEnv,如下所示:

¥If you would like to have IntelliSense for your Contentful environment variables, you can create a env.d.ts file in the src/ directory and configure ImportMetaEnv like this:

src/env.d.ts
interface ImportMetaEnv {
readonly CONTENTFUL_SPACE_ID: string;
readonly CONTENTFUL_DELIVERY_TOKEN: string;
readonly CONTENTFUL_PREVIEW_TOKEN: string;
}

你的根目录现在应该包含这些新文件:

¥Your root directory should now include these new files:

  • Directorysrc/
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

¥Installing dependencies

要连接你的 Contentful 空间,请使用以下单个命令为你的首选包管理器安装以下两个内容:

¥To connect with your Contentful space, install both of the following using the single command below for your preferred package manager:

Terminal window
npm install contentful @contentful/rich-text-html-renderer

接下来,在项目的 src/lib/ 目录中创建一个名为 contentful.ts 的新文件。

¥Next, create a new file called contentful.ts in the src/lib/ directory of your project.

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 文件中的凭据。

¥The above code snippet creates a new Contentful client, passing in credentials from the .env file.

最后,你的根目录现在应该包含这些新文件:

¥Finally, your root directory should now include these new files:

  • Directorysrc/
    • env.d.ts
    • Directorylib/
      • contentful.ts
  • .env
  • astro.config.mjs
  • package.json

¥Fetching data

Astro 组件可以通过使用 contentfulClient 并指定 content_type 从你的 Contentful 账户获取数据。

¥Astro components can fetch data from your Contentful account by using the contentfulClient and specifying the content_type.

例如,如果你的 “blogPost” 内容类型具有标题文本字段和内容富文本字段,则你的组件可能如下所示:

¥For example, if you have a “blogPost” content type that has a text field for a title and a rich text field for content, your component might look like this:

---
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>

你可以在 内容丰富的文档.txt 中找到更多查询选项。

¥You can find more querying options in the Contentful documentation.

使用 Astro 和 Contentful 制作博客

Section titled 使用 Astro 和 Contentful 制作博客

¥Making a blog with Astro and Contentful

通过上述设置,你现在可以创建一个使用 Contentful 作为 CMS 的博客。

¥With the setup above, you are now able to create a blog that uses Contentful as the CMS.

¥Prerequisites

  1. 内容丰富的空间 - 对于本教程,我们建议从空白处开始。如果你已经有了内容模型,请随意使用它,但你需要修改我们的代码片段以匹配你的内容模型。
  2. 内容丰富的 SDK 集成的 Astro 项目 - 请参阅 与 Astro 集成 以了解如何使用 Contentful 设置 Astro 项目的更多详细信息。

¥Setting up a Contentful model

在你的 Contentful 空间内,在内容模型部分,创建一个具有以下字段和值的新内容模型:

¥Inside your Contentful space, in the Content model section, create a new content model with the following fields and values:

  • 名称:博客文章

  • API 标识符:blogPost

  • 描述:此内容类型适用于博客文章

在你新创建的内容类型中,使用添加字段按钮添加 5 个具有以下参数的新字段:

¥In your newly created content type, use the Add Field button to add 5 new fields with the following parameters:

  1. 文本域

    • 名称:title

    • API 标识符:title(其他参数保留默认值)

  2. 日期和时间字段

    • 名称:date

    • API 标识符:date

  3. 文本域

    • 名称:slug

    • API 标识符:slug(其他参数保留默认值)

  4. 文本域

    • 名称:description

    • API 标识符:description

  5. 富文本字段

    • 名称:content

    • API 标识符:content

单击“保存”以保存你的更改。

¥Click Save to save your changes.

在 Contentful 空间的内容部分,单击添加条目按钮创建一个新条目。然后,填写字段:

¥In the Content section of your Contentful space, create a new entry by clicking the Add Entry button. Then, fill in the fields:

  • 标题:Astro is amazing!

  • 蛞蝓:astro-is-amazing

  • 描述:Astro is a new static site generator that is blazing fast and easy to use.

  • 日期:2022-10-05

  • 内容:This is my first blog post!

单击“发布”以保存你的条目。你刚刚创建了第一篇博客文章。

¥Click Publish to save your entry. You have just created your first blog post.

你可以随意添加任意数量的博客文章,然后切换到你最喜欢的代码编辑器,开始使用 Astro 进行黑客攻击!

¥Feel free to add as many blog posts as you want, then switch to your favorite code editor to start hacking with Astro!

¥Displaying a list of blog posts

创建一个名为 BlogPost 的新接口并将其添加到 src/lib/ 中的 contentful.ts 文件中。此界面将匹配 Contentful 中你的博客文章内容类型的字段。你将使用它来输入你的博客文章条目响应。

¥Create a new interface called BlogPost and add it to your contentful.ts file in src/lib/. This interface will match the fields of your blog post content type in Contentful. You will use it to type your blog post entries response.

src/lib/contentful.ts
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",
});

接下来,转到 Astro 页面,你将从 Contentful 获取数据。在此示例中,我们将在 src/pages/ 中使用主页 index.astro

¥Next, go to the Astro page where you will fetch data from Contentful. We will use the home page index.astro in src/pages/ in this example.

src/lib/contentful.ts 导入 BlogPost 接口和 contentfulClient

¥Import BlogPost interface and contentfulClient from src/lib/contentful.ts.

从 Contentful 中获取内容类型为 blogPost 的所有条目,同时传递 BlogPost 接口以键入你的响应。

¥Fetch all the entries from Contentful with a content type of blogPost while passing the BlogPost interface to type your response.

src/pages/index.astro
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
---

此 fetch 调用将返回你在 entries.items 的博客文章的数组。你可以使用 map() 创建一个新数组 (posts),用于格式化返回的数据。

¥This fetch call will return an array of your blog posts at entries.items. You can use map() to create a new array (posts) that formats your returned data.

下面的示例从我们的内容模型返回 items.fields 属性以创建博客文章预览,同时将日期重新格式化为更易读的格式。

¥The example below returns the items.fields properties from our Content model to create a blog post preview, and at the same time, reformats the date to a more readable format.

src/pages/index.astro
---
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 来显示每篇博客文章的预览。

¥Finally, you can use posts in your template to show a preview of each blog post.

src/pages/index.astro
---
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>

¥Generating individual blog posts

使用与上面相同的方法从 Contentful 获取数据,但这一次,在将为每个博客文章创建唯一页面路由的页面上。

¥Use the same method to fetch your data from Contentful as above, but this time, on a page that will create a unique page route for each blog post.

¥Static site generation

如果你使用 Astro 的默认静态模式,你将使用 动态路由getStaticPaths() 函数。该函数将在构建时被调用以生成成为页面的路径列表。

¥If you’re using Astro’s default static mode, you’ll use dynamic routes and the getStaticPaths() function. This function will be called at build time to generate the list of paths that become pages.

src/pages/posts/ 中创建一个名为 [slug].astro 的新文件。

¥Create a new file named [slug].astro in src/pages/posts/.

正如你在 index.astro 上所做的那样,从 src/lib/contentful.ts 导入 BlogPost 接口和 contentfulClient

¥As you did on index.astro, import the BlogPost interface and contentfulClient from src/lib/contentful.ts.

这次,在 getStaticPaths() 函数中获取数据。

¥This time, fetch your data inside a getStaticPaths() function.

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
}
---

然后,将每个项目映射到具有 paramsprops 属性的对象。params 属性将用于生成页面的 URL,props 属性将作为 props 传递给页面组件。

¥Then, map each item to an object with a params and props property. The params property will be used to generate the URL of the page and the props property will be passed to the page component as props.

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
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

¥The property inside params must match the name of the dynamic route. Since our filename is [slug].astro, we use slug.

在我们的示例中,props 对象将三个属性传递给页面:

¥In our example, the props object passes three properties to the page:

  • 标题(字符串)

  • 内容(使用 documentToHtmlString 转换为 HTML 的富文本文档)

  • 日期(使用 Date 构造函数格式化)

最后,你可以使用页面 props 来显示你的博客文章。

¥Finally, you can use the page props to display your blog post.

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const { items } = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const pages = 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/ 并单击你的其中一篇文章,以确保你的动态路由正常运行!

¥Navigate to http://localhost:4321/ and click on one of your posts to make sure your dynamic route is working!

¥Server side rendering

如果你有 选择 SSR 模式,你将使用动态路由,该路由使用 slug 参数从 Contentful 获取数据。

¥If you’ve opted in to SSR mode, you will use a dynamic route that uses a slug parameter to fetch the data from Contentful.

src/pages/posts 中创建 [slug].astro 页面。使用 Astro.params 从 URL 获取 slug,然后将其传递给 getEntries

¥Create a [slug].astro page in src/pages/posts. Use Astro.params to get the slug from the URL, then pass that to getEntries:

src/pages/posts/[slug].astro
---
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 页面。

¥If the entry is not found, you can redirect the user to the 404 page using Astro.redirect.

src/pages/posts/[slug].astro
---
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 对象。

¥To pass post data to the template section, create a post object outside the try/catch block.

使用 documentToHtmlStringcontent 从 Document 转换为 HTML,并使用 Date 构造函数设置日期格式。title 可以保持原样。然后,将这些属性添加到你的 post 对象中。

¥Use documentToHtmlString to convert content from a Document to HTML, and use the Date constructor to format the date. title can be left as-is. Then, add these properties to your post object.

src/pages/posts/[slug].astro
---
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");
}
---

最后,你可以引用 post 在模板部分显示你的博客文章。

¥Finally, you can reference post to display your blog post in the template section.

src/pages/posts/[slug].astro
---
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>

¥Publishing your site

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

¥To deploy your website, visit our deployment guides and follow the instructions for your preferred hosting provider.

¥Rebuild on Contentful changes

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

¥If your project is using Astro’s default static mode, you will need to set up a webhook to trigger a new build when your content changes. If you are using Netlify or Vercel as your hosting provider, you can use its webhook feature to trigger a new build from Contentful events.

要在 Netlify 中设置 Webhook:

¥To set up a webhook in Netlify:

  1. Go to your site dashboard and click on Build & deploy.

  2. Under the Continuous Deployment tab, find the Build hooks section and click on Add build hook.

  3. Provide a name for your webhook and select the branch you want to trigger the build on. Click on Save and copy the generated URL.

要在 Vercel 中设置 Webhook:

¥To set up a webhook in Vercel:

  1. Go to your project dashboard and click on Settings.

  2. Under the Git tab, find the Deploy Hooks section.

  3. Provide a name for your webhook and the branch you want to trigger the build on. Click Add and copy the generated URL.

将 webhook 添加到 Contentful
Section titled 将 webhook 添加到 Contentful

¥Adding a webhook to Contentful

在你的 Contentful 空间设置中,单击“Webhooks”选项卡,然后单击“添加 Webhook”按钮创建一个新的 webhook。为你的 Webhook 提供名称并粘贴你在上一部分中复制的 Webhook URL。最后,点击保存以创建 webhook。

¥In your Contentful space settings, click on the Webhooks tab and create a new webhook by clicking the Add Webhook button. Provide a name for your webhook and paste the webhook URL you copied in the previous section. Finally, hit Save to create the webhook.

现在,每当你在 Contentful 中发布新博客文章时,都会触发新的构建并更新你的博客。

¥Now, whenever you publish a new blog post in Contentful, a new build will be triggered and your blog will be updated.

More CMS guides

Astro 中文网 - 粤ICP备13048890号