Blog System

Blog pages for Engine Finder, featuring engine problem articles, guides, and category filtering.

Directory Structure

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+)

Key Files

FilePurpose
src/content/blog/Blog post markdown files
src/lib/blog/normalizeCategory.jsShared category normalization utility
src/lib/constants.tsBLOG_PAGINATION_SIZE = 12
src/components/PostCard.astroBlog post card component
src/components/CategoryFilter.astroCategory filter UI
src/components/Paginator.astroPagination component
src/components/TableOfContents.astroAuto-generated table of contents

Category Normalization

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.

Pagination

URL Structure

URLHandler
/blogindex.astro
/blog/page/2page/[page].astro
/blog/category/audi-enginescategory/[category].astro
/blog/category/audi-engines/page/2category/[category]/page/[page].astro

Static Generation

All blog routes use export const prerender = true; for static generation.

getStaticPaths Pattern

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:

  1. draft !== true - Draft posts are excluded
  2. pubDate <= now - Future-dated posts are excluded

Blog Post Frontmatter

---
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"
---

Table of Contents Feature

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:

Troubleshooting

404 on Blog Post

404 on Category Pagination

ReferenceError: normalizeCategory is not defined

SEO & Social Media Sharing

Open Graph Meta Tags

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:

Testing Social Shares:

  1. Use Facebook Sharing Debugger
  2. Enter your blog post URL
  3. Click “Scrape Again” to refresh Facebook’s cache
  4. Verify the featured image, title, and description appear correctly

Layout Component

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} />

JSON-LD Structured Data

Blog posts include comprehensive JSON-LD structured data for enhanced SEO and rich snippets in search results.

Schemas Included:

  1. BlogPosting Schema - Article metadata, author, publisher, dates
  2. BreadcrumbList Schema - Navigation hierarchy (Home > Blog > Category > Post)

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:

Testing Structured Data:

  1. Use Google’s Rich Results Test
  2. Enter your blog post URL
  3. Verify BlogPosting and BreadcrumbList schemas are detected
  4. Check for validation errors or warnings

Schema Properties:

BlogPosting:

BreadcrumbList:

Callout Boxes

Blog posts support 5 types of styled callout boxes to display data attractively within markdown content.

Available Callout Types

TypeColorUse ForIcon
Did You KnowBlueFacts, statistics, reliability ratings💡
Pro TipGreenMaintenance advice, diagnostic tips
WarningOrangeSafety alerts, expensive mistakes⚠️
Forum InsightPurpleOwner experiences, community quotes💬
StatisticRedPercentage data, key numbersLarge number

Usage in Markdown

<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>

Features

Complete Documentation

See BLOG_CALLOUTS_GUIDE.md for:

Visual Examples

Open BLOG_CALLOUTS_EXAMPLE.html in browser to see all callouts rendered.

Implementation

Styles are in src/pages/blog/[...slug].astro (lines 488-612)