# SEO Implementation for Product Updates Pages


**Last Updated:** 2025-11-20

## Overview

This document outlines the SEO implementation for all product updates-related pages, including the main page, month pages, and individual feature pages.

## Page Types

### 1. Main Product Updates Page (`/produkt-updates`)
- **File**: `v2/pages/produkt_updates.php`
- **Schema Types**: CollectionPage, WebPage, BreadcrumbList
- **OG Type**: `website`

### 2. Month Pages (`/produkt-updates/[month-slug]`)
- **File**: `v2/pages/produkt_updates_month.php`
- **Schema Types**: BlogPosting, WebPage, BreadcrumbList
- **OG Type**: `article`

### 3. Feature Pages (`/produkt-updates/[feature-slug]`)
- **File**: `v2/pages/post.php`
- **Schema Types**: BlogPosting, WebPage, BreadcrumbList
- **OG Type**: `article`

## Schema.org Implementation

### @graph Pattern

All pages use the `@graph` pattern to include multiple schema types in a single JSON-LD block:

```json
{
    "@context": "https://schema.org",
    "@graph": [
        {
            "@type": "CollectionPage|BlogPosting",
            "@id": "https://www.ordio.com/[url]#[type]",
            ...
        },
        {
            "@type": "WebPage",
            "@id": "https://www.ordio.com/[url]#webpage",
            ...
        },
        {
            "@type": "BreadcrumbList",
            "@id": "https://www.ordio.com/[url]#breadcrumb",
            ...
        }
    ]
}
```

### Organization References

All pages reference the Organization schema using `@id`:

```json
{
    "@id": "https://www.ordio.com/#organization"
}
```

This is used in:
- `author` field (BlogPosting)
- `publisher` field (BlogPosting)
- `about` field (WebPage, CollectionPage)

### Website References

All pages reference the Website schema:

```json
{
    "@id": "https://www.ordio.com/#website"
}
```

This is used in:
- `isPartOf` field (WebPage, CollectionPage)

### Date Formats

All dates use ISO 8601 format with timezone:

- **datePublished**: `Y-m-d\TH:i:s\Z` (e.g., `2025-11-01T00:00:00Z`)
- **dateModified**: `Y-m-d\TH:i:s\Z` (e.g., `2025-11-17T01:07:15Z`)

### Keywords Format

Keywords are stored as arrays (not strings):

```json
{
    "keywords": [
        "Feature Title",
        "Ordio",
        "Produkt-Updates"
    ]
}
```

### Image References

- Featured images use absolute URLs: `https://www.ordio.com/[path]`
- Fallback to logo reference: `@id: "https://www.ordio.com/#/schema/logo/image/"`
- Images are included in both BlogPosting and WebPage schemas

## Meta Tags

### Title Tags

- **Optimal Length**: 50-60 characters
- **Format**: `[Page Title] | Produkt-Updates`
- **Examples**:
  - Main: `Ordio Produkt-Updates | Produkt-Updates` (45 chars)
  - Month: `Ordio Updates November 2025 | Produkt-Updates` (52 chars)
  - Feature: `[Feature Title] | Ordio Produkt-Updates` (varies)

### Meta Descriptions

- **Optimal Length**: 150-160 characters
- **Format**: Descriptive text summarizing page content
- **Includes**: Primary keywords, call-to-action where appropriate

### Open Graph Tags

All pages include:

```html
<meta property="og:type" content="website|article">
<meta property="og:title" content="...">
<meta property="og:description" content="...">
<meta property="og:url" content="...">
<meta property="og:locale" content="de_DE">
<meta property="og:image" content="...">
<meta property="og:image:alt" content="...">
```

**Article-specific tags** (month and feature pages):

```html
<meta property="article:published_time" content="...">
<meta property="article:modified_time" content="...">
<meta property="article:author" content="Ordio GmbH">
<meta property="article:section" content="Produkt-Updates">
<meta property="article:tag" content="..."> <!-- if tag exists -->
```

### Twitter Card Tags

```html
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="...">
<meta name="twitter:description" content="...">
<meta name="twitter:image" content="...">
```

## Canonical URLs

- All canonical URLs are **absolute**: `https://www.ordio.com/[path]`
- Canonical URL **matches** `og:url` exactly
- No trailing slashes

## BreadcrumbList Schema

All pages include BreadcrumbList with proper hierarchy:

1. Home → `https://www.ordio.com`
2. Produkt-Updates → `https://www.ordio.com/produkt-updates`
3. Month (if applicable) → `https://www.ordio.com/produkt-updates/[month-slug]`
4. Feature (if applicable) → `https://www.ordio.com/produkt-updates/[feature-slug]`

## Image Optimization

- All featured images have descriptive `alt` attributes
- Images use WebP format with fallbacks
- Featured images are used for Open Graph images when available
- Fallback to logo when no featured image exists

## Validation Checklist

### Schema Validation
- [ ] Test with Google Rich Results Test: https://search.google.com/test/rich-results
- [ ] Test with Schema.org Validator: https://validator.schema.org/

### Open Graph Validation
- [ ] Test with Facebook Debugger: https://developers.facebook.com/tools/debug/
- [ ] Verify og:image dimensions (1200x630px recommended)

### Twitter Card Validation
- [ ] Test with Twitter Card Validator: https://cards-dev.twitter.com/validator

### Cross-Page Consistency
- ✅ Organization references use consistent `@id` pattern
- ✅ Date formats use consistent ISO 8601 format
- ✅ URL structures are consistent
- ✅ Canonical URLs are absolute and match og:url

## Best Practices

1. **Always use @graph pattern** for multiple schema types
2. **Reference Organization and Website** using `@id` pattern (not inline objects)
3. **Use ISO 8601 dates** with timezone for all date fields
4. **Keywords as arrays**, not strings
5. **Absolute URLs** for all external references
6. **Descriptive alt text** for all images
7. **Consistent date formats** across all pages
8. **Canonical URLs match og:url** exactly

## LLMS File Inclusion

### Overview

Product updates pages are manually reviewed and included in `llms.txt` and `llms-full.txt` files for AI/LLM crawler consumption. **CRITICAL**: This is a manual review process - each page must be individually reviewed for optimal SEO/GEO/AEO setup before adding to LLMS files.

**Process:** See `PRODUCT_UPDATES_LLMS_PROCESS.md` for complete manual review workflow.

**Note:** Automated generation (`v2/api/generate-llms.php`) is NOT used in production due to file write permissions. All updates are manual.

### File Differences

**`llms.txt` (Essentials):**
- Includes 3 most recent months
- Includes up to 3 features per month
- Uses shorter descriptions (100 characters)
- Uses `description` field for features
- Focuses on most important metadata

**`llms-full.txt` (Comprehensive):**
- Includes all months
- Includes all features
- Uses longer descriptions (200 characters)
- Uses `page_content` field for features when available (richer content)
- Includes all metadata (platforms, tags, dates)

### Month Page Entries

**Format:**
```
> [UPDATE: {Month Title} | {Feature Count} Features | {Platforms}] {Month Name} Updates - {Intro Text} (Action) für {Month Name} (Context) mit {Feature Count} neuen Features (Advantage) und {Improvement Count} Verbesserungen (Outcome)
https://www.ordio.com/produkt-updates/{month-slug}
```

**Title Extraction:**
- Uses `page_title` field if available (with blue markup syntax removed)
- Falls back to `month` field if `page_title` is empty
- Blue markup syntax (`*text*` and `**text**`) is automatically stripped

**Intro Text Extraction:**
- Extracts plain text from `intro_text` field (strips HTML tags)
- Truncates to 120 chars for `llms.txt`, 200 chars for `llms-full.txt`
- Truncation happens at word boundaries

**Platform Aggregation:**
- Aggregates platforms from all features in the month
- Formats as "Desktop & Mobile" or "Mobile" (single platform)
- Only included if at least one feature has platform information

### Feature Page Entries

**Format:**
```
> [FEATURE: {Title} | {Tag} | {Platforms}] {Title} - {Description} (Action) von Ordio (Context) mit {Tag-specific Advantage} (Advantage) für bessere Produktivität (Outcome)
https://www.ordio.com/produkt-updates/{feature-slug}
```

**Description Extraction:**
- For `llms.txt`: Uses `description` field, truncated to 100 characters
- For `llms-full.txt`: Uses `page_content` field if available, otherwise `description`, truncated to 200 characters
- HTML tags are stripped, HTML entities are decoded
- Truncation happens at word boundaries
- Plain text is cleaned (multiple spaces/newlines collapsed)

**Tag Information:**
- Tag labels are formatted using `getTagLabel()` function
- Tag-specific advantage phrasing:
  - "Neues Feature" → "mit einem neuen Feature"
  - "Verbesserung" → "mit Verbesserungen"
  - "Bugfix" → "mit Bugfixes"
  - Other tags → "mit {Tag Label}"

**Platform Information:**
- Formats array `['desktop', 'mobile']` as "Desktop & Mobile"
- Single platform formatted as "Desktop" or "Mobile"
- Only included if feature has platform information

**Sorting:**
- Features are sorted by `published_date` within each month (newest first)
- Features without `published_date` appear last

### Helper Functions

**`extractPlainTextFromHtml($html, $maxLength = null)`:**
- Strips HTML tags using `strip_tags()`
- Decodes HTML entities using `html_entity_decode()`
- Cleans whitespace (multiple spaces/newlines collapsed)
- Truncates at word boundaries if `$maxLength` provided
- Returns clean plain text

**`getFeatureDescription($feature, $usePageContent = false, $maxLength = 80)`:**
- Selects content source: `page_content` if `$usePageContent` is true, otherwise `description`
- Falls back to `title` if no content available
- Uses `extractPlainTextFromHtml()` for processing

**`getMonthTitle($month)`:**
- Returns `page_title` with blue markup syntax removed
- Falls back to `month` field if `page_title` is empty

**`formatPlatforms($platforms)`:**
- Converts array to readable string
- Formats two platforms as "Desktop & Mobile"
- Formats single platform as "Desktop" or "Mobile"

**`formatTag($tag)`:**
- Uses `getTagLabel()` to convert tag key to German label
- Returns empty string if tag is empty

**`aggregateMonthPlatforms($month)`:**
- Collects platforms from all features in a month
- Removes duplicates
- Returns formatted platform string

### Content Quality Guidelines

1. **Description Length:**
   - `llms.txt`: 100 characters (focus on key information)
   - `llms-full.txt`: 200 characters (more comprehensive)

2. **HTML Handling:**
   - All HTML tags are stripped
   - HTML entities are decoded
   - Plain text is cleaned and normalized

3. **Truncation:**
   - Always truncates at word boundaries
   - Adds "..." ellipsis when truncated
   - Ensures minimum 50% of maxLength is preserved

4. **Metadata Inclusion:**
   - Platforms: Only included if available
   - Tags: Only included if available
   - Dates: Used for sorting, not displayed in entries

### ACAO Framework Usage

All entries follow the ACAO (Action, Context, Advantage, Outcome) framework:

- **Action**: What the user can do (e.g., "Entdecke neueste Features")
- **Context**: Who/what it's for (e.g., "von Ordio", "für November 2025")
- **Advantage**: What benefit it provides (e.g., "mit 2 neuen Features", "mit einem neuen Feature")
- **Outcome**: What the result is (e.g., "für bessere Produktivität", "und bleibe informiert")

### Automation

LLMS files are automatically regenerated when:
- Features are added or deleted
- Feature URLs, content, or published dates change
- Month pages are added or deleted
- Content is updated via admin panel

Manual regeneration is available via:
- Admin panel Settings section
- Direct API call: `v2/api/generate-llms.php`

## Maintenance

When adding new pages or modifying existing ones:

1. Follow the @graph pattern established in existing pages
2. Include WebPage schema alongside content-specific schema (CollectionPage/BlogPosting)
3. Add BreadcrumbList with proper hierarchy
4. Reference Organization and Website using @id pattern
5. Use consistent date formats (ISO 8601 with timezone)
6. Include all required meta tags (OG, Twitter Card, article tags)
7. Ensure canonical URLs are absolute and match og:url
8. Add descriptive alt text to all images
9. Ensure `description` and `page_content` fields contain meaningful content for LLMS generation
10. Use `page_title` field for months to provide better context in LLMS files

