CMS Guides: recipes for how to use different content management systems in an Astro project
# Use a CMS with Astro
> How to use a CMS to add content to Astro
**Ready to connect a Headless CMS to your Astro project?** Follow one of our guides to integrate a CMS. ## CMS Guides [Section titled “CMS Guides”](#cms-guides) Note that many of these pages are **stubs**: they’re collections of resources waiting for your contribution! *  ### [Apostrophe](/en/guides/cms/apostrophecms/) *  ### [Builder.io](/en/guides/cms/builderio/) *  ### [ButterCMS](/en/guides/cms/buttercms/) *  ### [Caisy](/en/guides/cms/caisy/) *  ### [CloudCannon](/en/guides/cms/cloudcannon/) *  ### [Contentful](/en/guides/cms/contentful/) *  ### [Cosmic](/en/guides/cms/cosmic/) *  ### [Craft CMS](/en/guides/cms/craft-cms/) *  ### [Crystallize](/en/guides/cms/crystallize/) *  ### [DatoCMS](/en/guides/cms/datocms/) *  ### [Decap CMS](/en/guides/cms/decap-cms/) *  ### [Directus](/en/guides/cms/directus/) *  ### [Drupal](/en/guides/cms/drupal/) *  ### [Flotiq](/en/guides/cms/flotiq/) *  ### [Front Matter CMS](/en/guides/cms/frontmatter-cms/) *  ### [Ghost](/en/guides/cms/ghost/) *  ### [GitCMS](/en/guides/cms/gitcms/) *  ### [Hashnode](/en/guides/cms/hashnode/) *  ### [Hygraph](/en/guides/cms/hygraph/) *  ### [Keystatic](/en/guides/cms/keystatic/) *  ### [KeystoneJS](/en/guides/cms/keystonejs/) *  ### [Kontent.ai](/en/guides/cms/kontent-ai/) *  ### [microCMS](/en/guides/cms/microcms/) *  ### [Optimizely CMS](/en/guides/cms/optimizely/) *  ### [Payload CMS](/en/guides/cms/payload/) *  ### [Prepr CMS](/en/guides/cms/preprcms/) *  ### [Prismic](/en/guides/cms/prismic/) *  ### [Sanity](/en/guides/cms/sanity/) *  ### [Sitecore XM](/en/guides/cms/sitecore/) *  ### [Sitepins](/en/guides/cms/sitepins/) *  ### [Spinal](/en/guides/cms/spinal/) *  ### [Statamic](/en/guides/cms/statamic/) *  ### [Storyblok](/en/guides/cms/storyblok/) *  ### [Strapi](/en/guides/cms/strapi/) *  ### [StudioCMS](/en/guides/cms/studiocms/) *  ### [Tina CMS](/en/guides/cms/tina-cms/) *  ### [Umbraco](/en/guides/cms/umbraco/) *  ### [WordPress](/en/guides/cms/wordpress/) ## Why use a CMS? [Section titled “Why use a CMS?”](#why-use-a-cms) A Content Management System lets you write content and manage assets outside of your Astro project. This unlocks new features for working with content. Most CMSes give you a visual content editor, the ability to specify standard types of content, and a way to collaborate with others. A CMS can be useful for content that follows a particular structure, often giving you a dashboard-like experience and WYSIWYG editing tools. You might use a CMS to write blog posts using a CMS’s rich text editor instead of Markdown files. Or you might use a CMS to maintain product listings for an eCommerce shop, making certain fields required to avoid incomplete listings. Your Astro project can then fetch your content from your CMS and display it, wherever and however you want on your site. ## Which CMSes work well with Astro? [Section titled “Which CMSes work well with Astro?”](#which-cmses-work-well-with-astro) Because Astro takes care of the *presentation* of your content, you’ll want to choose a *headless* CMS, like those in the list above. This means that the CMS helps you write your content, but doesn’t generate a site that displays it. Instead, you fetch the content data and use in your Astro project. Some headless CMSes, like Storyblok, provide an Astro [integration](/en/guides/integrations-guide/) that helps fetch the content specifically for an Astro site. Others provide a JavaScript SDK, a library that you install and use to fetch your remote content. Explore a [list of over 100 headless content management systems](https://jamstack.org/headless-cms/) External where you can filter by type (e.g. Git-based, API driven) and license (open-source or closed-source). ## Can I use Astro without a CMS? [Section titled “Can I use Astro without a CMS?”](#can-i-use-astro-without-a-cms) Yes! Astro provides built-in support for [Markdown](/en/guides/markdown-content/).
# ApostropheCMS & Astro
> Edit content on the page in your Astro project using Apostrophe as your CMS.
[ApostropheCMS](https://apostrophecms.com/) is a content management system supporting on-page editing in Astro. ## Integrating with Astro [Section titled “Integrating with Astro”](#integrating-with-astro) In this section, you will use the [Apostrophe integration](https://apostrophecms.com/extensions/astro-integration) to connect ApostropheCMS to Astro. ### Prerequisites [Section titled “Prerequisites”](#prerequisites) To get started, you will need to have the following: 1. **An on-demand rendered Astro project** with the [Node.js adapter](/en/guides/integrations-guide/node/) installed and `output: 'server'` configured - If you don’t have an Astro project yet, our [installation guide](/en/install-and-setup/) will get you up and running in no time. 2. **An ApostropheCMS project with a configured environment variable called `APOS_EXTERNAL_FRONT_KEY`** - This environment variable can be set to any random string. If you don’t have an ApostropheCMS project yet, the [installation guide](https://docs.apostrophecms.org/guide/development-setup.html) will get one setup quickly. We highly recommend using the [Apostrophe CLI tool](https://apostrophecms.com/extensions/apos-cli) to facilitate this. ### Setting up project communication [Section titled “Setting up project communication”](#setting-up-project-communication) Your Astro project needs to have an `APOS_EXTERNAL_FRONT_KEY` environment variable set to the same value as the one in your ApostropheCMS project to allow communication between the two. This shared key acts as a means to verify requests between the frontend (Astro) and the backend (ApostropheCMS). Create a `.env` file in the root of your Astro project with the following variable: .env ```ini APOS_EXTERNAL_FRONT_KEY='RandomStrongString' ``` Your root directory should now include this new file: * **.env** * astro.config.mjs * package.json ### Installing dependencies [Section titled “Installing dependencies”](#installing-dependencies) To connect Astro with your ApostropheCMS project, install the official Apostrophe integration in your Astro project using the command below for your preferred package manager. * npm ```shell npm install @apostrophecms/apostrophe-astro vite @astro/node ``` * pnpm ```shell pnpm add @apostrophecms/apostrophe-astro vite @astro/node ``` * Yarn ```shell yarn add @apostrophecms/apostrophe-astro vite @astro/node ``` ### Configuring Astro [Section titled “Configuring Astro”](#configuring-astro) Configure both the `apostrophe-astro` integration and `vite` in your `astro.config.mjs` file. The following example provides the base URL of your Apostrophe instance and paths to folders in your project to map between the ApostropheCMS [widgets](https://docs.apostrophecms.org/guide/core-widgets.html) and [page template](https://docs.apostrophecms.org/guide/pages.html) types and your Astro project. It also configures Vite’s server-side rendering. astro.config.mjs ```js import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import apostrophe from '@apostrophecms/apostrophe-astro'; import { loadEnv } from 'vite'; const env = loadEnv("", process.cwd(), 'APOS'); export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), integrations: [ apostrophe({ aposHost: 'http://localhost:3000', widgetsMapping: './src/widgets', templatesMapping: './src/templates' }) ], vite: { ssr: { // Do not externalize the @apostrophecms/apostrophe-astro plugin, we need // to be able to use virtual: URLs there noExternal: [ '@apostrophecms/apostrophe-astro' ], }, define: { 'process.env.APOS_EXTERNAL_FRONT_KEY': JSON.stringify(env.APOS_EXTERNAL_FRONT_KEY), 'process.env.APOS_HOST': JSON.stringify(env.APOS_HOST) } } }); ``` For complete configuration options and explanations, see the [`apostrophe-astro` documentation](https://apostrophecms.com/extensions/astro-integration#configuration-astro). ### Connecting ApostropheCMS widgets to Astro components [Section titled “Connecting ApostropheCMS widgets to Astro components”](#connecting-apostrophecms-widgets-to-astro-components) ApostropheCMS widgets are blocks of structured content that can be added to the page such as layout columns, images, and text blocks. You will need to create an Astro component for each widget in your Apostrophe project, plus a file to map those components to the corresponding Apostrophe widget. Create a new folder at `src/widgets/` for your Astro components and the mapping file, `index.js`. Mapped components located in this folder receive a `widget` property containing your widget’s schema fields, and any custom props, through `Astro.props`. These values are then available for on-page editing. The following example shows a `RichTextWidget.astro` component accessing the content from its corresponding ApostropheCMS widget to allow for in-context editing: src/widgets/RichTextWidget.astro ```js --- const { widget } = Astro.props; const { content } = widget; --- ``` Some standard Apostrophe widgets, such as images and videos, require **placeholders** because they do not contain editable content by default. The following example creates a standard `ImageWidget.astro` component that sets the `src` value conditionally to either the value of the `aposPlaceholder` image passed by the widget, a fallback image from the Astro project, or the image selected by the content manager using the Apostrophe `attachment` helper: src/widgets/ImageWidget.astro ```js --- const { widget } = Astro.props; const placeholder = widget?.aposPlaceholder; const src = placeholder ? '/images/image-widget-placeholder.jpg' : widget?._image[0]?.attachment?._urls['full']; --- ``` For more examples, see [the `astro-frontend` starter project widget examples](https://github.com/apostrophecms/astro-frontend/tree/main/src/widgets). Each `.astro` component must be mapped to the corresponding core Apostrophe widget in `src/widgets/index.js`. The example below adds the previous two components to this file: src/widgets/index.js ```js import RichTextWidget from './RichTextWidget.astro'; import ImageWidget from './ImageWidget.astro'; const widgetComponents = { '@apostrophecms/rich-text': RichTextWidget, '@apostrophecms/image': ImageWidget }; export default widgetComponents; ``` See [the ApostropheCMS documentation](https://apostrophecms.com/extensions/astro-integration) for naming conventions for standard, pro, and custom-project-level widgets The project directory should now look like this: * .env * astro.config.mjs * package.json ### Adding page types [Section titled “Adding page types”](#adding-page-types) Much like widgets, any page type template in your ApostropheCMS project needs to have a corresponding template component in your Astro project. You will also need a file that maps the Apostrophe page types to individual components. Create a new folder at `src/templates/` for your Astro components and the mapping file, `index.js`. Mapped components located in this folder receive a `page` property containing the schema fields of your page, and any custom props, through `Astro.props`. These components can also access an `AposArea` component to render Apostrophe areas. The following example shows a `HomePage.astro` component rendering a page template from its corresponding `home-page` ApostropheCMS page type, including an area schema field named `main`: src/templates/HomePage.astro ```js --- import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro'; const { page, user, query } = Astro.props.aposData; const { main } = page; ---
{ page.title }
``` Each `.astro` component must be mapped to the corresponding core Apostrophe page type in `src/templates/index.js`. The example below adds the previous `HomePage.astro` component to this file: src/templates/index.js ```js import HomePage from './HomePage.astro'; const templateComponents = { '@apostrophecms/home-page': HomePage }; export default templateComponents; ``` See [the ApostropheCMS documentation](https://apostrophecms.com/extensions/astro-integration/#how-apostrophe-template-names-work) for template naming conventions, including special pages and piece page types. The project directory should now look like this: * .env * astro.config.mjs * package.json ### Creating the \[…slug.astro] component and fetching Apostrophe data [Section titled “Creating the \[…slug.astro\] component and fetching Apostrophe data”](#creating-the-slugastro-component-and-fetching-apostrophe-data) Since Apostrophe is responsible for connecting URLs to content, including creating new content and pages on the fly, you will only need one top-level Astro page component: the `[...slug].astro` route. The following example shows a minimal `[...slug].astro` component: src/pages/\[...slug].astro ```js --- import aposPageFetch from '@apostrophecms/apostrophe-astro/lib/aposPageFetch.js'; import AposLayout from '@apostrophecms/apostrophe-astro/components/layouts/AposLayout.astro'; import AposTemplate from '@apostrophecms/apostrophe-astro/components/AposTemplate.astro'; const aposData = await aposPageFetch(Astro.request); const bodyClass = `myclass`; if (aposData.redirect) { return Astro.redirect(aposData.url, aposData.status); } if (aposData.notFound) { Astro.response.status = 404; } --- ``` See [the ApostropheCMS documentation](https://apostrophecms.com/extensions/astro-integration#creating-the-slugastro-component-and-fetching-apostrophe-data) for additional templating options, including slots available in the `AposTemplate` component. ## Making a blog with Astro and ApostropheCMS [Section titled “Making a blog with Astro and ApostropheCMS”](#making-a-blog-with-astro-and-apostrophecms) With the integration set up, you can now create a blog with Astro and ApostropheCMS. Your blog will use an Apostrophe piece, a stand-alone content type that can be included on any page, and a piece page type, a specialized page type that is used for displaying those pieces either individually or collectively. ### Prerequisites [Section titled “Prerequisites”](#prerequisites-1) 1. **An ApostropheCMS project with the Apostrophe CLI tool installed** - You can create a new project or use an existing one. However, this tutorial will only show how to create a blog piece and piece page type. You will have to integrate any other existing project code independently. If you don’t have the CLI tool installed, consult the [Apostrophe CLI installation instructions](https://docs.apostrophecms.org/guide/setting-up.html#the-apostrophe-cli-tool). 2. **An Astro project integrated with ApostropheCMS** - To create a project from scratch, see [integrating with Astro](#integrating-with-astro) for instructions on how to set up the integration, or use the [astro-frontend](https://github.com/apostrophecms/astro-frontend) starter project. ### Creating a blog piece and piece page type [Section titled “Creating a blog piece and piece page type”](#creating-a-blog-piece-and-piece-page-type) To create your blog piece and piece page type for their display, navigate to the root of your ApostropheCMS project in your terminal. Use the ApostropheCMS CLI tool to create the new piece and piece page type with the following command: ```sh apos add piece blog --page ``` This will create two new modules in your project, one for the blog piece type and one for the corresponding piece page type. Next, open the `app.js` file at the root of your ApostropheCMS project in your code editor and add your new modules. app.js ```js require('apostrophe')({ // other configuration options modules: { // other project modules blog: {}, 'blog-page': {} } }); ``` The `blog-page` module also needs to be added to the `@apostrophecms/page` module `types` option array so that it can be selected in the page creation modal. In your ApostropheCMS project, open the `modules/@apostrophecms/page/index.js` file and add the `blog-page`. modules/@apostrophecms/page/index.js ```js module.exports = { options: { types: [ { name: '@apostrophecms/home-page', label: 'Home' }, // Any other project pages { name: 'blog-page', label: 'Blog' } ] } }; ``` ### Creating the blog schema [Section titled “Creating the blog schema”](#creating-the-blog-schema) In an ApostropheCMS project, editors are offered a set of input fields for adding content. Here is an example of a simple blog post that adds three input fields, an `authorName`, `publicationDate` and `content` area where content managers can add multiple widget instances. In this case, we are specifying they can add the image and rich-text widgets we created during the [integration setup](#connecting-apostrophecms-widgets-to-astro-components). modules/blog/index.js ```js module.exports = { extend: '@apostrophecms/piece-type', options: { label: 'Blog', // Additionally add a `pluralLabel` option if needed. }, fields: { add: { authorName: { type: 'string', label: 'Author' }, publicationDate: { type: 'date', label: 'Publication date' }, content: { type: 'area', label: 'Content', options: { widgets: { '@apostrophecms/rich-text': {}, '@apostrophecms/image': {} } } } }, group: { basics: { label: 'Basic', fields: [ 'authorName', 'publicationDate', 'content' ] } } } }; ``` At this point, all the components coming from the ApostropheCMS project are set up. Start the local site from the command line using `npm run dev`, making sure to pass in the `APOS_EXTERNAL_FRONT_KEY` environment variable set to your selected string: ```bash APOS_EXTERNAL_FRONT_KEY='MyRandomString' npm run dev ``` ### Displaying the blog posts [Section titled “Displaying the blog posts”](#displaying-the-blog-posts) To display a page with all the blog posts create a `BlogIndex.astro` component file in the `src/templates` directory of your Astro project and add the following code: After fetching both the page and pieces data from the `aposData` prop, this component creates markup using both fields from the blog piece schema we created, but also from the `piece.title` and `piece._url` that is added to each piece by Apostrophe. src/templates/BlogIndex.astro ```js --- import dayjs from 'dayjs'; const { page, pieces } = Astro.props.aposData; ---
{ page.title }
Blog Posts
{pieces.map(piece => (
Released On { dayjs(piece.publicationDate).format('MMMM D, YYYY') }
))} ``` To display individual blog posts, create a `BlogShow.astro` file in the Astro project `src/templates` folder with the following code: This component uses the `` component to display any widgets added to the `content` area and the `authorName` and `publicationDate` content entered into the fields of the same names. src/templates/BlogShow\.astro ```js --- import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro'; import dayjs from 'dayjs'; const { page, piece } = Astro.props.aposData; const { main } = piece; ---
{ piece.title }
Created by: { piece.authorName }
Released On { dayjs(piece.publicationDate).format('MMMM D, YYYY') }
``` Finally, these Astro components must be mapped to the corresponding ApostropheCMS page types. Open the Astro project `src/templates/index.js` file and modify it to contain the following code: src/templates/index.js ```js import HomePage from './HomePage.astro'; import BlogIndexPage from './BlogIndexPage.astro'; import BlogShowPage from './BlogShowPage.astro'; const templateComponents = { '@apostrophecms/home-page': HomePage, '@apostrophecms/blog-page:index': BlogIndexPage, '@apostrophecms/blog-page:show': BlogShowPage }; export default templateComponents; ``` ### Creating blog posts [Section titled “Creating blog posts”](#creating-blog-posts) Adding blog posts to your site is accomplished by using the ApostropheCMS content and management tools to create those posts and by publishing at least one index page to display them. With the Astro dev server running, navigate to the login page located at in your browser preview. Use the credentials that were added during the [creation of the ApostropheCMS project](https://docs.apostrophecms.org/guide/development-setup.html#creating-a-project) to log in as an administrator. Your ApostropheCMS project should still be running. Once you are logged in, your browser will be redirected to the home page of your project and will display an admin bar at the top for editing content and managing your project. To add your first blog post, click on the `Blogs` button in the admin bar to open the blog piece creation modal. Clicking on the `New Blog` button in the upper right will open an editing modal where you can add content. The `content` area field will allow you to add as many image and rich text widgets as you desire. You can repeat this step and add as many posts as you want. You will also follow these steps every time you want to add a new post. To publish a page for displaying all your posts, click on the `Pages` button in the admin bar. From the page tree modal click on the `New Page` button. In the `Type` dropdown in the right column select `Blog`. Add a title for the page and then click `Publish and View`. You will only need to do this once. Any new blog posts that are created will be automatically displayed on this page. Individual blog posts can be displayed by clicking on the link created on the index page. The `content` area of individual posts can be edited directly on the page by navigating to the post and clicking `edit` in the admin bar. Other fields can be edited by using the editing modal opened when clicking the `Blogs` menu item in the admin bar. ### Deploying your site [Section titled “Deploying your site”](#deploying-your-site) To deploy your website, you need to host both your Astro and ApostropheCMS projects. For Astro, visit our [deployment guides](/en/guides/deploy/) and follow the instructions for your preferred hosting provider. For the ApostropheCMS project, follow the instructions for your hosting type in our [hosting guide](https://docs.apostrophecms.org/guide/hosting.html). Finally, you’ll need to supply an `APOS_HOST` environment variable to the Astro project to reflect the correct URL where your ApostropheCMS site has been deployed. ## Official Resources [Section titled “Official Resources”](#official-resources) * [Astro integration for ApostropheCMS](https://apostrophecms.com/extensions/astro-integration) - ApostropheCMS Astro plugin, integration guide and starter projects for both Apostrophe and Astro * [Sample Astro project for use with ApostropheCMS](https://github.com/apostrophecms/astro-frontend) - A simple Astro project with several pages and Apostrophe widgets already created. * [Sample ApostropheCMS starter-kit for use with Astro](https://apostrophecms.com/starter-kits/astro-integration-starter-kit) - An ApostropheCMS project with core page modifications for use with Astro. ## Community Resources [Section titled “Community Resources”](#community-resources) * [Integrating ApostropheCMS with Astro, Pt. 1](https://apostrophecms.com/blog/how-to-integrate-astro-with-apostrophecms-pt-1) by Antonello Zaini * [Integrating ApostropheCMS with Astro, Pt. 2](https://apostrophecms.com/blog/how-to-integrate-astro-with-apostrophecms-pt-2) by Antonello Zaini * [Show & Tell Live | Astro & Apostrophe](https://youtu.be/cwJhtJhAhwA?si=6iUU9EjidfdnOdCh)
# Builder.io & Astro
> Add content to your Astro project using Builder.io’s visual CMS
[Builder.io](https://www.builder.io/) is a visual CMS that supports drag-and-drop content editing for building websites. This recipe will show you how to connect your Builder space to Astro with zero client-side JavaScript. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) To get started, you will need to have the following: * **A Builder account and space** - If you don’t have an account yet, [sign up for free](https://www.builder.io/) and create a new space. If you already have a space with Builder, feel free to use it, but you will need to modify the code to match the model name (`blogpost`) and custom data fields. * **A Builder API key** - This public key will be used to fetch your content from Builder. [Read Builder’s guide on how to find your key](https://www.builder.io/c/docs/using-your-api-key#finding-your-public-api-key). ## Setting up credentials [Section titled “Setting up credentials”](#setting-up-credentials) To add your Builder API key and your Builder model name to Astro, create a `.env` file in the root of your project (if one does not already exist) and add the following variables: .env ```ini BUILDER_API_PUBLIC_KEY=YOUR_API_KEY BUILDER_BLOGPOST_MODEL='blogpost' ``` Now, you should be able to use this API key in your project. If you would like to have IntelliSense for your environment variables, you can create a `env.d.ts` file in the `src/` directory and configure `ImportMetaEnv` like this: src/env.d.ts ```ts interface ImportMetaEnv { readonly BUILDER_API_PUBLIC_KEY: string; } ``` Your project should now include these files: * **.env** * astro.config.mjs * package.json ## Making a blog with Astro and Builder [Section titled “Making a blog with Astro and Builder”](#making-a-blog-with-astro-and-builder) ### Creating a model for a blog post [Section titled “Creating a model for a blog post”](#creating-a-model-for-a-blog-post) The instructions below create an Astro blog using a Builder model (Type: “Section”) called `blogpost` that contains two required text fields: `title` and `slug`. In the Builder app create the model that will represent a blog post: go to the **Models** tab and click the **+ Create Model** button to create model with the following fields and values: * **Type:** Section * **Name:** “blogpost” * **Description:** “This model is for a blog post” In your new model use the **+ New Custom Field** button to create 2 new fields: 1. Text field * **Name:** “title” * **Required:** Yes * **Default value** “I forgot to give this a title” (leave the other parameters as their defaults) 2. Text field * **Name:** “slug” * **Required:** Yes * **Default value** “some-slugs-take-their-time” (leave the other parameters as their defaults) Then click the **Save** button in the upper right. Slugs There are some pitfalls with the `slug` field: * Make sure your slug is not just a number. This seems to break the fetch request to Builder’s API. * Make sure your slugs are unique, since your site’s routing will depend on that. ### Setting up the preview [Section titled “Setting up the preview”](#setting-up-the-preview) To use Builder’s visual editor, create the page `src/pages/builder-preview.astro` that will render the special ``: * .env * astro.config.mjs * package.json Then add the following content: src/pages/builder-preview\.astro ```astro --- const builderAPIpublicKey = import.meta.env.BUILDER_API_PUBLIC_KEY; const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL; --- Preview for builder.ioThis is your header ``` In the above example, `` tells Builder where to insert the content from its CMS. #### Setting the new route as the preview URL [Section titled “Setting the new route as the preview URL”](#setting-the-new-route-as-the-preview-url) 1. Copy the full URL of your preview, including the protocol, to your clipboard (e.g. `https://{your host}/builder-preview`). 2. Go to the **Models** tab in your Builder space, pick the model you’ve created and paste the URL from step 1 into the **Preview URL** field. Make sure the URL is complete and includes the protocol, for example `https://`. 3. Click the **Save** button in the upper right. #### Testing the preview URL setup [Section titled “Testing the preview URL setup”](#testing-the-preview-url-setup) 1. Make sure your site is live (e.g. your dev server is running) and the `/builder-preview` route is working. 2. In your Builder space under the **Content** tab, click on **New** to create a new content entry for your `blogpost` model. 3. In the Builder editor that just opened, you should be able to see the `builder-preview.astro` page with a big **Add Block** in the middle. ### Creating a blog post [Section titled “Creating a blog post”](#creating-a-blog-post) 1. In Builder’s visual editor, create a new content entry with the following values: * **title:** ‘First post, woohoo!’ * **slug:** ‘first-post-woohoo’ 2. Complete your post using the **Add Block** button and add a text field with some post content. 3. In the text field above the editor, give your entry a name. This is how it will be listed in the Builder app. 4. When you’re ready click the **Publish** button in the upper right corner. 5. Create as many posts as you like, ensuring that all content entries contain a `title` and a `slug` as well as some post content. ### Displaying a list of blog posts [Section titled “Displaying a list of blog posts”](#displaying-a-list-of-blog-posts) Add the following content to `src/pages/index.astro` in order to fetch and display a list of all post titles, each linking to its own page: src/pages/index.astro ```astro --- const builderAPIpublicKey = import.meta.env.BUILDER_API_PUBLIC_KEY; const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL; const { results: posts } = await fetch( `https://cdn.builder.io/api/v3/content/${builderModel}?${new URLSearchParams({ apiKey: builderAPIpublicKey, fields: ["data.slug", "data.title"].join(","), cachebust: "true", }).toString()}` ) .then((res) => res.json()) .catch(); --- Blog Index
``` Fetching via the content API returns an array of objects containing data for each post. The `fields` query parameter tells Builder which data is included (see highlighted code). `slug` and `title` should match the names of the custom data fields you’ve added to your Builder model. The `posts` array returned from the fetch displays a list of blog post titles on the home page. The individual page routes will be created in the next step. Go to your index route and you should be able to see a list of links each with the title of a blog post! ### Displaying a single blog post [Section titled “Displaying a single blog post”](#displaying-a-single-blog-post) Create the page `src/pages/posts/[slug].astro` that will [dynamically generate a page](/en/guides/routing/#dynamic-routes) for each post. * .env * astro.config.mjs * package.json This file must contain: * A [`getStaticPaths()`](/en/reference/routing-reference/#getstaticpaths) function to fetch `slug` information from Builder and create a static route for each blog post. * A `fetch()` to the Builder API using the `slug` identifier to return post content and metadata (e.g. a `title`). * A `` in the template to render the post content as HTML. Each of these is highlighted in the following code snippet. src/pages/posts/\[slug].astro ```diff --- +export async function getStaticPaths() { const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL; const builderAPIpublicKey = import.meta.env.BUILDER_API_PUBLIC_KEY; const { results: posts } = await fetch( `https://cdn.builder.io/api/v3/content/${builderModel}?${new URLSearchParams( { apiKey: builderAPIpublicKey, fields: ["data.slug", "data.title"].join(","), cachebust: "true", } ).toString()}` ) .then((res) => res.json()) .catch // ...catch some errors...); (); return posts.map(({ data: { slug, title } }) => ({ params: { slug }, props: { title }, })) } const { slug } = Astro.params; const { title } = Astro.props; const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL; +const builderAPIpublicKey = import.meta.env.BUILDER_API_PUBLIC_KEY; // Builder's API requires this field but for this use case the url doesn't seem to matter - the API returns the same HTML const encodedUrl = encodeURIComponent("moot"); const { html: postHTML } = await fetch( `https://cdn.builder.io/api/v1/qwik/${builderModel}?${new URLSearchParams({ apiKey: builderAPIpublicKey, url: encodedUrl, +"query.data.slug": slug, cachebust: "true", }).toString()}` ) .then((res) => res.json()) .catch(); --- {title}This is your header ``` Now when you click on a link on your index route, you will be taken to the individual blog post page. ### Publishing your site [Section titled “Publishing your site”](#publishing-your-site) To deploy your website, visit our [deployment guides](/en/guides/deploy/) and follow the instructions for your preferred hosting provider. #### Rebuild on Builder changes [Section titled “Rebuild on Builder changes”](#rebuild-on-builder-changes) 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 whenever you click **Publish** in the Builder editor. ##### Netlify [Section titled “Netlify”](#netlify) 1. Go to your site dashboard, then **Site Settings** 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 [Section titled “Vercel”](#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. ##### Adding a webhook to Builder [Section titled “Adding a webhook to Builder”](#adding-a-webhook-to-builder) 1. In your Builder dashboard, go into your **`blogpost`** model. Under **Show More Options**, select **Edit Webhooks** at the bottom. 2. Add a new webhook by clicking on **Webhook**. Paste the URL generated by your hosting provider into the **Url** field. 3. Click on **Show Advanced** under the URL field and toggle the option to select **Disable Payload**. With the payload disabled, Builder sends a simpler POST request to your hosting provider, which can be helpful as your site grows. Click **Done** to save this selection. With this webhook in place, whenever you click the **Publish** button in the Builder editor, your hosting provider rebuilds your site - and Astro fetches the newly published data for you. Nothing to do but lean back and pump out that sweet sweet content! ## Official resources [Section titled “Official resources”](#official-resources) * Check out [the official Builder.io starter project](https://github.com/BuilderIO/builder/tree/main/examples/astro-solidjs), which uses Astro and SolidJS. * The [official Builder quickstart guide](https://www.builder.io/c/docs/quickstart#step-1-add-builder-as-a-dependency) covers both the use of the REST API as well as data fetching through an integration with a JavaScript framework like Qwik, React or Vue. * [Builder’s API explorer](https://builder.io/api-explorer) can help if you need to troubleshoot your API calls. ## Community resources [Section titled “Community resources”](#community-resources) * Read [Connecting Builder.io’s Visual CMS to Astro](https://www.hamatoyogi.dev/blog/astro-log/connecting-builderio-to-astro) by Yoav Ganbar.
# ButterCMS & Astro
> Add content to your Astro project using ButterCMS
[ButterCMS](https://buttercms.com/) is a headless CMS and blog engine that allows you to publish structured content to use in your project. ## Integrating with Astro [Section titled “Integrating with Astro”](#integrating-with-astro) In this section, we’ll use the [ButterCMS SDK](https://www.npmjs.com/package/buttercms) to bring your data into your Astro project. To get started, you will need to have the following: ### Prerequisites [Section titled “Prerequisites”](#prerequisites) 1. **An Astro project** - If you don’t have an Astro project yet, our [Installation guide](/en/install-and-setup/) will get you up and running in no time. 2. **A ButterCMS account**. If you don’t have an account, you can [sign up](https://buttercms.com/join/) for a free trial. 3. **Your ButterCMS API Token** - You can find your API Token on the [Settings](https://buttercms.com/settings/) page. ### Setup [Section titled “Setup”](#setup) 1. Create a `.env` file in the root of your project and add your API token as an environment variable: .env ```ini BUTTER_TOKEN=YOUR_API_TOKEN_HERE ``` 2. Install the ButterCMS SDK as a dependency: * npm ```shell npm install buttercms ``` * pnpm ```shell pnpm add buttercms ``` * Yarn ```shell yarn add buttercms ``` 3. Create a `buttercms.js` file in a new `src/lib/` directory in your project: src/lib/buttercms.js ```js import Butter from "buttercms"; export const butterClient = Butter(import.meta.env.BUTTER_TOKEN); ``` **This authenticates the SDK using your API Token and exports it to be used across your project.** ### Fetching Data [Section titled “Fetching Data”](#fetching-data) To fetch content, import this client and use one of its `retrieve` functions. In this example, we [retrieve a collection](https://buttercms.com/docs/api/#retrieve-a-collection) that has three fields: a short text `name`, a number `price`, and a WYSIWYG `description`. src/pages/ShopItem.astro ```astro --- import { butterClient } from "../lib/buttercms"; const response = await butterClient.content.retrieve(["shopitem"]); interface ShopItem { name: string, price: number, description: string, } const items = response.data.data.shopitem as ShopItem[]; --- {items.map(item =>
{item.name} - ${item.price}
)} ``` The interface mirrors the field types. The WYSIWYG `description` field loads as a string of HTML, and [`set:html`](/en/reference/directives-reference/#sethtml) lets you render it. Similarly, you can [retrieve a page](https://buttercms.com/docs/api/#get-a-single-page) and display its fields: src/pages/ShopItem.astro ```astro --- import { butterClient } from "../lib/buttercms"; const response = await butterClient.page.retrieve("*", "simple-page"); const pageData = response.data.data; interface Fields { seo_title: string, headline: string, hero_image: string, } const fields = pageData.fields as Fields; --- {fields.seo_title}
{fields.headline}
``` ## Official Resources [Section titled “Official Resources”](#official-resources) * [Astro + ButterCMS Starter Project](https://buttercms.com/starters/astro-starter-project/) * The [full ButterCMS API documentation](https://buttercms.com/docs/api/) * ButterCMS’s [JavaScript Guide](https://buttercms.com/docs/api-client/javascript/) ## Community Resources [Section titled “Community Resources”](#community-resources) * Add yours!
# Caisy & Astro
> Add content to your Astro project using Caisy as a CMS
[Caisy](https://caisy.io/) is a headless CMS that exposes a GraphQL API to access content. ## Using Caisy CMS with Astro [Section titled “Using Caisy CMS with Astro”](#using-caisy-cms-with-astro) Use `graphql-request` and Caisy’s rich text renderer for Astro to fetch your CMS data and display your content on an Astro page: src/pages/blog/\[...slug].astro ```astro --- import RichTextRenderer from '@caisy/rich-text-astro-renderer'; import { gql, GraphQLClient } from 'graphql-request'; const params = Astro.params; const client = new GraphQLClient( `https://cloud.caisy.io/api/v3/e/${import.meta.env.CAISY_PROJECT_ID}/graphql`, { headers: { 'x-caisy-apikey': import.meta.env.CAISY_API_KEY } } ); const gqlResponse = await client.request( gql` query allBlogArticle($slug: String) { allBlogArticle(where: { slug: { eq: $slug } }) { edges { node { text { json } title slug id } } } } `, { slug: params.slug } ); const post = gqlResponse?.allBlogArticle?.edges?.[0]?.node; ---
{post.title}
``` ## Official Resources [Section titled “Official Resources”](#official-resources) * Check out the Caisy + Astro example on [GitHub](https://github.com/caisy-io/caisy-example-astro) or [StackBlitz](https://stackblitz.com/github/caisy-io/caisy-example-astro?file=src%2Fpages%2Fblog%2F%5B...slug%5D.astro) * Query your documents in [draft mode](https://caisy.io/developer/docs/external-api/localization-and-preview#preview-mode-15) and multiple [locales](https://caisy.io/developer/docs/external-api/localization-and-preview#localization-in-a-graphql-query-8). * Use [pagination](https://caisy.io/developer/docs/external-api/queries-pagination) to query large numbers of documents. * Use [filter](https://caisy.io/developer/docs/external-api/external-filter-and-sorting) in your queries and [order](https://caisy.io/developer/docs/external-api/external-filter-and-sorting#sorting-8) the results
# CloudCannon & Astro
> Add content to your Astro project using CloudCannon as a CMS
[CloudCannon](https://cloudcannon.com) is a Git-based headless content management system that provides a visual editor for your content. ## Official Resources [Section titled “Official Resources”](#official-resources) * [Astro Starter Template](https://cloudcannon.com/templates/astro-starter/) * [Astro Multilingual Starter Template](https://cloudcannon.com/templates/astro-multilingual-starter/) * [Astro Starter Guide](https://cloudcannon.com/documentation/guides/astro-starter-guide/) * [Bookshop & Astro Guide](https://cloudcannon.com/documentation/guides/bookshop-astro-guide/) * [Astro Beginner Tutorial Series](https://cloudcannon.com/tutorials/astro-beginners-tutorial-series/) * Blog: [How CloudCannon’s live editing works with Astro and Bookshop](https://cloudcannon.com/blog/how-cloudcannons-live-editing-works-with-astro-and-bookshop/) * Blog: [Out-of-this-world support for all Astro users](https://cloudcannon.com/blog/out-of-this-world-support-for-all-astro-users/) ## Community Resources [Section titled “Community Resources”](#community-resources) * [CloudCannon announces official support for Astro](https://astro.build/blog/astro-cloudcannon-support/) ## Themes [Section titled “Themes”](#themes) * [ Sendit](https://astro.build/themes/details/sendit/)
# Contentful & Astro
> Add content to your Astro project using Contentful as a CMS
[Contentful](https://www.contentful.com/) is a headless CMS that allows you to manage content, integrate with other services, and publish to multiple platforms. ## Integrating with Astro [Section titled “Integrating with Astro”](#integrating-with-astro) In this section, we’ll use the [Contentful SDK](https://github.com/contentful/contentful.js) to connect your Contentful space to Astro with zero client-side JavaScript. ### Prerequisites [Section titled “Prerequisites”](#prerequisites) To get started, you will need to have the following: 1. **An Astro project** - If you don’t have an Astro project yet, our [Installation guide](/en/install-and-setup/) will get you up and running in no time. 2. **A Contentful account and a Contentful space**. If you don’t have an account, you can [sign up](https://www.contentful.com/sign-up/) for a free account and create a new Contentful space. You can also use an existing space if you have one. 3. **Contentful credentials** - You can find the following credentials in your Contentful dashboard **Settings > API keys**. If you don’t have any API keys, create one by selecting **Add API key**. * **Contentful space ID** - The ID of your Contentful space. * **Contentful delivery access token** - The access token to consume *published* content from your Contentful space. * **Contentful preview access token** - The access token to consume *unpublished* content from your Contentful space. ### Setting up credentials [Section titled “Setting up credentials”](#setting-up-credentials) To add your Contentful space’s credentials to Astro, create an `.env` file in the root of your project with the following variables: .env ```ini 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. 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 ```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: * **.env** * astro.config.mjs * package.json ### Installing dependencies [Section titled “Installing dependencies”](#installing-dependencies) To connect with your Contentful space, install both of the following using the single command below for your preferred package manager: * [`contentful.js`](https://github.com/contentful/contentful.js), the official Contentful SDK for JavaScript * [`rich-text-html-renderer`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-html-renderer), a package to render Contentful’s rich text fields to HTML. - npm ```shell npm install contentful @contentful/rich-text-html-renderer ``` - pnpm ```shell pnpm add contentful @contentful/rich-text-html-renderer ``` - Yarn ```shell yarn add contentful @contentful/rich-text-html-renderer ``` Next, create a new file called `contentful.ts` in the `src/lib/` directory of your project. src/lib/contentful.ts ```ts 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", }); ``` The above code snippet creates a new Contentful client, passing in credentials from the `.env` file. Caution While in development mode, your content will be fetched from the **Contentful preview API**. This means that you will be able to see unpublished content from the Contentful web app. At build time, your content will be fetched from the **Contentful delivery API**. This means that only published content will be available at build time. Finally, your root directory should now include these new files: * .env * astro.config.mjs * package.json ### Fetching data [Section titled “Fetching data”](#fetching-data) Astro components can fetch data from your Contentful account by using the `contentfulClient` and specifying the `content_type`. 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: ```astro --- 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({ content_type: "blogPost", }); --- {entries.items.map((item) => (
{item.fields.title}
))} ``` You can find more querying options in the [Contentful documentation](https://contentful.github.io/contentful.js/). ## Making a blog with Astro and Contentful [Section titled “Making a blog with Astro and Contentful”](#making-a-blog-with-astro-and-contentful) With the setup above, you are now able to create a blog that uses Contentful as the CMS. ### Prerequisites [Section titled “Prerequisites”](#prerequisites-1) 1. **A Contentful space** - For this tutorial we recommend starting with an empty space. If you already have a content model, feel free to use it, but you will need to modify our code snippets to match your content model. 2. **An Astro project integrated with the [Contentful SDK](https://github.com/contentful/contentful.js)** - See [integrating with Astro](#integrating-with-astro) for more details on how to set up an Astro project with Contentful. ### Setting up a Contentful model [Section titled “Setting up a Contentful model”](#setting-up-a-contentful-model) Inside your Contentful space, in the **Content model** section, create a new content model with the following fields and values: * **Name:** Blog Post * **API identifier:** `blogPost` * **Description:** This content type is for a blog post In your newly created content type, use the **Add Field** button to add 5 new fields with the following parameters: 1. Text field * **Name:** title * **API identifier:** `title` (leave the other parameters as their defaults) 2. Date and time field * **Name:** date * **API identifier:** `date` 3. Text field * **Name:** slug * **API identifier:** `slug` (leave the other parameters as their defaults) 4. Text field * **Name:** description * **API identifier:** `description` 5. Rich text field * **Name:** content * **API identifier:** `content` Click **Save** to save your changes. In the **Content** section of your Contentful space, create a new entry by clicking the **Add Entry** button. Then, fill in the fields: * **Title:** `Astro is amazing!` * **Slug:** `astro-is-amazing` * **Description:** `Astro is a new static site generator that is blazing fast and easy to use.` * **Date:** `2022-10-05` * **Content:** `This is my first blog post!` Click **Publish** to save your entry. You have just created your first blog post. 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 [Section titled “Displaying a list of blog posts”](#displaying-a-list-of-blog-posts) 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 ```diff 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", }); ``` 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. Import `BlogPost` interface and `contentfulClient` from `src/lib/contentful.ts`. 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 ```astro --- import { contentfulClient } from "../lib/contentful"; import type { BlogPost } from "../lib/contentful"; const entries = await contentfulClient.getEntries({ content_type: "blogPost", }); --- ``` 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. 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 ```diff --- import { contentfulClient } from "../lib/contentful"; import type { BlogPost } from "../lib/contentful"; const entries = await contentfulClient.getEntries({ 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() }; +}); --- ``` Finally, you can use `posts` in your template to show a preview of each blog post. src/pages/index.astro ```diff --- import { contentfulClient } from "../lib/contentful"; import type { BlogPost } from "../lib/contentful"; const entries = await contentfulClient.getEntries({ 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() }; }); --- My Blog
``` ### Generating individual blog posts [Section titled “Generating individual blog posts”](#generating-individual-blog-posts) 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 [Section titled “Static site generation”](#static-site-generation) If you’re using Astro’s default static mode, you’ll use [dynamic routes](/en/guides/routing/#dynamic-routes) and the `getStaticPaths()` function. This function will be called at build time to generate the list of paths that become pages. Create a new file named `[slug].astro` in `src/pages/posts/`. As you did on `index.astro`, import the `BlogPost` interface and `contentfulClient` from `src/lib/contentful.ts`. This time, fetch your data inside a `getStaticPaths()` function. src/pages/posts/\[slug].astro ```astro --- import { contentfulClient } from "../../lib/contentful"; import type { BlogPost } from "../../lib/contentful"; export async function getStaticPaths() { const entries = await contentfulClient.getEntries({ content_type: "blogPost", }); } --- ``` 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 ```diff --- 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({ 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; } --- ``` The property inside `params` must match the name of the dynamic route. Since our filename is `[slug].astro`, we use `slug`. In our example, the `props` object passes three properties to the page: * title (a string) * content (a rich text Document converted to HTML using `documentToHtmlString`) * date (formatted using the `Date` constructor) Finally, you can use the page `props` to display your blog post. src/pages/posts/\[slug].astro ```diff --- 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({ 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; --- {title}
{title}
``` Navigate to and click on one of your posts to make sure your dynamic route is working! #### On-demand rendering [Section titled “On-demand rendering”](#on-demand-rendering) If you’ve [opted into on-demand rendering with an adapter](/en/guides/on-demand-rendering/), you will use a dynamic route that uses a `slug` parameter to fetch the data from Contentful. Create a `[slug].astro` page in `src/pages/posts`. Use [`Astro.params`](/en/reference/api-reference/#params) to get the slug from the URL, then pass that to `getEntries`: src/pages/posts/\[slug].astro ```astro --- import { contentfulClient } from "../../lib/contentful"; import type { BlogPost } from "../../lib/contentful"; const { slug } = Astro.params; const data = await contentfulClient.getEntries({ content_type: "blogPost", "fields.slug": slug, }); --- ``` If the entry is not found, you can redirect the user to the 404 page using [`Astro.redirect`](/en/guides/routing/#dynamic-redirects). src/pages/posts/\[slug].astro ```diff --- import { contentfulClient } from "../../lib/contentful"; import type { BlogPost } from "../../lib/contentful"; const { slug } = Astro.params; +try { const data = await contentfulClient.getEntries({ content_type: "blogPost", "fields.slug": slug, }); +} catch (error) { +return Astro.redirect("/404"); +} --- ``` To pass post data to the template section, create a `post` object outside the `try/catch` block. 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 ```diff --- 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({ 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"); } --- ``` Finally, you can reference `post` to display your blog post in the template section. src/pages/posts/\[slug].astro ```diff --- 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({ 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?.title}
{post?.title}
``` ### Publishing your site [Section titled “Publishing your site”](#publishing-your-site) To deploy your website, visit our [deployment guides](/en/guides/deploy/) and follow the instructions for your preferred hosting provider. #### Rebuild on Contentful changes [Section titled “Rebuild on Contentful changes”](#rebuild-on-contentful-changes) 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) 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 [Section titled “Vercel”](#vercel) 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. ##### Adding a webhook to Contentful [Section titled “Adding a webhook to Contentful”](#adding-a-webhook-to-contentful) 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. Now, whenever you publish a new blog post in Contentful, a new build will be triggered and your blog will be updated.
# Cosmic & Astro
> Add content to your Astro project using Cosmic as a CMS
[Cosmic](https://www.cosmicjs.com/) is a [headless CMS](https://www.cosmicjs.com/headless-cms) that provides an admin dashboard to manage content and an API that can integrate with a diverse range of frontend tools. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) 1. **An Astro project**- If you’d like to start with a fresh Astro project, read the [installation guide](/en/install-and-setup/). This guide follows a simplified version of the [Astro Headless CMS Theme](https://astro.build/themes/details/cosmic-cms-blog/) with [Tailwind CSS](https://tailwindcss.com/) for styling. 2. **A Cosmic account and Bucket** - [Create a free Cosmic account](https://app.cosmicjs.com/signup) if you don’t have one. After creating your account, you’ll be prompted to create a new empty project. There’s also a [Simple Astro Blog template](https://www.cosmicjs.com/marketplace/templates/simple-astro-blog) available (this is the same template as the Astro Headless CMS Theme) to automatically import all of the content used in this guide. 3. **Your Cosmic API keys** - From your Cosmic dashboard, you will need to locate both the **Bucket slug** and **Bucket read key** in order to connect to Cosmic. ## Integrating Cosmic with Astro [Section titled “Integrating Cosmic with Astro”](#integrating-cosmic-with-astro) ### Installing Necessary Dependencies [Section titled “Installing Necessary Dependencies”](#installing-necessary-dependencies) Add the [Cosmic JavaScript SDK](https://www.npmjs.com/package/@cosmicjs/sdk) to fetch data from your Cosmic Bucket. * npm ```shell npm install @cosmicjs/sdk ``` * pnpm ```shell pnpm add @cosmicjs/sdk ``` * Yarn ```shell yarn add @cosmicjs/sdk ``` ### Configuring API Keys [Section titled “Configuring API Keys”](#configuring-api-keys) Create a `.env` file at the root of your Astro project (if it does not already exist). Add both the **Bucket slug** and **Bucket read key** available from your Cosmic dashboard as public environment variables. .env ```ini PUBLIC_COSMIC_BUCKET_SLUG=YOUR_BUCKET_SLUG PUBLIC_COSMIC_READ_KEY=YOUR_READ_KEY ``` ## Fetching Data from Cosmic [Section titled “Fetching Data from Cosmic”](#fetching-data-from-cosmic) 1. Create a new file called `cosmic.js`. Place this file inside of the `src/lib` folder in your project. 2. Add the following code to fetch data from Cosmic using the SDK and your environment variables. The example below creates a function called `getAllPosts()` to fetch metadata from Cosmic `posts` objects: src/lib/cosmic.js ```js import { createBucketClient } from '@cosmicjs/sdk' const cosmic = createBucketClient({ bucketSlug: import.meta.env.PUBLIC_COSMIC_BUCKET_SLUG, readKey: import.meta.env.PUBLIC_COSMIC_READ_KEY }) export async function getAllPosts() { const data = await cosmic.objects .find({ type: 'posts' }) .props('title,slug,metadata,created_at') return data.objects } ``` Read more about [queries in the Cosmic documentation](https://www.cosmicjs.com/docs/api/queries). 3. Import your query function in a `.astro` component. After fetching data, the results from the query can be used in your Astro template. The following example shows fetching metadata from Cosmic `posts` and passing these values as props to a `` component to create a list of blog posts: src/pages/index.astro ```astro --- import Card from '../components/Card.astro' import { getAllPosts } from '../lib/cosmic' const data = await getAllPosts() ---
{ data.map((post) => ( tag)} /> )) }
``` [View source code for the Card component](https://github.com/cosmicjs/simple-astro-blog/blob/main/src/components/Card.astro) ## Building a Blog with Astro and Cosmic [Section titled “Building a Blog with Astro and Cosmic”](#building-a-blog-with-astro-and-cosmic) You can manage your Astro blog’s content using Cosmic and create webhooks to automatically redeploy your website when you update or add new content. ### Cosmic Content Objects [Section titled “Cosmic Content Objects”](#cosmic-content-objects) The following instructions assume that you have an **Object Type** in Cosmic called **posts**. Each individual blog post is a `post` that is defined in the Cosmic dashboard with the following Metafields: 1. **Text input** - Author 2. **Image** - Cover Image 3. **Repeater** - Tags * **Text input** - Title 4. **Text area** - Excerpt 5. **Rich Text** - Content ### Displaying a List of Blog Posts in Astro [Section titled “Displaying a List of Blog Posts in Astro”](#displaying-a-list-of-blog-posts-in-astro) Using the same [data-fetching method](#fetching-data-from-cosmic) as above, import the `getAllPosts()` query from your script file and await the data. This function provides metadata about each `post` which can be displayed dynamically. The example below passes these values to a `` component to display a formatted list of blog posts: src/pages/index.astro ```astro --- import Card from '../components/Card.astro' import { getAllPosts } from '../lib/cosmic' const data = await getAllPosts() ---
{ data.map((post) => ( tag)} /> )) }
``` ### Generating Individual Blog Posts with Astro [Section titled “Generating Individual Blog Posts with Astro”](#generating-individual-blog-posts-with-astro) To generate an individual page with full content for each blog post, create a [dynamic routing page](/en/guides/routing/#dynamic-routes) at `src/pages/blog/[slug].astro`. This page will export a `getStaticPaths()` function to generate a page route at the `slug` returned from each `post` object. This function is called at build time and generates and renders all of your blog posts at once. To access your data from Cosmic: * Query Cosmic inside of `getStaticPaths()` to fetch data about each post and provide it to the function. * Use each `post.slug` as a route parameter, creating the URLs for each blog post. * Return each `post` inside of `Astro.props`, making the post data available to HTML template portion of the page component for rendering. The HTML markup below uses various data from Cosmic, such as the post title, cover image, and the **rich text content** (full blog post content). Use [set:html](/en/reference/directives-reference/#sethtml) on the element displaying the rich text content from Cosmic to render HTML elements on the page exactly as fetched from Cosmic. src/pages/blog/\[slug].astro ```astro --- import { getAllPosts } from '../../lib/cosmic' import { Image } from 'astro:assets' export async function getStaticPaths() { const data = (await getAllPosts()) || [] return data.map((post) => { return { params: { slug: post.slug }, props: { post } } }) } const { post } = Astro.props ---
{post.title}
{post.metadata.author?.title} { post.metadata.cover_image && ( ) } ``` ### Publishing your site [Section titled “Publishing your site”](#publishing-your-site) To deploy your website, visit the [deployment guides](/en/guides/deploy/) and follow the instructions for your preferred hosting provider. #### Rebuild on Cosmic content updates [Section titled “Rebuild on Cosmic content updates”](#rebuild-on-cosmic-content-updates) You can set up a webhook in Cosmic directly to trigger a redeploy of your site on your hosting platform (e.g. Vercel) whenever you update or add content Objects. Under “Settings” > “webhooks”, add the URL endpoint from Vercel and select the Object Type you would like to trigger the webhook. Click “Add webhook” to save it. ##### Netlify [Section titled “Netlify”](#netlify) 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 [Section titled “Vercel”](#vercel) 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. ## Themes [Section titled “Themes”](#themes) * [ Astro Headless CMS Blog](https://astro.build/themes/details/cosmic-cms-blog/)
# Craft CMS & Astro
> Add content to your Astro project using Craft CMS as a CMS
[Craft CMS](https://craftcms.com/) is a flexible open source CMS with an accessible authoring experience. It includes its own front end, but can also be used as a headless CMS to provide content to your Astro project. ## Official Resources [Section titled “Official Resources”](#official-resources) * Check out the official Craft CMS [GraphQL API guide](https://craftcms.com/docs/5.x/development/graphql.html) * The official documentation for Craft’s [`headlessMode` config setting](https://craftcms.com/docs/5.x/reference/config/general.html#headlessmode) ## Community Resources [Section titled “Community Resources”](#community-resources) * [SSG Astro with Headless Craft CMS Content Fetched At Build Time](https://www.olets.dev/posts/ssg-astro-with-headless-craft-cms-content-fetched-at-build-time/) * [SSG Astro with Headless Craft CMS Content Fetched At Build Time Or Cached In Advance](https://www.olets.dev/posts/ssg-astro-with-headless-craft-cms-content-fetched-at-build-time-or-cached-in-advance/) * [SSR Astro With Headless Craft CMS](https://www.olets.dev/posts/ssr-astro-with-headless-craft-cms/)
# Crystallize & Astro
> Add content to your Astro project using Crystallize as a CMS
[Crystallize](https://crystallize.com/) is a headless content management system for eCommerce that exposes a GraphQL API. ## Example [Section titled “Example”](#example) src/pages/index.astro ```astro --- // Fetch your catalogue paths from Crystallize GraphQL API import BaseLayout from '../../layouts/BaseLayout.astro'; import { createClient } from '@crystallize/js-api-client'; const apiClient = createClient({ tenantIdentifier: 'furniture' }); const query = ` query getCataloguePaths{ catalogue(language: "en", path: "/shop") { name children { name path } } } ` const { data: { catalogue } } = await apiClient.catalogueApi(query) ---
{catalogue.name}
```
# DatoCMS & Astro
> Add content to your Astro project using DatoCMS
[DatoCMS](https://datocms.com/) is a web-based, headless CMS to manage digital content for your sites and apps. ## Integrating with Astro [Section titled “Integrating with Astro”](#integrating-with-astro) In this guide, you will fetch content data from DatoCMS in your Astro project, then display it on a page. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) To get started, you will need to have the following: * **An Astro project** - If you don’t have an Astro project yet, you can follow the instructions in our [Installation guide](/en/install-and-setup/). * **A DatoCMS account and project** - If you don’t have an account, you can [sign up for a free account](https://dashboard.datocms.com/signup). * **The read-only API Key for your DatoCMS project** - You can find it in the admin dashboard of your project, under “Settings” > “API Tokens”. ## Setting up the credentials [Section titled “Setting up the credentials”](#setting-up-the-credentials) Create a new file (if one does not already exist) named `.env` in the root of your Astro project. Add a new environment variable as follows, using the API key found in your DatoCMS admin dashboard: .env ```ini DATOCMS_API_KEY=YOUR_API_KEY ``` For TypeScript support, declare the typing of this environment variable in the `env.d.ts` file in the `src/` folder. If this file does not exist, you can create it and add the following: src/env.d.ts ```ts interface ImportMetaEnv { readonly DATOCMS_API_KEY: string; } ``` Your root directory should now include these files: * **.env** * astro.config.mjs * package.json ## Create a Model in DatoCMS [Section titled “Create a Model in DatoCMS”](#create-a-model-in-datocms) In the DatoCMS admin dashboard of your project, navigate to “Settings” > “Models” and create a new Model called “Home” with the “Single Instance” toggle selected. This will create a home page for your project. In this model, add a new text field for the page title. Navigate to the “Content” tab in your project and click on your newly-created home page. You can now add a title. Save the page, and continue. ## Fetching data [Section titled “Fetching data”](#fetching-data) In your Astro project, navigate to the page that will fetch and display your CMS content. Add the following query to fetch the content for `home` using the DatoCMS GraphQL API. This example displays the page title from DatoCMS on `src/pages/index.astro`: src/pages/index.astro ```astro --- const response = await fetch('https://graphql.datocms.com/', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: `Bearer ${import.meta.env.DATOCMS_API_KEY}`, }, body: JSON.stringify({ query: `query Homepage { home { title } } `, }), }); const json = await response.json(); const data = json.data.home; ---
{data.title}
``` This GraphQL query will fetch the `title` field in the `home` page from your DatoCMS Project. When you refresh your browser, you should see the title on your page. ## Adding Dynamic modular content blocks [Section titled “Adding Dynamic modular content blocks”](#adding-dynamic-modular-content-blocks) If your DatosCMS project includes [modular content](https://www.datocms.com/docs/content-modelling/modular-content), then you will need to build a corresponding `.astro` component for each block of content (e.g. a text section, a video, a quotation block, etc.) that the modular field allows in your project. The example below is a `` Astro component for displaying a “Multiple-paragraph text” block from DatoCMS. src/components/Text.astro ```astro --- export interface TextProps { text: string } export interface Props { item: TextProps } const { item } = Astro.props; --- ``` To fetch these blocks, edit your GraphQL query to include the modular content block you created in DatoCMS. In this example, the modular content block is named **content** in DatoCMS. This query also includes the unique `_modelApiKey` of each item to check which block should be displayed in the modular field, based on which block was chosen by the content author in the DatoCMS editor. Use a switch statement in the Astro template to allow for dynamic rendering based on the data received from the query. The following example represents a DatoCMS modular content block that allows an author to choose between a text field (``) and an image (``) rendered on the home page: src/pages/index.astro ```diff --- +import Image from '../components/Image.astro'; +import Text from '../components/Text.astro'; const response = await fetch('https://graphql.datocms.com/', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: `Bearer ${import.meta.env.DATOCMS_API_KEY}`, }, body: JSON.stringify({ query: `query Homepage { home { title content { ... on ImageRecord { _modelApiKey image{ url } } ... on TextRecord { _modelApiKey text(markdown: true) } } } } `, }), }); const json = await response.json(); const data = json.data.home; ---
{data.title}
+{ + data.content.map((item: any) => { +switch (item._modelApiKey) { +case 'image': +return ; +case 'text': +return ; +default: +return null; + } + }) +} ``` ## Publishing your site [Section titled “Publishing your site”](#publishing-your-site) To deploy your website, visit our [deployment guides](/en/guides/deploy/) and follow the instructions for your preferred hosting provider. ## Publish on DatoCMS content changes [Section titled “Publish on DatoCMS content changes”](#publish-on-datocms-content-changes) 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 when you change content in DatoCMS. ### Netlify [Section titled “Netlify”](#netlify) 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 [Section titled “Vercel”](#vercel) 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. ### Adding a webhook to DatoCMS [Section titled “Adding a webhook to DatoCMS”](#adding-a-webhook-to-datocms) In your DatoCMS project admin dashboard, navigate to the **Settings** tab and click **Webhooks**. Click the plus icon to create a new webhook and give it a name. In the URL field, paste the URL generated by your preferred hosting service. As Trigger, select whichever option suits your needs. (For example: build every time a new record is published.) ## Starter project [Section titled “Starter project”](#starter-project) You can also check out the [Astro blog template](https://www.datocms.com/marketplace/starters/astro-template-blog) on the DatoCMS marketplace to learn how to create a blog with Astro and DatoCMS.
# Decap CMS & Astro
> Add content to your Astro project using Decap as a CMS
[Decap CMS](https://www.decapcms.org/) (formerly Netlify CMS) is an open-source, Git-based content management system. Decap allows you to take full advantage of all of Astro’s features, including image optimization and content collections. Decap adds a route (typically `/admin`) to your project that will load a React app to allow authorized users to manage content directly from the deployed website. Decap will commit changes directly to your Astro project’s source repository. ## Installing DecapCMS [Section titled “Installing DecapCMS”](#installing-decapcms) There are two options for adding Decap to Astro: 1. [Install Decap via a package manager](https://decapcms.org/docs/install-decap-cms/#installing-with-npm) with the following command: * npm ```shell npm install decap-cms-app ``` * pnpm ```shell pnpm add decap-cms-app ``` * Yarn ```shell yarn add decap-cms-app ``` 2. Import the package into a `
``` ## Configuration [Section titled “Configuration”](#configuration) 1. Create a static admin folder at `public/admin/` 2. Add `config.yml` to `public/admin/`: 3. To add support for content collections, configure each schema in `config.yml`. The following example configures a `blog` collection, defining a `label` for each entry’s frontmatter property: /public/admin/config.yml ```yml collections: - name: "blog" # Used in routes, e.g., /admin/collections/blog label: "Blog" # Used in the UI folder: "src/content/blog" # The path to the folder where the documents are stored create: true # Allow users to create new documents in this collection fields: # The fields for each document, usually in frontmatter - { label: "Layout", name: "layout", widget: "hidden", default: "blog" } - { label: "Title", name: "title", widget: "string" } - { label: "Publish Date", name: "date", widget: "datetime" } - { label: "Featured Image", name: "thumbnail", widget: "image" } - { label: "Rating (scale of 1-5)", name: "rating", widget: "number" } - { label: "Body", name: "body", widget: "markdown" } ``` 4. Add the `admin` route for your React app in `src/pages/admin.html`. /src/pages/admin.html ```html