Now that we have a basic blog page, let's fill it with posts! First, we'll create individual post pages (dynamically), then we'll come back and add a post feed on the blog page with a reusable post card component.
Lesson #2 Concepts:
- Content modeling
- Rendering markdown content
- Next.js dynamic routes
- Rendering and annotating components
Add Post Model
As we did with the blog page, let's begin by adding a Post
model.
# .stackbit/models/Post.yaml
name: Post
layout: Post
fields:
- type: string
name: title
required: true
- type: date
name: date
required: true
- type: string
name: excerpt
default: Excerpt goes here ...
We've given the posts required title
and date
fields, along with an excerpt
field, which we'll use to render a preview of the post content.
Because we omitted the hideContent
option, there will be a content area to edit the main content of the page. We'll see this in action later in this lesson.
Configure Post Page
Like the BlogPost
model, we also want posts to be of the page
type, so let's add that configuration to stackbit.yaml
.
# stackbit.yaml
hello: world
# ...
contentModels:
# ...
Post:
isPage: true
urlPath: '/blog/{slug}'
newFilePath: 'blog/{slug}.md'
Note here that we are interpolating each post's slug
in the URL. We'll see this come to life as we create posts, which is the next step.
Add Post Content
Now you can add a new page of type Post
in Stackbit. You'll have to fill in the two required fields, along with the slug.
The value you add to the slug field here becomes the slug
value from your model configuration to define the URL for that page. For example, if you used my-first-post
as the slug, that means the content file will be created at content/pages/blog/my-first-post.md
and that the page itself is expected to be rendered at /blog/my-first-post
.
The post content file will look something like this:
---
title: My First Post
date: '2022-02-22'
excerpt: Excerpt goes here ...
layout: Post
---
Etiam facilisis lacus nec pretium lobortis. Praesent dapibus justo non efficitur efficitur. Nullam viverra justo arcu, eget egestas tortor pretium id. Sed imperdiet mattis eleifend. Vivamus suscipit et neque imperdiet venenatis ...
Notice that excerpt and the main content area are populated even though you didn't add any content for them. Stackbit works to provide sensible defaults to avoid breaking your site when a new page is added.
Add a few posts. After creating each post, you'll be taken to a 404 page, just as you were with the blog page. We'll resolve those errors in the next step by creating the post page component.
Post Page Component
Let's add the post page, keeping things simple as we did with the blog page. We'll add a title and parse the markdown to create the body content.
// pages/blog/[slug].js
import Markdown from 'markdown-to-jsx'
import { pageUrlPath } from '../../utils/page-utils'
import { pagesByLayout } from '../../utils/sourcebit-utils'
const allPosts = pagesByLayout('Post')
const PostPage = ({ post }) => {
return (
<div data-sb-object-id={post?.__metadata?.id}>
<h1 data-sb-field-path="title">{post.frontmatter.title}</h1>
<Markdown data-sb-field-path="markdown_content">{post.markdown}</Markdown>
</div>
)
}
export const getStaticProps = async ({ params }) => {
const currentPath = `/blog/${params.slug}`
const post = allPosts.find((page) => pageUrlPath(page) === currentPath)
return { props: { post } }
}
export const getStaticPaths = async () => {
return {
paths: allPosts.map((page) => pageUrlPath(page)),
fallback: false
}
}
export default PostPage
There are several items to make note of with this code:
- The annotation (
data-sb-field-path
) for the main body area is calledmarkdown_content
. This is a reserved field specific to the main content area of the source file. - We're using
markdown-to-jsx
to transform the markdown content to HTML. This is to keep the process simple. Ideally you'd do this work withingetStaticProps()
to keep your component code lighter (for better performance in production). getStaticPaths()
is using a helper functionpageUrlPath
to dynamically build the individual Next.js pages. Look inutils/page-utils.js
for this method. Learn more about dynamic routing with Next.js.
Because we already added the annotations, your Stackbit editor should refresh to show the page content, and it should now be visually editable!

Add Posts to Blog Page
With individual post pages in place, we can add a feed of these posts to the main blog page. We'll render a post card component, which will represent and link to an individual post.
Add Post Card Component
Our components in this project live in the components
directory. Add a PostCard
component.
// components/PostCard.jsx
import * as React from 'react'
import Link from 'next/link'
import Markdown from 'markdown-to-jsx'
import { pageUrlPath } from '../utils/page-utils'
export const PostCard = ({ post }) => {
return (
<Link href={pageUrlPath(post)}>
<a data-sb-object-id={post.__metadata.id}>
<h3 data-sb-field-path=".title">{post.frontmatter.title}</h3>
{post.frontmatter.excerpt && <Markdown data-sb-field-path=".excerpt">{post.frontmatter.excerpt}</Markdown>}
</a>
</Link>
)
}
Once again we're using the pageUrlPath()
helper to build the URL.
Also note that we've once again used the data-sb-object-id
annotation. This is because these cards will be rendered on pages that are not the post page. So we have to tell Stackbit where to store this information, which is in the individual post source file (e.g. content/pages/blog/my-first-post.md
).
Add Cards to Blog Page
With the component in place, we're ready to add the cards to the main blog page. (Comments describe the new lines of code.)
// pages/blog/index.jsx
// Import post card
import { PostCard } from '../../components/PostCard'
import { pagesByLayout } from '../../utils/sourcebit-utils'
const BlogPage = ({ page, posts }) => {
return (
<div data-sb-object-id={page?.__metadata?.id}>
<h1 data-sb-field-path="title">{page.frontmatter.title}</h1>
{/* Loop through posts and add post card for each one. */}
{(posts ?? []).map((post) => (
<PostCard key={post.__metadata.id} post={post} />
))}
</div>
)
}
export default BlogPage
export const getStaticProps = async () => {
const page = pagesByLayout('Blog')[0]
// Retrieve all posts and send to the page component as a prop, sorted by date.
const posts = pagesByLayout('Post').sort((a, b) => {
return new Date(b.frontmatter.date) - new Date(a.frontmatter.date)
})
return { props: { page, posts } }
}
Look back at your browser and you should see a list of posts. Try adding a few new posts and you should see them appear in the list, sorted by date.

Next, we'll take a look at creating a post feed component that can be reused on other pages.
Getting Started Lessons