is a lightweight web framework capable of shipping highly performant websites with minimal (or non-existent) JavaScript bundles. In this guide, we’re going to create our content with Cosmic and retrieve it in our Astro project, allowing for quick and easy content creation and collaboration. The end result will be a clean and composable website that aces the lighthouse scores all across the board. Astro TL;DR Install the Cosmic template. Simple Astro Blog Download the Simple Astro Blog . source code Step 1: Configure your Astro project To create a new Astro project, run . Astro provides a convenient to set up some boilerplate code and get your project configured quickly. You can choose from a few different templates, but in our case, we’re keeping it simple with the “Just the Basics” option configuring a nice organized directory, a few Astro components to reference, and an Astro config file. npm create astro@latest CLI tool If you are using Cosmic in this guide, make sure you install the : Cosmic NPM package # npm npm install cosmicjs # pnpm pnpm add cosmicjs # yarn yarn add cosmicjs Next, we will create two items in our project directory. First, create a folder in the folder, and then create a file. After you’ve created this file, create a in the root of your directory. Here, we will store our environment variables from the Cosmic Bucket we’re about to set up, and in our file we will import the environment variables and create our API calls to retrieve our data. lib src cosmic.js .env cosmic.js Once you’ve created these two items, put them aside for now, as we go on to creating our Content Model in Cosmic. Step 1.5 (Optional): Adding Astro Integrations Astro provides some useful , allowing you to quickly configure your favorite UI libraries, SSR adapters, and other tools like and . For this guide, we encourage using both the and integrations as they will improve website performance and developer experience. integrations Astro Image Tailwind CSS Astro Image Tailwind CSS Using the Astro CLI, we can run the commands and , and Astro will install each individual package and update the file to support the integration. npx astro add image npx astro add tailwind astro.config.mjs Step 2: Configure a Cosmic Bucket What is a headless CMS? With a headless CMS, content is created using a dashboard and then delivered using an API. The content layer is separated from the presentation layer, making content flexible, reusable, and scalable. If you’d like to learn more about what a headless CMS is, we created explaining it. this concise article Create an account If you haven’t already, create a . Then navigate to the dashboard to create a new project. We’re going to start from scratch here, but you choose from a wide variety of templates by selecting “Start with a template” and clicking “Select Template” on the desired template. Name your project and Bucket Environment, then click “Save Bucket.” Cosmic account Creating an Object Let’s create a simple Object, click “Add Object Type” and then you will see that we can now setup our first Object. We’re going to call this Object “Posts”, which will contain multiple blog posts, so go ahead and select “Multiple” then under “Singular Name” type in “Post.” Our default fields will contain the and the , so you can leave these as they are. Slug field Content field Content Model The Content Model is where we design the structure of our content. We can use different like strings, numbers, and even media (images). From this structure, we can craft our API calls to retrieve this specific data and render it onto our application. Metafields Our Object will consist of: An Excerpt (text) A Cover Image (media) Once you’ve created these Metafields, click “Save Object Type.” Let’s create our first Post! Navigate to “Posts” in the sidebar and click “Add Post.” Fill in the , , and with some sample text. Then, upload an image for the and select “Publish.” Title Excerpt Content Cover Image Now that we’ve got our first published Object, we’re ready to tie it all together and get this data into our Astro application. Step 3: Retrieve data from the Cosmic API Before we leave the Cosmic dashboard, we need to grab a couple of things. On the sidebar, click on To make this easier, I’m going to rename the to something like then click “Save Settings” Settings>API Acess. Bucket Slug my-astro-blog . Now we’re going to take the and values and place them into our file in our Astro app. Bucket Slug Read Key .env # .env PUBLIC_COSMIC_BUCKET_SLUG=my-astro-blog PUBLIC_COSMIC_READ_KEY=<your_read_key_here> Once set, let’s go into the file we created in Step 1. We will import the Cosmic module, instantiate an variable and import our environment variables. Refer to for setting environment variables in Astro. cosmic.js api these docs // src/lib/cosmic.js import Cosmic from "cosmicjs"; const api = Cosmic(); const bucket = api.bucket({ slug: import.meta.env.PUBLIC_COSMIC_BUCKET_SLUG, read_key: import.meta.env.PUBLIC_COSMIC_READ_KEY, }); Now that we’re all set up, let’s write our API call to get the Post we made in our dashboard. // src/lib/cosmic.js export async function getAllPosts() { const data = await bucket.objects .find({ type: "posts", }) .props("title,slug,content,metadata") return data.objects; } Using Cosmic’s , we can the specific type of Object in our API, which is “posts” (the slug of the Object itself), and set the to all of the parameters we set up when we created our Object earlier. In our case, we want to retrieve the , , , and the Cover Image, which lives inside of the chaining methods find props title slug content metadata. Remember that the default parameters are accessible at a higher level than the metadata, and the metadata itself comes from the custom metafields you create within an Object. Grabbing the Cover Image would look like this: . metadata.cover_image.imgix_url Rendering the data Now we can pull in the API call onto one of our Astro pages. By calling the function in the “helmet” of our Astro page or component, we can access all of the content we create in Cosmic. getAllPosts // src/pages/index.astro import { getAllPosts } from "../lib/cosmic" const data = await getAllPosts() To render out a list of the posts we make, we can create a minimal Card component like this: // src/components/Card.Astro --- const { href, title, body } = Astro.props --- <li class="bg-white rounded-md shadow-md hover:shadow-xl transition"> <a href={`/blog/${href}`}> <div class="flex flex-col h-full gap-y-2 p-6"> <h2 class="font-semibold text-xl pr-4 pt-2"> {title} </h2> <p> {body} </p> </div> </a> </li> Note that this is an Astro component. We can pass props to it by defining a de-structured object to . This functionality is done within the dashes at the top of the component, or the “helmet.” Astro.props Going back to our index.astro page, let’s pass the data from Cosmic and render the card. // src/pages/index.astro --- import { getAllPosts } from "../lib/cosmic" import Card from "../components/Card.astro"; const data = await getAllPosts() --- <section> <ul class="grid md:grid-cols-2 gap-8"> { data.map((post) => ( <Card title={post.title} href={post.slug} body={post.metadata.excerpt} /> )) } </ul> </section> You’ll notice that we are passing the slug of our Cosmic post into the prop. We can generate dynamic routes easily with Astro. We do this using , which will generate dynamic page routes at build time. href getStaticPaths // src/pages/blolg/[slug].astro --- import { getAllPosts } from "../../lib/cosmic"; import { Image } from "@astrojs/image/components"; export async function getStaticPaths() { const data = (await getAllPosts()) || []; return data.map((post) => { return { params: { slug: post.slug }, props: { post }, }; }); } const { post } = Astro.props; --- <article> <section class="border-b pb-8"> <h1 class="text-4xl font-bold">{post.title}</h1> <div class="my-8"></div> </section> <Image src={post.metadata.cover_image.imgix_url} format="webp" width={1200} aspectRatio={16 / 9} quality={50} alt="" class={"rounded-md shadow-lg my-12"} /> <p>{post.content}</p> </article> Using , we are generating the path for each “Post” in Cosmic using it’s . This happens at build time and uses the data stored in . We then pass the data for each “Post” through to the and further set the { } object to We can re-use our HTML template below the helmet for each blog post we render. getStaticPaths slug params props post Astro.props. After running a mobile Lighthouse report for the application, we have achieved 100s all across the board with little effort. Conclusion I hope you found this guide helpful to learn how Astro provides ease of use in development, easy integration with a headless CMS, and the capability to achieve high-performing Lighthouse scores out of the box. You can check out the template used in this guide here Also Published Here