This document explains how the blog works in this project, specifically:
/blog/blog/[slug]src/pages/blog/index.astrosrc/pages/blog/[slug].astroSupporting components used by these pages:
src/components/blog/BlogHero.astrosrc/components/blog/BlogFilterBar.astrosrc/components/blog/BlogFeaturedCard.astrosrc/components/blog/BlogGridCard.astrosrc/components/blog/BlogSidebar.astrosrc/components/blog/BlogRelatedPosts.astrosrc/components/blog/BlogProgressBar.astrosrc/components/blog/BlogScripts.astro/blog Works (index.astro)index.astro does:
getCollection("blog", ({ data }) => !data.draft)featuredPost = posts[0]remainingPosts = posts.slice(1)This means the newest non-draft post becomes the featured card, and all others go into the grid.
The page renders in this order:
BlogHeroBlogFilterBar (uses categories = ["All", "Startups", "SMEs", "AI", "Healthtech"])BlogFeaturedCard post={featuredPost}remainingPosts.map((post) => <BlogGridCard post={post} />)#no-results) for filtering fallbackBottomCTAWhatsAppFABnew Date(post.data.date).featuredPost would be undefined, so your card component should safely handle that case.BlogFilterBar; actual client-side filtering behavior usually depends on component logic/scripts./blog/[slug] Works ([slug].astro)[slug].astro uses getStaticPaths():
{
params: { slug: post.id },
props: { post }
}
So each post id becomes a route like /blog/<post.id>.
For each generated page:
const { post } = Astro.propsconst { Content, headings } = await render(post)
Content renders markdown bodyheadings powers table of contents/sidebar navigationwordCount from post.bodyrelatedPostsRelated posts are selected when:
p.id !== post.id (exclude current post)p.data.categories.some((c) => post.data.categories.includes(c))The page builds postSchema (BlogPosting) and passes it to Layout.
Includes:
headline, description, wordCountarticleSection from categoriesmainEntityOfPage)The single post page renders:
BlogProgressBar<img> for absolute/root images, <Image> for Astro asset paths)<Content />)BlogSidebar with headingsFAQ block when post.data.faq existsBlogRelatedPostsBottomCTAWhatsAppFABBlogScriptsflowchart TD
A[src/content/blog/*.md or .mdx] --> B[getCollection('blog')]
B --> C[/blog index.astro]
B --> D[getStaticPaths in /blog/[slug].astro]
C --> E[Featured + Grid cards]
D --> F[One static route per post.id]
F --> G[render(post) => Content + headings]
G --> H[Post page UI + sidebar + related + schema]
When you add a new non-draft post to the blog content collection:
getCollection("blog")./blog (often as featured if newest)./blog/<post.id>.If a post is not visible:
draft: false in frontmatter.date format is valid.post.id is what you expect for route URL.categories array exists for related-post/category features.If /blog/[slug] is not generated:
getCollection("blog", ({ data }) => !data.draft).