# A blog index, start to finish

> The worked example: a parent page, its children, a card written once, and the loop that lists them newest first without anybody maintaining it.
>
> From the MightyBuilder documentation: https://docs.mightybuilder.ai/dynamic/a-blog-index

A real one, start to finish. Three pieces: the articles, a card, and the loop.

## The shape

Your articles live under one address, and the page that lists them is an
ordinary page — see [Articles](/dynamic/articles).

```
Blog                    /blog          an ordinary page, with the loop on it
  A year of sourdough   /articles/a-year-of-sourdough
  Opening on Sundays    /articles/opening-on-sundays
```

The Blog page does not own them. Rename it, move it, add a second one, and the
articles stay where they are.

## The loop

On the `/blog` page:

```liquid
{% assign posts = collections.articles | sort: "date" | reverse %}
{% for post in posts limit: 6 %}
  <article>
    <a href="{{ post.url }}">
      <h2>{{ post.title }}</h2>
      <p>{{ post.excerpt }}</p>
    </a>
  </article>
{% else %}
  <p>No articles yet.</p>
{% endfor %}
```

Read it in order:

1. **`assign`** builds the list: every article on the site, sorted by date,
   newest first. Without the sort the list comes in the order you arrange the
   articles in, which is the right thing for a portfolio and the wrong one for
   a blog.
2. **`for`** walks it, at most six.
3. **`else`** is what shows when the list is empty — worth writing, because a
   brand new blog is empty and a bare heading with nothing under it looks
   broken.

## Assign first, always

The filters have to go on the `assign` line, not the `for` line. This is the one
trap in the whole language and it is covered on its own page:
[Sorting and filtering](/dynamic/sorting-and-filtering).

## The card, as a block

Six articles means six copies of the card design in one page. Move the card
into a **block** — a piece of markup kept as its own document — and the loop
renders that instead:

```liquid
{% assign posts = collections.articles | sort: "date" | reverse %}
{% for post in posts limit: 6 %}
  {% render "aB3x9kQ2mN01", post: post %}
{% endfor %}
```

`post: post` is the part that matters. It hands the block the article it is
currently on, so the block can be written once and used by the blog index and a
homepage strip without becoming two near-identical copies.

Inside the block, the thing handed in is called `post` because that is what the
call named it:

```liquid
<article>
  <a href="{{ post.url }}">
    {% if post.image %}
      <img src="{{ post.image.src }}" alt="{{ post.image.alt }}">
    {% endif %}
    <h3>{{ post.title }}</h3>
    <p>{{ post.excerpt }}</p>
  </a>
</article>
```

The `{% if post.image %}` guard is not optional politeness. An article with no
picture has `image` set to nothing at all, and without the guard you get an
empty `<img>` on every card that lacks one.

The block works for pages too. It reads `url`, `title`, `excerpt` and `image`,
which both kinds have — so the same card can list a section's child pages
without being written twice.

## You do not have to write any of this

Ask for it. "Make the blog index list the articles automatically, newest first,
six at a time" is a request the AI handles, and it writes the markup above.

This page exists so you can read what it wrote, change it, and know what is
possible to ask for.
