# Blog Template Best Practices

**Last Updated:** 2026-01-10

Validated best practices for blog template development, cross-referenced with Ordio patterns and industry standards for SEO, performance, accessibility, and user experience.

## Overview

This document consolidates best practices for blog template development, validated against industry standards and cross-referenced with existing Ordio patterns from templates, product pages, and homepage.

## SEO Best Practices

### Meta Tags

**Title Tags**:

- **Length**: Max 60 characters (Google truncates at ~60)
- **Pattern**: `{Post Title} | {Category} | Ordio`
- **Include**: Year if relevant, primary keyword, brand name
- **Uniqueness**: Every page must have unique title
- **Example**: `Leitfaden zur Finanzbuchhaltung | Lexikon | Ordio`

**Meta Descriptions**:

- **Length**: 155-160 characters (optimal for SERP display)
- **Tone**: Du tone (informal "du")
- **Content**: Benefit-driven, include primary keyword naturally
- **Uniqueness**: Every page must have unique description
- **Example**: `Leitfaden zur Finanzbuchhaltung: Hier erfährst du alles, was du zum Thema wissen musst. Behalte deine Finanzen im Griff. Jetzt reinlesen!`

**Canonical URLs**:

- **Format**: Absolute URLs, HTTPS
- **Pattern**: Match actual page URL exactly
- **Purpose**: Prevent duplicate content issues
- **Example**: `https://www.ordio.com/insights/lexikon/leitfaden-zur-finanzbuchhaltung/`

**Open Graph Tags**:

- **Required**: `og:title`, `og:description`, `og:url`, `og:type`, `og:image`
- **Type**: `article` for posts, `website` for index
- **Image**: 1200x630px recommended, WebP format
- **Locale**: `de_DE` for German content

**Twitter Card Tags**:

- **Card Type**: `summary_large_image`
- **Labels**: "Written by", "Est. reading time"
- **Image**: Same as OG image

**Robots Meta**:

- **Index Pages**: `index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1`
- **Category Archives**: `noindex, follow` (current WordPress pattern)

### Heading Hierarchy

**Critical Requirements**:

- **Post Title**: Displayed as H1 in `PostHeader.php` component
- **Content Headings**: Must start with H2 (never H1, H3, or H4 as first heading)
- **Sequential Hierarchy**: H2 → H3 → H4 (no skipping levels)
- **No Backwards Hierarchy**: Never go backwards (e.g., H3 → H2 is invalid)
- **Semantic Structure**: Headings should reflect content structure, not just styling

**Best Practices**:

- **First Heading**: Always H2 (post title is H1)
- **Main Sections**: Use H2 for top-level sections
- **Subsections**: Use H3 for subsections under H2
- **Sub-subsections**: Use H4 for subsections under H3
- **Maximum Depth**: Generally avoid going deeper than H4
- **Consistency**: Maintain consistent hierarchy throughout post

**Common Issues to Avoid**:

- ❌ Starting content with H3 or H4 (should be H2)
- ❌ Backwards hierarchy (H3 → H2)
- ❌ Skipped levels (H2 → H4 without H3)
- ❌ All headings at same level when they should be hierarchical

**Validation**:

- Use `v2/scripts/blog/analyze-heading-hierarchy.py` to analyze all posts
- Use `v2/scripts/blog/validate-heading-fixes.py` to validate fixes
- Fix issues with `v2/scripts/blog/fix-heading-hierarchy.py`

**See**: `docs/content/blog/HEADING_HIERARCHY_GUIDE.md` for complete guidelines

- **Single Posts**: `index, follow, max-image-preview:large...`

### Schema Markup

**Required Schemas** (All Pages):

- **Organization**: Company information, logo, social links
- **WebSite**: Site-wide search action, publisher reference
- **WebPage**: Page-specific metadata, breadcrumb reference

**Page-Specific Schemas**:

**Index Page**:

- WebPage schema
- BreadcrumbList schema

**Category Archives**:

- CollectionPage schema
- ImageObject schema (primary image)
- BreadcrumbList schema

**Single Posts**:

- Article schema (with author Person reference)
- ImageObject schema (featured image)
- BreadcrumbList schema
- Person schema (author)

**Schema Best Practices**:

- Use JSON-LD format (preferred by Google)
- Include all required fields for schema type
- Use absolute URLs for all references
- Validate with Google Rich Results Test
- Ensure dates are ISO 8601 format
- Include `@id` for cross-referencing

### Heading Hierarchy

**Best Practices**:

- **H1**: One per page (page/post title)
- **H2**: Main sections (5-10 for most; 8-12 for main topics)
- **H3**: Subsections (as needed)
- **H4-H6**: Nested content (use sparingly)

**Structure Example**:

```
H1: Post Title
  H2: Introduction Section
    H3: Subsection
  H2: Main Content Section
    H3: Subsection
  H2: Conclusion Section
```

**Accessibility**: Proper heading hierarchy improves screen reader navigation and SEO.

### URL Structure

**Current WordPress Pattern**:

- Index: `/insights/`
- Category: `/insights/{category}/`
- Post: `/insights/{category}/{slug}/`

**Best Practices**:

- Maintain current URL structure (SEO-friendly)
- Use descriptive slugs (keywords, readable)
- Avoid deep nesting (max 2-3 levels)
- Use hyphens, not underscores
- Lowercase URLs
- No trailing slashes (or consistent)

**Migration**: Preserve exact URLs to maintain SEO equity.

## Performance Best Practices

### Image Optimization

**Format**:

- **WebP**: Primary format (smaller file size, better quality)
- **Fallback**: JPEG/PNG for older browsers
- **SVG**: For icons and simple graphics

**Responsive Images**:

```html
<img
  src="/insights/bilder/image-640w.webp"
  srcset="
    /insights/bilder/image-640w.webp   640w,
    /insights/bilder/image-1024w.webp 1024w,
    /insights/bilder/image-1280w.webp 1280w
  "
  sizes="(max-width: 640px) 640px,
         (max-width: 1024px) 1024px,
         1280px"
  alt="Descriptive alt text"
  width="1280"
  height="720"
  loading="lazy"
/>
```

**Hero Images**:

- Use `fetchpriority="high"` for LCP images
- Preload critical hero images
- Optimize file size (compress, WebP)

**Content Images**:

- Use `loading="lazy"` for below-fold images
- Explicit width/height attributes (prevents CLS)
- Responsive srcset for multiple sizes

**Performance Targets**:

- **LCP**: < 2.5s (Largest Contentful Paint)
- **FID**: < 100ms (First Input Delay)
- **CLS**: < 0.1 (Cumulative Layout Shift)
- **PageSpeed Score**: > 90 (mobile and desktop)

### CSS Optimization

**Loading Strategy**:

- Critical CSS inline or preloaded
- Non-critical CSS deferred (media="print" trick)
- Minified CSS files (.min.css)
- Cache busting with filemtime()

**Pattern**:

```php
<link rel="stylesheet" href="/v2/css/blog-base.min.css?v=<?php echo filemtime(__DIR__ . '/../css/blog-base.min.css'); ?>" media="print" onload="this.media='all'">
    <link rel="stylesheet" href="/v2/css/blog-post.min.css?v=<?php echo filemtime(__DIR__ . '/../css/blog-post.min.css'); ?>" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="/v2/css/blog-base.min.css"></noscript>
    <noscript><link rel="stylesheet" href="/v2/css/blog-post.min.css"></noscript>
```

**Best Practices**:

- Use Tailwind CSS utility classes (existing pattern)
- Minimize custom CSS
- Remove unused CSS (tree shaking)
- Use CSS containment for performance

### JavaScript Optimization

**Loading Strategy**:

- Defer non-critical scripts
- Async for independent scripts
- Minified JS files (.min.js)
- No blocking scripts above fold

**Pattern**:

```php
<script src="/v2/js/blog.min.js?v=<?php echo filemtime(__DIR__ . '/../js/blog.min.js'); ?>" defer></script>
```

**Best Practices**:

- Use Alpine.js for interactivity (existing pattern)
- Minimize JavaScript bundle size
- Code splitting for large apps
- No console.log/error/warn statements (use structured logger)

### Caching Strategy

**Static Assets**:

- Long cache headers (1 year)
- Versioned filenames (cache busting)
- CDN caching

**HTML Pages**:

- Short cache (1 hour)
- Revalidation on content update
- CDN edge caching

**Implementation**:

- Use `filemtime()` for cache busting
- Set appropriate cache headers
- Use CDN for static assets

## Accessibility Best Practices

### Semantic HTML

**Required Elements**:

- `<article>` for blog posts
- `<nav>` for navigation
- `<main>` for main content
- `<header>` for post header
- `<section>` for content sections
- `<footer>` for footer
- `<time>` for dates

**Benefits**: Improves screen reader navigation, SEO, and code maintainability.

### ARIA Attributes

**Navigation**:

- `aria-label` for navigation menus
- `aria-current="page"` for current page indicators
- `aria-live` for dynamic content updates

**Images**:

- Descriptive `alt` attributes (required)
- `aria-hidden="true"` for decorative images

**Forms**:

- `aria-label` or `aria-labelledby` for form inputs
- `aria-describedby` for help text
- `aria-invalid` for validation errors

**Landmarks**:

- Use semantic HTML5 elements (preferred)
- Add ARIA landmarks if needed

### Keyboard Navigation

**Requirements**:

- All interactive elements keyboard accessible
- Visible focus states (outline, highlight)
- Logical tab order
- Skip links for main content

**Focus States**:

```css
a:focus,
button:focus {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}
```

**Tab Order**: Ensure logical flow through page elements.

### Color Contrast

**WCAG AA Requirements**:

- **Normal Text**: 4.5:1 contrast ratio
- **Large Text**: 3:1 contrast ratio (18pt+ or 14pt+ bold)

**Testing**: Use tools like WebAIM Contrast Checker or axe DevTools.

**Best Practices**:

- Test all text/background combinations
- Ensure sufficient contrast for links
- Consider colorblind users (don't rely on color alone)

### Screen Reader Support

**Requirements**:

- Proper heading hierarchy
- Descriptive link text (not "click here")
- Alt text for all images
- Form labels and error messages
- Skip links for navigation

**Testing**: Test with screen readers (NVDA, JAWS, VoiceOver).

## User Experience Best Practices

### Navigation

**Category Navigation**:

- Clear category labels
- Post counts per category
- Active state highlighting
- Responsive design (mobile-friendly)

**Breadcrumbs**:

- Show navigation path
- Link to parent pages
- Schema markup for SEO
- Responsive display

**Pagination**:

- Previous/Next links
- Page numbers (if many pages)
- Current page highlighting
- Accessible labels

### Content Layout

**Readability**:

- Max content width: 1280px (7xl)
- Comfortable line length (50-75 characters)
- Adequate line height (1.5-1.75)
- Sufficient whitespace

**Typography**:

- Readable font size (16px base)
- Clear font hierarchy
- Sufficient contrast
- Responsive font sizes

**Content Structure**:

- Short paragraphs (2-3 sentences)
- Bullet points for lists
- Headings for sections
- Visual breaks (images, spacing)

### Post Cards

**Required Elements**:

- Featured image (if available)
- Category badge
- Title (linked)
- Excerpt/preview
- Publication date
- Hover effects

**Layout**:

- Responsive grid (1-3 columns)
- Consistent card sizing
- Clear visual hierarchy
- Touch-friendly targets (44x44px minimum)

### Related Posts

**Selection Criteria**:

- Semantic similarity
- Shared topics/clusters
- Same category (optional)
- Limit: 14 posts (current pattern)

**Display**:

- Post card component reuse
- Responsive grid
- Clear heading ("Ähnliche Artikel")
- Accessible links

## Cross-Reference with Ordio Patterns

### Template Patterns

**From `templates_template.php`**:

- Configuration loading pattern
- Dynamic meta tag generation
- Schema markup generation
- Component inclusion pattern
- Alpine.js integration

**Apply to Blog**:

- Use similar config loading pattern
- Create `blog-meta-generator.php`
- Create `blog-schema-generator.php`
- Use component-based architecture
- Integrate Alpine.js for interactivity

### Product Page Patterns

**From `product_schichtplan.php`**:

- Hero section structure
- Content section patterns
- CTA integration
- Schema markup patterns

**Apply to Blog**:

- Post header similar to hero
- Content sections with proper spacing
- Related posts as CTA alternative
- Article schema similar to product schema

### Homepage Patterns

**From `landingpage.php`**:

- Performance optimization (LCP, CLS)
- Image optimization patterns
- CSS/JS loading patterns
- Mobile optimization

**Apply to Blog**:

- Optimize hero images for LCP
- Use same CSS/JS loading patterns
- Apply mobile-first responsive design
- Follow performance targets

### Shared Patterns

**From `shared-patterns.mdc`**:

- Universal validation checklist
- Copy guidelines (du tone)
- Meta tag patterns
- Schema patterns
- Performance baseline
- Mobile optimization

**Apply to Blog**:

- Follow all validation checklists
- Use du tone throughout
- Follow meta tag patterns
- Use schema patterns
- Meet performance targets
- Optimize for mobile

## Component Architecture

### Component Structure

**Recommended**:

```
v2/components/blog/
├── PostCard.php
├── PostHeader.php
├── PostContent.php
├── RelatedPosts.php
├── Pagination.php
├── CategoryNav.php
├── Breadcrumbs.php
└── TopicHubHero.php
```

**Pattern** (from templates):

- Props set before include
- Conditional rendering
- Reusable across templates

### Helper Functions

**Recommended**:

```
v2/config/blog-template-helpers.php
v2/config/blog-meta-generator.php
v2/config/blog-schema-generator.php
```

**Pattern** (from templates):

- Centralized functions
- Dynamic generation
- Override support

## Data Structure

### Post Data Format

**Recommended JSON Structure**:

```json
{
  "slug": "leitfaden-zur-finanzbuchhaltung",
  "title": "Leitfaden zur Finanzbuchhaltung",
  "category": "lexikon",
  "content": "<html>...</html>",
  "excerpt": "Short excerpt...",
  "featured_image": "/insights/bilder/image.webp",
  "publication_date": "2023-09-01T10:40:44+00:00",
  "modified_date": "2025-03-31T18:58:44+00:00",
  "author": {
    "name": "Emma",
    "url": "/author/emma/"
  },
  "meta": {
    "title": "Leitfaden zur Finanzbuchhaltung | Lexikon | Ordio",
    "description": "Leitfaden zur Finanzbuchhaltung: Hier erfährst du alles...",
    "keywords": ["Arbeitgeber", "Buchhaltung", "Gesetz"]
  },
  "topics": ["personalverwaltung"],
  "clusters": {
    "primary": "personalverwaltung",
    "secondary": ["compliance"]
  },
  "related_posts": [
    {
      "slug": "related-post-slug",
      "title": "Related Post Title",
      "url": "/insights/lexikon/related-post-slug/"
    }
  ],
  "reading_time": 6
}
```

### Category Data Format

**Recommended JSON Structure**:

```json
{
  "slug": "lexikon",
  "name": "Lexikon",
  "description": "Fachbegriffe rund um Arbeitsrecht...",
  "post_count": 22,
  "url": "/insights/lexikon/"
}
```

## Migration Considerations

### URL Preservation

**Critical**: Maintain exact URLs to preserve SEO equity.

**Pattern**:

- Current: `/insights/{category}/{slug}/`
- Migrated: `/insights/{category}/{slug}/` (same)

**Redirects**: Only if URL structure changes (not recommended).

### Content Migration

**Requirements**:

- Preserve all HTML content
- Maintain internal links
- Update image paths
- Preserve metadata

**Processing**:

- HTML sanitization (allowlist safe tags)
- Image optimization (WebP conversion)
- Link processing (internal/external)
- Metadata extraction

### Performance Improvement

**Opportunities**:

- Static generation (faster than WordPress)
- Image optimization (WebP, responsive)
- Code optimization (minified, cached)
- CDN caching

**Targets**:

- LCP < 2.5s (improve from WordPress)
- CLS < 0.1 (prevent layout shifts)
- PageSpeed > 90 (mobile and desktop)

## Testing Requirements

### Functional Testing

**Checklist**:

- [ ] All links work correctly
- [ ] Pagination functions properly
- [ ] Category filters work
- [ ] Related posts display correctly
- [ ] Forms submit correctly
- [ ] Search works (if implemented)

### Performance Testing

**Tools**:

- Google PageSpeed Insights
- WebPageTest
- Chrome DevTools Lighthouse

**Metrics**:

- LCP < 2.5s
- FID < 100ms
- CLS < 0.1
- PageSpeed Score > 90

### SEO Testing

**Tools**:

- Google Rich Results Test
- Schema.org Validator
- Screaming Frog SEO Spider

**Checklist**:

- [ ] All schema validates
- [ ] Meta tags complete
- [ ] Canonical URLs correct
- [ ] Sitemap generated
- [ ] Robots.txt correct

### Accessibility Testing

**Tools**:

- axe DevTools
- WAVE (Web Accessibility Evaluation Tool)
- Screen readers (NVDA, JAWS, VoiceOver)

**Checklist**:

- [ ] Proper heading hierarchy
- [ ] Alt text for all images
- [ ] Keyboard navigation works
- [ ] Color contrast meets WCAG AA
- [ ] ARIA attributes correct
- [ ] Focus states visible

### Cross-Browser Testing

**Browsers**:

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)

**Checklist**:

- [ ] Layout consistent
- [ ] Functionality works
- [ ] Performance acceptable
- [ ] No console errors

## Documentation Requirements

### Template Documentation

**Required**:

- Template structure documentation
- Component API documentation
- Helper function documentation
- Data structure documentation

**Format**: Markdown files in `docs/content/blog/`

### Code Documentation

**Required**:

- Inline comments for complex logic
- Function documentation (PHPDoc)
- Component prop documentation
- Usage examples

**Format**: Comments in PHP files

### Cursor Rules

**Required**:

- Blog template patterns rule file
- SEO requirements
- Performance requirements
- Accessibility requirements

**Format**: `.cursor/rules/blog-templates.mdc`

## Last Updated Date Display

### When to Update `modified_date`

The `modified_date` field should be updated whenever:

- Post content is edited (text, images, links)
- SEO metadata is updated
- Post structure is modified (headings, formatting)
- Any substantive changes are made to the post

### Display Logic

The "last updated" date is displayed automatically when:

- `modified_date` is 7+ days after `publication_date`
- Post is displayed in default variant (not compact)
- Both dates are valid ISO 8601 format

### Format

- **Header**: "Zuletzt aktualisiert: [date]" with refresh icon
- **Card**: Badge below publication date with refresh icon
- **Language**: German month names (Januar, Februar, etc.)

### Best Practices

1. Always update `modified_date` when editing posts
2. Use ISO 8601 format: `Y-m-d\TH:i:sP` (e.g., `2026-01-10T21:26:17+00:00`)
3. The 7-day threshold prevents clutter for recently published posts
4. Schema markup automatically includes `dateModified` for SEO

## Image Lightbox Best Practices

### Purpose

Provide better user experience for viewing images in detail without leaving the page. All images in blog post content are automatically clickable and open in a full-screen lightbox modal.

### Implementation

- **Component**: `BlogImageLightbox.php` - Reusable lightbox modal component
- **State Management**: Alpine.js `x-data` on `<body>` element in `post.php`
- **Image Wrapping**: `PostContent.php` automatically wraps images in clickable containers
- **JavaScript Utilities**: `blog-image-lightbox.js` provides srcset parsing and helper functions

### Image Optimization

- **Format**: Use WebP format for all images
- **Responsive Images**: Include `srcset` attributes with multiple sizes
- **Lightbox Display**: Automatically uses largest available image from `srcset`
- **File Size**: Ensure images are optimized for web (compressed, appropriate dimensions)
- **Storage**: Images stored in `/v2/img/insights/` and served via `/insights/bilder/`

### Accessibility Considerations

- **Alt Text**: Always include descriptive `alt` text for images
- **Keyboard Navigation**:
  - Escape key closes modal
  - Tab key navigates to close button
  - Enter/Space activates image trigger
- **Screen Reader Support**:
  - Modal announces opening with `aria-label`
  - Image alt text is announced
  - Close button has descriptive label
- **Focus Management**:
  - Focus moves to close button on open
  - Focus returns to trigger image on close
  - Body scroll is locked when modal is open

### Performance

- **Lazy Loading**: Images load on-demand (not preloaded)
- **Optimization**: Use existing optimized images from `/insights/bilder/`
- **Animations**: CSS transforms for GPU-accelerated animations
- **Preloading**: Optional preloading utility available in `blog-image-lightbox.js` for critical images

### Edge Cases

- **Images in Links**: Skipped - preserves existing link behavior
- **Images in Figures**: Wrapped correctly while preserving `<figure>` and `<figcaption>` structure
- **Images in Lists**: Wrapped correctly while preserving list structure
- **Responsive Images**: Uses largest image from `srcset` attribute for lightbox display
- **Missing Alt Text**: Uses fallback text ("Bild" or filename)

### CSS Classes

- `.blog-image-lightbox-modal` - Main modal container (dark backdrop)
- `.blog-image-lightbox-close` - Close button (top-right, X icon)
- `.blog-image-lightbox-container` - Image container wrapper
- `.blog-image-lightbox-image` - Lightbox image (responsive sizing)
- `.blog-image-lightbox-trigger` - Clickable image wrapper in post content

### Browser Support

- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile Safari (iOS)
- Chrome Mobile (Android)

## Related Documentation

- [Template Patterns Analysis](TEMPLATE_PATTERNS_ANALYSIS.md) - Existing template patterns
- [Frontend Patterns Extraction](FRONTEND_PATTERNS_EXTRACTION.md) - Extracted component patterns
- [Migration Template Requirements](MIGRATION_TEMPLATE_REQUIREMENTS.md) - Template specifications
- [Migration Architecture](MIGRATION_ARCHITECTURE.md) - Technical architecture
- [SEO Structure](FRONTEND_SEO_STRUCTURE.md) - SEO patterns
