content/tutorials/1.getting-started/rendering-dynamic-blocks-using-astro.md
Having used Directus as a Headless CMS to create individual blocks that can be re-used on different pages, let's integrate them into our Astro website.
You will need:
To grant public access to your blocks collections in Directus,navigate to Settings -> Access Policies -> Public Policy From there, enable the Read permission for the blocks collections to ensure they are accessible as needed.
Create a new Astro project by running the command:
npx create-astro@latest astro-dynamic-blocks
When prompted, select the following configurations:
How would you like to start your new project? A basic, minimal starter (recommended)
Install dependencies? (recommended) Yes
Initialize a new git repository? (optional) No
Navigate into the project directory and install the Directus SDK by running the command:
npm install @directus/sdk
Next, run the command npm run dev to start the development server and you should see the Astro project running on http://localhost:4321/ in your browser.
First, create a .env file in the root of your project and add the following environment variables:
DIRECTUS_URL=https://your-directus-project-url.com
In the src directory, create a lib directory and inside of it, create a directus.ts file to set up your Directus client instance and fetch the page blocks.:
/// <reference types="vite/client" />
import { createDirectus, rest, readItems } from "@directus/sdk";
const DIRECTUS_URL = import.meta.env.DIRECTUS_URL;
const client = createDirectus(DIRECTUS_URL).with(rest());
export async function fetchPageBlocks(slug: string) {
console.log(slug);
const pages = await client.request(
readItems("pages", {
filter: {
slug: { _eq: slug },
},
fields: [
"*",
{
blocks: [
"*",
{
item: {
block_hero: ["*"],
block_cardgroup: [
"*",
{
posts: [
"*.*", // Fetch all fields from related posts
],
cards: [
"*.*", // Fetch all fields from related cards
],
},
],
block_richtext: ["*"],
},
},
],
},
],
limit: 1,
})
);
return pages[0] || []; // Return blocks array or empty if not found
}
export default client;
The code above:
fetchPageBlocks function that fetches all the related blocks for a specific page based on the slug passed as an argument.Now that the Directus SDK is set up, create the components that will render each of the blocks coming from Directus.
Going by the structure of our reusable blocks, let's create a single component for each individual collection in the components directory.
Create a Hero.astro file in the components directory and add the following code:
---
import { Image } from 'astro:assets';
interface Button {
label: string;
href: string;
variant: string;
}
const {
headline = '',
content = '',
buttons = [],
image = ''
} = Astro.props.item as {
headline?: string;
content?: string;
buttons?: Button[];
image?: string;
};
const DIRECTUS_URL = import.meta.env.DIRECTUS_URL;
---
<section>
{headline && <h1>{headline}</h1>}
{content && <div set:html={content} />}
{buttons.length > 0 && (
<div>
{buttons.map(({ label, href, variant }) => (
<a href={href || '#'} class={variant || ''}>
{label || 'Click here'}
</a>
))}
</div>
)}
{image && DIRECTUS_URL && (
)}
</section>
Create a RichText.astro file in the components directory and add the following code:
---
const { headline = '', content = '' } = Astro.props.item || {};
---
<section>
{headline && <h1>{headline}</h1>}
{content && <p set:html={content} />}
</section>
Create a CardGroup.astro file in the components directory and add the following code:
---
const {
headline = '',
content = '',
posts = [],
cards = [],
group_type = 'custom'
} = Astro.props.item || {};
const DIRECTUS_URL = import.meta.env.DIRECTUS_URL;
---
<section>
{headline && <h2>{headline}</h2>}
{content && <div set:html={content}></div>}
<div>
{group_type === "posts" && posts.length > 0 && (
<div style="display: flex;">
{posts.map(({ posts_id }) => (
posts_id && (
<div style="width: 400px; margin: 0 10px;">
{posts_id.title && <h3>{posts_id.title}</h3>}
{posts_id.image && (
)}
{posts_id.content && <div set:html={posts_id.content}></div>}
</div>
)
))}
</div>
)}
{group_type === "custom" && cards.length > 0 && (
<div style="display: flex;">
{cards.map(({ title, content }) => (
<div>
{title && <h3>{title}</h3>}
{content && <p>{content}</p>}
</div>
))}
</div>
)}
{group_type === "posts" && posts.length > 0 && (
<div style="display: flex;">
{posts.map(({ title, content }) => (
<div>
{title && <h3>{title}</h3>}
{content && <p>{content}</p>}
</div>
))}
</div>
)}
</div>
</section>
With all the components created, let's create one more component that will map each blocks to their component to render it correctly, e.g block_hero should render the Hero component.
In the components directory, create a BlocksToComponents.astro file and add the following code:
---
import Hero from "./Hero.astro";
import RichText from "./RichText.astro";
import CardGroup from "./CardGroup.astro";
const { blocks } = Astro.props
---
{blocks.map((block: any) => {
const View = (
block.collection === 'block_hero' ? Hero :
block.collection === 'block_richtext' ? RichText :
block.collection === 'block_cardgroup' ? CardGroup : () => null
)
return View && <View {...block} />
})}
This component loops through the blocks array and maps each block to its corresponding component along with the props it needs to render.
In the pages directory, create a [slug].astro file and add the following code:
---
import Layout from "../layouts/Layout.astro";
import { fetchPageBlocks } from "../lib/directus";
import client from "../lib/directus";
import { readItems } from "@directus/sdk";
import BlocksToComponents from "../components/BlocksToComponents.astro";
export async function getStaticPaths() {
// Fetch all available pages
const pages = await client.request(readItems("pages", { fields: ["slug"] }));
return pages.map((page) => ({
params: { slug: page.slug },
}));
}
const { slug } = Astro.params;
//fetch blocks for the page
const page = await fetchPageBlocks(slug);
const { blocks } = page
---
<Layout>
<BlocksToComponents blocks={blocks} />
</Layout>
The code above:
slug using the getStaticPaths method and returns the slug property as a params to Astro.slug.BlocksToComponents component to render the blocks.This ensures that the blocks are rendered dynamically based on the page they are associated with and the components they are mapped to.
Head over to your browser and navigate to http://localhost:4321/your-page-slug to see the blocks rendered on the page.
In this tutorial, you learned how to integrate Directus blocks into an Astro project. You set up the Directus SDK to fetch the blocks for a specific page and created components to render each block. You also created a component to map each block to its corresponding component and dynamically fetched the page data to render the blocks on the page.