内容丰富且 Astro
Contentful 是一个无头 CMS,允许你管理内容、与其他服务集成以及发布到多个平台。
¥Contentful is a headless CMS that allows you to manage content, integrate with other services, and publish to multiple platforms.
与 Astro 集成
Section titled “与 Astro 集成”¥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:
-
Astro 项目 - 如果你还没有 Astro 项目,我们的 安装指南 将立即帮助你启动并运行。
-
Contentful 账户和一个 Contentful 空间。如果你没有账户,你可以 报名 获取免费账户并创建一个新的 Contentful 空间。如果你有现有空间,也可以使用。
-
内容丰富的凭证 - 你可以在 Contentful 仪表板设置 > 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:
CONTENTFUL_SPACE_ID=YOUR_SPACE_IDCONTENTFUL_DELIVERY_TOKEN=YOUR_DELIVERY_TOKENCONTENTFUL_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:
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:
-
contentful.js,官方 JavaScript Contentful SDK -
rich-text-html-renderer,一个将 Contentful 的富文本字段渲染为 HTML 的包。
npm install contentful @contentful/rich-text-html-rendererpnpm add contentful @contentful/rich-text-html-rendereryarn add 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.
import * as 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
- 内容丰富的空间 - 对于本教程,我们建议从空白处开始。如果你已经有了内容模型,请随意使用它,但你需要修改我们的代码片段以匹配你的内容模型。
- 与 内容丰富的 SDK 集成的 Astro 项目 - 请参阅 与 Astro 集成 以了解如何使用 Contentful 设置 Astro 项目的更多详细信息。
建立内容模型
Section titled “建立内容模型”¥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:
-
文本域
-
名称:title
-
API 标识符:
title(其他参数保留默认值)
-
-
日期和时间字段
-
名称:date
-
API 标识符:
date
-
-
文本域
-
名称:slug
-
API 标识符:
slug(其他参数保留默认值)
-
-
文本域
-
名称:description
-
API 标识符:
description
-
-
富文本字段
-
名称: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!
显示博客文章列表
Section titled “显示博客文章列表”¥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.
import * as contentful from "contentful";import type { 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.
---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.
---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.
---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>生成个人博客文章
Section titled “生成个人博客文章”¥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.
静态站点生成
Section titled “静态站点生成”¥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.
---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 属性将作为 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.
---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.
---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!
¥On-demand rendering
如果你有 选择使用适配器进行按需渲染,你将使用动态路由,该路由使用 slug 参数从 Contentful 获取数据。
¥If you’ve opted into on-demand rendering with an adapter, 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:
---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.
---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.
使用 documentToHtmlString 将 content 从 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.
---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.
---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>发布你的网站
Section titled “发布你的网站”¥Publishing your site
要部署你的网站,请访问我们的 部署指南 并按照你首选托管提供商的说明进行操作。
¥To deploy your website, visit our deployment guides and follow the instructions for your preferred hosting provider.
重建内容丰富的变化
Section titled “重建内容丰富的变化”¥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
Section titled “Netlify”要在 Netlify 中设置 Webhook:
¥To set up a webhook in Netlify:
-
Go to your site dashboard and click on Build & deploy.
-
Under the Continuous Deployment tab, find the Build hooks section and click on Add build hook.
-
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
Section titled “Vercel”要在 Vercel 中设置 Webhook:
¥To set up a webhook in Vercel:
-
Go to your project dashboard and click on Settings.
-
Under the Git tab, find the Deploy Hooks section.
-
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.