Get Started Astro Tutorial — 9. Displaying a post
- 1. Creating the project in Content Island
- 2. Defining the data model
- 3. Adding data
- 4. Creating the Astro project
- 5. Creating the folder structure
- 6. Connecting Content Island with Astro
- 7. Building the About page
- 8. Page with list of posts
- 9. Displaying a post
- 10. Automatic Deployment with Astro and Content Island using GitHub Actions
- 11. Deploying to Netlify
- 12. Auto-deploy with GitHub Webhooks + Content Island
9. Displaying a post
To build the page that displays a single post, we’ll reuse much of what we’ve already done for the post list page. You might wonder: why reuse code if we’re only loading one post here? The reason lies in how Astro works.
At build time, Astro reads all posts and generates a separate static page for each post—so the process is quite similar to building the post list page.
To generate one page per post, we’ll use a dynamic route "[slug]". That slug will be the post title, so if we have a post titled "my-first-post", the generated page will be "my-first-post.html".
Let’s get started. Under the "posts" folder, create a file named "[slug].astro".
First, we configure Markdown rendering and syntax highlighting:
./src/pages/posts/[slug].astro
---
import { marked } from 'marked';
import Layout from '../../layouts/Layout.astro';
import { getPosts } from './post.api';
import hljs from 'highlight.js';
const renderer = new marked.Renderer();
renderer.code = ({ text, lang }) => {
const validLang = hljs.getLanguage(lang || 'plaintext') ? lang || 'plaintext' : 'plaintext';
const highlighted = hljs.highlight(text, { language: validLang }).value;
return `<pre><code class="hljs ${validLang}">${highlighted}</code></pre>`;
};
marked.setOptions({ renderer });
---Then we use "getStaticPaths" to generate a slug for each post. We pass the correct post via props and generate each page:
Place this just before the frontmatter:
export async function getStaticPaths() {
const posts = await getPosts();
return posts.map((post) => ({
params: {
slug: post.title
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-"),
},
props: { post },
}));
}
const { post } = Astro.props;
const postContent = marked(post.content || "");
const postDate = new Date(post.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
const postImage = `url(${post.image.url})`;Let’s render a simple layout:
<Layout>
<article>
<h1>{post.title}</h1>
<p><strong>Date:</strong> {post.date}</p>
<p><strong>Author:</strong> {post.author}</p>
<img src={post.image.url} alt={post.title} width="800" />
<p><strong>Summary:</strong> {post.summary}</p>
<div>
<strong>Content:</strong>
<p>{post.content}</p>
</div>
</article>
</Layout>Now let’s add some nice styles:
<style define:vars={{ postImage }}>
.background::before {
content: '';
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 360px;
background-image: var(--postImage);
background-size: cover;
background-repeat: no-repeat;
background-position: top center;
}
.article {
max-width: 800px;
margin-right: auto;
margin-left: auto;
margin-top: var(--space-2xl);
padding: var(--space-lg);
background-color: var(--pure-white);
display: flex;
flex-direction: column;
gap: var(--space-lg);
}
.article__header {
text-align: center;
}
.article__title {
font-size: var(--fs-3xl);
font-family: var(--ff-title);
}
.article__content {
display: flex;
flex-direction: column;
gap: var(--space-sm);
h2 {
margin-top: var(--space-sm);
}
ul {
padding-left: var(--space-md);
}
img {
width: 100%;
object-fit: cover;
aspect-ratio: 21/9;
border-radius: var(--border-radius);
}
code {
font-size: var(--fs-xs);
border-radius: var(--border-radius);
padding: var(--space-xs);
}
}
</style>And update the markup to use the styles:
<Layout>
<div class="container background">
<article class="article">
<header class="article__header">
<p>
<time datetime="2025-03-05">{postDate}</time>
</p>
<h1 class="article__title">{post.title}</h1>
</header>
<div set:html={postContent} class="article__content" />
<footer>
<p>Written by <strong>{post.author}</strong></p>
</footer>
</article>
</div>
</Layout>And that’s it! ✅