# Product Page FAQ Optimization Guide

**Last Updated:** 2026-04-02 (`/mitarbeiter-app` misc-faqs parity + head FAQPage plain text)

Guide for structuring, optimizing, and implementing FAQs on Ordio product pages (e.g., `/payroll`, `/arbeitszeiterfassung`, `/schichtplan`). Aligns with tools page FAQ patterns and SEO/AEO/GEO best practices.

**Cross-site standards:** See [FAQ_WEBSITE_STANDARD.md](../../FAQ_WEBSITE_STANDARD.md) for shared format guidance (paragraphs vs lists), FAQ-set rework, Google FAQ rich-result context, and iteration loop. **Rework checklist:** [FAQ_REWORK_DECISION_TREE.md](../../FAQ_REWORK_DECISION_TREE.md).

## Overview

Product page FAQs should:

- Use a **single flat list** (no H3 subsections)
- Follow **logical flow order** (definition → how-to → features → compliance → support)
- Target **40–80 words per answer**
- Include **2–3 contextual internal links**
- Emit **one FAQPage JSON-LD** from the same FAQ data as the accordion (post-footer `include-marketing-faq-jsonld.php` when using `misc-faqs/*.json`; no duplicate FAQPage in `<head>` `@graph`)

### Draft `*-neu` product URLs (parallel to live)

During a template rebuild, a draft route may load FAQs from `docs/content/pages/product-pages/{page}-neu/faq-answers-optimized.json` while the public URL still maps to the registry `docs_dir` (e.g. `schichtplan/`). **Schichtplan (live):** `/schichtplan` serves `product_schichtplan_neu.php` with FAQs from `schichtplan-neu/faq-answers-optimized.json`; **`/schichtplan-neu` → `/schichtplan` (301)** — do not link to the draft path.

- **Research SSOT** (GSC, SISTRIX, `faq-research.json`, `KEYWORD_DECISION.md`) stays under the **registry folder** `docs/content/pages/product-pages/{live-slug}/data/`, not under `*-neu/`.
- **Parity:** When FAQs are final, keep **`v2/data/misc-faqs/{file}.json`** (live accordion + post-footer JSON-LD), **`docs/.../{live-slug}/faq-answers-optimized.json`**, and any draft **`{page}-neu/faq-answers-optimized.json`** aligned on the same questions and answers (JSON shapes differ: `items[]` vs `answers[]`).
- **QA:** Run `python3 v2/scripts/product-pages/validate-faq-answers.py --page={key}` and `php v2/scripts/dev-helpers/verify-faq-jsonld-parity.php --all-misc-faqs` after bulk edits. For draft pages that emit FAQPage from PHP, use `faq_answer_html_to_schema_plain_text()` for `acceptedAnswer.text` (see [`v2/helpers/faq-schema-plain-text.php`](../../../../v2/helpers/faq-schema-plain-text.php)).
- **`/abwesenheiten`:** `product_absences.php` lädt `abwesenheiten/faq-answers-optimized.json` für Akkordeon und FAQPage-JSON-LD im Markup; halte `v2/data/misc-faqs/product_absences.json` bei FAQ-Änderungen synchron (gleiche Fragen/Antworten), damit Parity-Audits und zentrale FAQ-Karten konsistent bleiben.
- **`/dokumentenmanagement`:** `product_documents.php` lädt `dokumentenmanagement/faq-answers-optimized.json` für Akkordeon und FAQPage im `<head>`-`@graph`; halte `v2/data/misc-faqs/product_documents.json` synchron und nutze `faq_answer_html_to_schema_plain_text()` für `acceptedAnswer.text` (nicht rohes `strip_tags`).
- **`/checklisten`:** `product_checklists.php` lädt `checklisten/faq-answers-optimized.json` für Akkordeon und FAQPage im `<head>`-`@graph`; halte `v2/data/misc-faqs/product_checklists.json` synchron und nutze `faq_answer_html_to_schema_plain_text()` für `acceptedAnswer.text` (nicht rohes `strip_tags`).
- **`/mitarbeiter-app`:** `product_mobile_app.php` lädt `mitarbeiter-app/faq-answers-optimized.json` für Akkordeon und FAQPage im `<head>`-`@graph`; halte `v2/data/misc-faqs/product_mobile_app.json` synchron und nutze `faq_answer_html_to_schema_plain_text()` für `acceptedAnswer.text` (nicht rohes `strip_tags`).

## FAQ Structure Requirements

### HTML Structure

Wrap the FAQ block in a section with **`id="faq"`** (anchor + styling hooks). Use a topic keyword in **Ordio blue** after “Häufig gestellte Fragen zu/zur” via `<span class="highlighted">…</span>` (colour is enforced in [`v2/css/product-pages.css`](../../../../v2/css/product-pages.css): `.highlighted` and `section#faq h2 .highlighted`).

```html
<section class="py-12 sm:py-24 bg-[#fbfbfb] px-6" id="faq">
    <div class="max-w-5xl mx-auto">
        <h2 class="font-gilroybold text-4xl sm:text-5xl leading-[102%] tracking-[-1.5px] text-[#333333] mb-12 text-center">
            Häufig gestellte Fragen zu <span class="highlighted">Feature Name</span>
        </h2>
        <div class="mb-16">
            <div class="space-y-4">
                <!-- details items -->
            </div>
        </div>
    </div>
</section>
```

Use the same `<details>` pattern as tools pages for each item:

```html
<details id="faq-1" class="bg-white rounded-lg p-6 [&>summary::-webkit-details-marker]:hidden [&>summary::marker]:content-none group">
    <summary class="flex items-center justify-between font-inter600 text-lg cursor-pointer">
        <span>Question text here?</span>
        <svg class="w-5 h-5 transform transition-transform duration-300 ease-in-out shrink-0 ml-2 group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
        </svg>
    </summary>
    <div class="mt-4 text-gray-600">
        <p>Answer text (40–80 words).</p>
    </div>
</details>
```

**Chevron animation:** Rotation (collapsed → down, open → up) is implemented in **`product-pages.min.css`** using `details > summary svg` / `details[open] > summary svg` with **`!important`**, so it wins over Tailwind’s `.transform` utilities. Optional `group-open:rotate-180` on the SVG is redundant for behaviour but harmless.

**Requirements:**

- **`id="faq"`** on the wrapping `<section>`
- Unique `id="faq-1"` through `id="faq-N"` on each `<details>`
- `group` class on `<details>` (for markup consistency; chevron rotation does not depend on Tailwind `group-open`)
- No H3 subsection headings; all FAQs in one `space-y-4` container

### Logical Flow Order

Order FAQs by user journey:

1. **Definition** – Was ist [Feature]?
2. **How-to / Ablauf** – Wie funktioniert [Feature]?
3. **Requirements / What** – Welche Daten muss ich eingeben?
4. **Integration** – Fließen Arbeitszeiten automatisch ein?
5. **Features** – Zuschläge, Minijobs, Meldungen
6. **Compliance** – Rechtssicher, DSGVO, gesetzliche Änderungen
7. **Wechsel / Onboarding** – Umstieg, Import, Schnittstellen
8. **Support / Vertrag** – Support, Test, Vertragsbindung

## Keyword Research Process

### Tools

- **Serper MCP** – PAA questions for target keywords (German: google.de)
- **SISTRIX** – `keyword.seo.serpfeatures` for PAA; keyword volumes
- **GSC** – Export queries for the product page URL
- **Firecrawl MCP** – Scrape competitor FAQs (`firecrawl_scrape` with `formats: ['markdown']`)

### Output Files

Store research in `docs/content/pages/product-pages/{page-slug}/`:

- `faq-research.json` – PAA questions, related searches, keyword notes
- `performance-gsc.json` – GSC export (see GSC Export Workflow below)
- `competitor-faq-analysis.json` – Competitor FAQ structure
- `current-faqs.json` – Extracted FAQs from PHP (via `extract-product-faqs.php`)
- `faq-questions.json` – Questions for answer generation (optional; falls back to current-faqs.json)
- `faq-answers-optimized.json` – Generated answers (via `generate-product-faq-answers.php`)
- `faq-final-order.json` – Final order after merge/reorder

### GSC Export Workflow

1. **Export from GSC:** Search Console → Performance → filter Page URL contains `ordio.com/{slug}` (e.g. `ordio.com/payroll`, `ordio.com/arbeitszeiterfassung`) → export Pages and/or Queries tab as CSV.
2. **Save CSV** to `docs/content/pages/product-pages/{slug}/gsc-export.csv` (or any path).
3. **Run script:**
   ```bash
   php v2/scripts/product-pages/gsc-product-export.php --csv=gsc-export.csv --page=payroll
   ```
   Valid `--page` values: `payroll`, `timetracking`, `absences`, `documents`, `personnelfile`, `checklists`, `events`, `appstore`, `mobile_app`, `nano_ai`, `shiftplan`
4. **Output:** `docs/content/pages/product-pages/{slug}/performance-gsc.json` with `pages` and `queries` (top 50 by clicks).

**Sample:** `payroll/gsc-export-sample.csv` shows expected format (Page/Query, Clicks, Impressions, CTR, Position). Replace with real export for production use.

### Extract Script

Extract current FAQs from any product page:

```bash
php v2/scripts/product-pages/extract-product-faqs.php --page=payroll
```

**Output:** `docs/content/pages/product-pages/{slug}/current-faqs.json` with `faqs` array (question, answer, subsection).

### FAQ Answer Generator (Gemini)

Generate optimized answers (40–80 words, du tone, Ordio mentions) via Gemini:

```bash
php v2/scripts/product-pages/generate-product-faq-answers.php --page=payroll --use-ai
```

**Input:** `faq-questions.json` or `current-faqs.json` in `docs/content/pages/product-pages/{slug}/`  
**Output:** `faq-answers-optimized.json`  
**Requires:** `GEMINI_API_KEY` or `v2/config/gemini-api-key.php` ([setup](../../../development/GEMINI_API_KEY_LOCAL.md))  
**Dry-run:** `--dry-run` to validate input without API calls

## Content Optimization

### Answer Length

- **Target:** 40–80 words per answer
- **Structure:** Direct answer (first sentence) → context → actionable info
- **Du tone:** Informal "du" throughout
- **Ordio mentions:** Natural, 1–2 per section

### Keyword Integration

- Primary keywords in 3–5 FAQs
- Related terms naturally integrated
- No keyword stuffing

### Internal Links

Add 2–3 contextual links across FAQs, e.g.:

- `/arbeitszeiterfassung` (Zeiterfassung)
- `/schichtplan` (Schichtplanung)
- `/demo-vereinbaren` (Demo / conversion CTA — do **not** link to `/kostenlos-testen`; it is a paid-ads LP)

## FAQPage Schema

### Location

For JSON-driven product FAQs (`v2/data/misc-faqs/product_*.json` + `render-faq-json.php`), emit FAQPage **after** `footer.php` via [`include-marketing-faq-jsonld.php`](../../../../v2/components/include-marketing-faq-jsonld.php). The helper uses [`ordio_faqpage_schema_node()`](../../../../v2/helpers/faq-jsonld.php) and [`faq_answer_html_to_schema_plain_text()`](../../../../v2/helpers/faq-schema-plain-text.php)—do **not** add a second FAQPage inside the `<head>` `@graph`.

Product pages that still load FAQs from `docs/content/pages/product-pages/{slug}/faq-answers-optimized.json` should keep **one** FAQ emission path: either migrate to `misc-faqs` + post-footer include, or generate head FAQ from the same file in one place (avoid hand-duplicated `mainEntity`).

**`/arbeitszeiterfassung`:** SSOT is `docs/content/pages/product-pages/arbeitszeiterfassung/faq-answers-optimized.json` only (`product_timetracking.php`). There is **no** `misc-faqs` mirror; do not reintroduce `v2/data/misc-faqs/product_timetracking.json` or a `faq-jsonld.php` map entry for it.

### Structure (conceptual)

The emitted JSON-LD matches:

```json
{
  "@type": "FAQPage",
  "@id": "https://www.ordio.com/{page}#faq",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Plain text from HTML question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Plain text from HTML answer"
      }
    }
  ]
}
```

### Requirements

- Plain text for schema matches HTML via `faq_answer_html_to_schema_plain_text()`
- All non-empty FAQs included
- Exactly one FAQPage script per URL

## Validation Checklist

- [ ] All FAQs in single flat list (no H3 subsections)
- [ ] Unique IDs `faq-1` through `faq-N`
- [ ] Answers 40–80 words
- [ ] 2–3 internal links present
- [ ] FAQPage JSON-LD post-footer (or single generated source) with all FAQs; no duplicate FAQPage in head `@graph`
- [ ] Schema JSON valid (no parse errors)
- [ ] Google Rich Results Test passes
- [ ] Expand/collapse works; mobile responsive; keyboard accessible

## Post-Implementation Next Steps

1. **Preview:** Test at `http://localhost:8003/payroll` – verify expand/collapse, mobile layout, keyboard accessibility.
2. **Schema:** After deploy, run [Google Rich Results Test](https://search.google.com/test/rich-results) on `https://www.ordio.com/payroll`.
3. **GSC:** Export real GSC data for the page, run `gsc-product-export.php --page=payroll --csv=...` to update `performance-gsc.json` – use for future FAQ prioritization.

## Research-First Workflow

Data-driven question selection: GSC → Serper PAA → Firecrawl competitors → Product context → Question selection → Generate → Validate → Implement.

### Phase 2: Research (per page)

**Registry-backed 10 Funktionen (recommended):** `bash v2/scripts/product-pages/run-feature-page-research-pipeline.sh feature-<slug>` then `php v2/scripts/product-pages/generate-feature-page-data-synthesis.php --page=feature-<slug>`. Hub: [FEATURE_PAGES_CONTENT_INDEX.md](FEATURE_PAGES_CONTENT_INDEX.md).

1. **Product context:** `php v2/scripts/product-pages/extract-product-context.php --page=slug`
2. **GSC:** Prefer API: `php v2/scripts/tools/collect-tool-gsc-queries.php --path=/… --output=<docs_dir>/data/gsc-queries.json`; or CSV → `gsc-product-export.php --csv=... --page=slug` → `performance-gsc.json`
3. **Serper PAA:** `python3 v2/scripts/marketing-pages/serper-paa-research.py --page=feature-<slug>` → `data/faq-research.json` (requires `SERPER_API_KEY`). Alias: `python3 v2/scripts/product-pages/serper-paa-research.py` delegates for registry/legacy keys; **`--page=events`** still uses the legacy file location.
4. **Competitor FAQs:** `python3 v2/scripts/product-pages/scrape-competitor-faqs.py --page=feature-<slug>` (requires `FIRECRAWL_API_KEY`; registry `competitor_urls`)

**Or run legacy bundled steps:** `bash v2/scripts/product-pages/run-research-workflow.sh payroll` (prefers `run-feature-page-research-pipeline.sh feature-*` for registry pages)

### Phase 3: Question Selection

`php v2/scripts/product-pages/select-faq-questions.php --page=slug` → `faq-questions.json` (15–20 questions, prioritized)

### Phase 4: Generate & Validate

1. `php v2/scripts/product-pages/generate-product-faq-answers.php --page=slug --use-ai` → `faq-answers-optimized.json`
2. `python3 v2/scripts/product-pages/validate-faq-answers.py --page=slug` → `validation-report.json`
3. Manual accuracy review against product docs

### Phase 5: Implement

Add FAQ section to PHP page, wire `include-marketing-faq-jsonld.php` when using `misc-faqs`, internal links. Test expand/collapse, schema, accessibility.

### Troubleshooting

- **Extract fails for PHP-rendered FAQs (e.g. Events):** The extract script now parses `$faqs = [['q'=>...,'a'=>...]]` arrays. Run `extract-product-faqs.php --page=events` to verify.
- **No GSC data:** Use PAA and competitor FAQs as primary sources; `performance-gsc.json` with `queries: []` is valid.
- **Serper/Firecrawl keys missing:** Run workflow with MCP in chat (Serper MCP, Firecrawl MCP) and manually merge into `faq-research.json` / `competitor-faq-analysis.json`.

## Per-Page Research Workflow (Legacy / MCP)

1. **Serper MCP:** PAA for target keywords (German: lohnabrechnung, Payroll, Gehaltsabrechnung, etc.)
2. **Web Search:** Competitor FAQ patterns
3. **Firecrawl MCP:** Scrape 3–5 competitor product pages (`firecrawl_scrape` with `formats: ['markdown']`)
4. **GSC:** Manual export → `gsc-product-export.php --page=slug --csv=...`
5. **Output files:** `faq-research.json`, `competitor-faq-analysis.json`, `performance-gsc.json`

See `PRODUCT_PAGE_FAQ_AUDIT.md` for per-page status and last updated dates.

## Reference

- `.cursor/rules/tools-pages-faq.mdc` – FAQ structure, schema patterns
- `docs/content/blog/FAQ_BEST_PRACTICES.md` – Answer length, PAA optimization
- `docs/content/blog/FAQ_SCHEMA_BEST_PRACTICES.md` – Schema requirements
