Blog pages for Engine Finder, featuring engine problem articles, guides, and category filtering.
src/pages/blog/
├── index.astro # Main blog listing (page 1)
├── page/[page].astro # Blog pagination (pages 2+)
├── [...slug].astro # Individual blog post pages
└── category/
├── [category].astro # Category listing (page 1)
└── [category]/page/[page].astro # Category pagination (pages 2+)
| File | Purpose |
|---|---|
src/content/blog/ | Blog post markdown files |
src/lib/blog/normalizeCategory.js | Shared category normalization utility |
src/lib/constants.ts | BLOG_PAGINATION_SIZE = 12 |
src/components/PostCard.astro | Blog post card component |
src/components/CategoryFilter.astro | Category filter UI |
src/components/Paginator.astro | Pagination component |
src/components/TableOfContents.astro | Auto-generated table of contents |
Categories are normalized to consolidate brand variants (e.g., “Mercedes”, “Mercedes-Benz” → “Mercedes-Benz Engines”).
Shared utility: src/lib/blog/normalizeCategory.js
import { normalizeCategory } from '@/lib/blog/normalizeCategory';
This function is used by both [category].astro and [category]/page/[page].astro to ensure consistent category handling.
BLOG_PAGINATION_SIZE)index.astro or [category].astro)page/[page].astro)| URL | Handler |
|---|---|
/blog | index.astro |
/blog/page/2 | page/[page].astro |
/blog/category/audi-engines | category/[category].astro |
/blog/category/audi-engines/page/2 | category/[category]/page/[page].astro |
All blog routes use export const prerender = true; for static generation.
export async function getStaticPaths() {
const allPosts = await getCollection('blog', ({ data }) => {
const now = new Date();
return data.draft !== true && new Date(data.pubDate) <= now;
});
// ... generate paths
}
Important: Posts are filtered by:
draft !== true - Draft posts are excludedpubDate <= now - Future-dated posts are excluded---
title: "Article Title"
description: "Article description"
pubDate: 2025-01-15
heroImage: "/images/blog/image.jpg"
categories: ["Engine Problems", "Audi Engines", "Maintenance"]
author: "Engine Finder"
featured: true
draft: false
slug: "problems/article-slug"
---
Every blog post automatically displays a Table of Contents that:
Component: src/components/TableOfContents.astro
Implementation:
const { Content, headings } = await post.render();
// ...
<TableOfContents headings={headings} />
Styling:
draft is false (not true)pubDate is not in the future[category]/page/[page].astro existsnormalizeCategory is imported from shared utilityexport const prerender = true; is setimport { normalizeCategory } from '@/lib/blog/normalizeCategory';Blog posts are configured with proper Open Graph meta tags for social media sharing (Facebook, LinkedIn, etc.).
Implementation: src/pages/blog/[...slug].astro
<Layout
title={post.data.title}
description={post.data.description}
canonical={`https://www.enginefinder.co.za/blog/${post.slug}/`}
ogImage={post.data.heroImage ? `https://www.enginefinder.co.za${post.data.heroImage}` : 'https://www.enginefinder.co.za/images/mechanic-holding-engine.jpg'}
ogType="article"
publishedTime={post.data.pubDate.toISOString()}
modifiedTime={post.data.updatedDate ? post.data.updatedDate.toISOString() : undefined}
author="Craig Sandeman"
/>
Key Features:
heroImage) is automatically used as the Open Graph imageTesting Social Shares:
The Layout.astro component handles all Open Graph and Twitter Card meta tags:
<!-- Open Graph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:type" content={ogType} />
<meta property="article:published_time" content={publishedTime} />
<meta property="article:author" content={author} />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImage} />
Blog posts include comprehensive JSON-LD structured data for enhanced SEO and rich snippets in search results.
Schemas Included:
Implementation: src/pages/blog/[...slug].astro
<!-- JSON-LD Structured Data for BlogPosting -->
<script type="application/ld+json" set:html={JSON.stringify(blogPostingSchema)} />
<!-- JSON-LD Structured Data for BreadcrumbList -->
<script type="application/ld+json" set:html={JSON.stringify(breadcrumbSchema)} />
Key Features:
set:html directiveupdatedDate)Testing Structured Data:
Schema Properties:
BlogPosting:
headline - Post titledescription - Post descriptionimage - Featured image URLdatePublished - Publication date (ISO 8601)dateModified - Last updated date (falls back to pubDate if not set)author - Organization entity (Engine Finder)publisher - Organization with logomainEntityOfPage - Canonical URLBreadcrumbList:
Blog posts support 5 types of styled callout boxes to display data attractively within markdown content.
| Type | Color | Use For | Icon |
|---|---|---|---|
| Did You Know | Blue | Facts, statistics, reliability ratings | 💡 |
| Pro Tip | Green | Maintenance advice, diagnostic tips | ✅ |
| Warning | Orange | Safety alerts, expensive mistakes | ⚠️ |
| Forum Insight | Purple | Owner experiences, community quotes | 💬 |
| Statistic | Red | Percentage data, key numbers | Large number |
<div class="callout callout-did-you-know">
<div class="callout-icon">💡</div>
<div class="callout-content">
<strong>Did You Know?</strong>
<p>The BMW N52 has a 73% reliability rating.</p>
<cite>Source: Consumer Reports 2023</cite>
</div>
</div>
See BLOG_CALLOUTS_GUIDE.md for:
Open BLOG_CALLOUTS_EXAMPLE.html in browser to see all callouts rendered.
Styles are in src/pages/blog/[...slug].astro (lines 488-612)
src/content/config.tssrc/layouts/Layout.astroBLOG_CALLOUTS_GUIDE.md