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”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 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.
Setting up credentials
Section titled “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:
BUILDER_API_PUBLIC_KEY=YOUR_API_KEYBUILDER_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:
interface ImportMetaEnv { readonly BUILDER_API_PUBLIC_KEY: string;}Your project should now include these files:
Making a blog with Astro and Builder
Section titled “Making a blog with Astro and Builder”Creating a model for a blog post
Section titled “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:
-
Text field
- Name: “title”
- Required: Yes
- Default value “I forgot to give this a title”
(leave the other parameters as their defaults)
-
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.
Setting up the preview
Section titled “Setting up the preview”To use Builder’s visual editor, create the page src/pages/builder-preview.astro that will render the special <builder-component>:
Then add the following content:
---const builderAPIpublicKey = import.meta.env.BUILDER_API_PUBLIC_KEY;const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL;---
<html lang="en"> <head> <title>Preview for builder.io</title> </head> <body> <header>This is your header</header>
<builder-component model={builderModel} api-key={builderAPIpublicKey} ></builder-component> <script async src="https://cdn.builder.io/js/webcomponents"></script>
<footer>This is your footer</footer> </body></html>In the above example, <builder-component> 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”Testing the preview URL setup
Section titled “Testing the preview URL setup”Creating a blog post
Section titled “Creating a blog post”Displaying a list of blog posts
Section titled “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:
---
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();---
<html lang="en"> <head> <title>Blog Index</title> </head> <body> <ul> { posts.flatMap(({ data: { slug, title } }) => ( <li> <a href={`/posts/${slug}`}>{title}</a> </li> )) } </ul> </body></html>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”Create the page src/pages/posts/[slug].astro that will dynamically generate a page for each post.
This file must contain:
- A
getStaticPaths()function to fetchsluginformation from Builder and create a static route for each blog post. - A
fetch()to the Builder API using theslugidentifier to return post content and metadata (e.g. atitle). - A
<Fragment />in the template to render the post content as HTML.
Each of these is highlighted in the following code snippet.
---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 HTMLconst 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();---<html lang="en"> <head> <title>{title}</title> </head> <body> <header>This is your header</header> <article> <Fragment set:html={postHTML} /> </article> <footer>This is your footer</footer> </body></html>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”To deploy your website, visit our deployment guides and follow the instructions for your preferred hosting provider.
Rebuild on Builder changes
Section titled “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”Vercel
Section titled “Vercel”Adding a webhook to Builder
Section titled “Adding a webhook to Builder”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”- Check out the official Builder.io starter project, which uses Astro and SolidJS.
- The official Builder quickstart guide 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 can help if you need to troubleshoot your API calls.
Community resources
Section titled “Community resources”- Read Connecting Builder.io’s Visual CMS to Astro by Yoav Ganbar.