Get Started Astro Tutorial — 8. Page with list of posts
- 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
8. Home Page – Post List
Let's get to the main page. In this case, we want to display a list of published posts.
How does this work in Astro?
At build time, we read all the posts and generate the listing page. The final result is a static HTML file served to the client with the list of posts.
To do this, we'll create a posts folder, where we define the model and API to fetch the list.
One interesting thing about Content Island is that we can directly generate the model of an entity in TypeScript from the application.
./posts/post.model.ts
import type { Media } from '@content-island/api-client';
export interface SinglePost {
id: string;
title: string;
slug: string;
date: string;
summary: string;
author: string;
content: string;
image: Media;
}Now let's move on to the API. To fetch the list of posts, we use getContentList, specify the content type name, and map the content to our model.
./posts/posts.api.ts
import client from "../../lib/client";
import type { SinglePost } from "./post.model";
import { mapContentToModel } from "@content-island/api-client";
export async function getPosts(): Promise<SinglePost[]> {
const posts = await client.getContentList<SinglePost>({
contentType: "Post",
});
return posts;
}And on the home page, we fetch the posts and display them.
In the server-side generation code, we read the list of posts and add a function to format the date.
./src/pages/index.astro
---
import { Image } from 'astro:assets';
import Layout from '../layouts/Layout.astro';
import { getPosts } from './posts/post.api';
const posts = await getPosts();
const date = (date: string) =>
new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---Let's show the data without styling for now:
<Layout>
<h1>Welcome to Astro Blog</h1>
<a href="/about">About</a>
<p>Latest posts:</p>
<ul>
{
posts.map(post => (
<li>
<h2>
<a href={`/posts/${post.slug}`}>{post.title}</a>
</h2>
<p>
<strong>Author:</strong> {post.author}
</p>
<p>{post.summary}</p>
</li>
))
}
</ul>
</Layout>It works… although it looks pretty ugly. Should we add some styling?
Let’s add some styles at the end of the file:
<style>
.posts__header {
text-align: center;
margin-bottom: var(--space-lg);
h1 {
font-size: var(--fs-3xl);
font-family: var(--ff-title);
}
}
.posts__list {
list-style: none;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
justify-items: center;
gap: var(--space-lg);
}
.post {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.post__image {
width: 100%;
height: auto;
object-fit: cover;
object-position: top;
aspect-ratio: 16/9;
border-radius: var(--border-radius);
}
.post__text {
display: flex;
flex-direction: column;
gap: var(--space-2xs);
}
.post__header {
display: flex;
flex-direction: column;
gap: 2px;
}
.post__date {
font-size: var(--fs-2xs);
opacity: 0.7;
}
.post__title {
text-decoration: none;
color: var(--text-color);
transition: color 0.3s ease-in-out;
&:hover {
color: var(--title-hover-color);
}
h2 {
font-size: var(--fs-lg);
font-family: var(--ff-title);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
line-height: 1.175;
}
}
.post__description {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
}
</style>And replace what we put in Layout with this:
Make sure to import:
---
+ import { Image } from 'astro:assets';<Layout>
<section class="container">
<div class="posts__header">
<h1>CurioVerso Blog</h1>
<p>All Posts</p>
</div>
<ul class="posts__list">
{
posts.map(post => (
<li>
<article class="post">
<a
href={`/posts/${post.slug}`}
>
<Image
src={post.image.url}
alt={post.image.name}
width={250}
height={100}
class:list={'post__image'}
loading="eager"
/>
</a>
<div class="post__text">
<div class="post__header">
<p class="post__date">
<time datetime="2025-03-05">{date(post.date)}</time>
</p>
<a
href={`/posts/${post.title
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')}`}
class="post__title"
>
<h2>{post.title}</h2>
</a>
</div>
<div>
<p class="post__description">{post.summary}</p>
</div>
</div>
</article>
</li>
))
}
</ul>
</section>
</Layout>Looks nice! 🙂 Ready to tackle the page that displays a single post?