ultimate-jekyll-manager
Version:
Ultimate Jekyll dependency manager
207 lines (155 loc) • 9.45 kB
Markdown
# SEO — Alternatives Pages & Structured Data
This doc covers two SEO-focused subsystems:
1. **Alternatives Collection** — competitor comparison landing pages.
2. **Schema / Structured Data (JSON-LD)** — `SoftwareApplication` and `FAQPage` JSON-LD blocks.
## Alternatives Collection (SEO Competitor Comparison Pages)
UJ provides an `alternatives` collection for SEO landing pages that target users searching for competitors (e.g., "ExampleApp alternatives"). These pages are entirely frontmatter-driven and designed to convert visitors who are comparing products.
### How It Works
1. The `alternatives` collection is registered in `src/config/_config_default.yml` (UJM-controlled)
2. Each alternative is a markdown file in the consuming project's `_alternatives/` directory
3. The layout chain: `blueprint/alternatives/alternative` → `themes/classy/frontend/pages/alternatives/alternative`
4. An index page at `/alternatives` lists all alternatives automatically
5. **Shared content lives in the layout** — the theme layout provides default testimonials, stats, FAQs, CTA, and why_switch content so competitor pages only need competitor-specific data
6. The layout frontmatter uses `{{ page.resolved.alternative.competitor.name }}` to dynamically insert the competitor name into shared content (e.g., FAQ questions, CTA headlines)
### Creating an Alternative Page
In the consuming project, create `src/_alternatives/competitor-name.md`. Only competitor-specific data is needed — shared sections are inherited from the layout:
```yaml
---
layout: blueprint/alternatives/alternative
sitemap:
include: true
alternative:
competitor:
name: "Competitor Name"
description: "Brief description of the competitor (shown on /alternatives listing)"
comparison:
features:
- name: "Feature Name"
icon: "sparkles"
ours:
value: true # or string like "Unlimited"
theirs:
value: false # or string like "Limited"
---
```
That's it! The layout automatically generates:
- Hero with "Brand vs Competitor Name" headline
- Why Switch section with default differentiator items
- Testimonials, Stats, FAQs, and CTA with shared content
- All text dynamically references the competitor name via `{{ page.resolved.alternative.competitor.name }}`
**To override any inherited section**, define it in the competitor's frontmatter — `page.resolved` merge gives page-level values highest priority.
### Available Sections
All sections are **optional** — omit or leave empty to hide. Sections with `(shared)` have default content in the layout:
| Section | Frontmatter Key | Description |
|---------|----------------|-------------|
| Hero | `alternative.hero` | Gradient animated hero with "Brand vs Competitor" headline (shared) |
| Comparison | `alternative.comparison` | Side-by-side feature table — **must be defined per competitor** |
| Why Switch | `alternative.why_switch` | Alternating image/text showcase blocks (shared) |
| Video | `alternative.video` | YouTube embed (default: hidden, set `youtube_id` to show) |
| Testimonials | `alternative.testimonials` | Reuses `testimonial-scroll.html` component (shared) |
| Stats | `alternative.stats` | Social proof numbers with icons (shared) |
| FAQs | `alternative.faqs` | Accordion with switching-related questions (shared) |
| CTA | `alternative.cta` | Final conversion card with buttons (shared) |
### Dynamic Competitor Name in Frontmatter
The layout uses `{{ page.resolved.alternative.competitor.name }}` in its frontmatter defaults to dynamically reference the competitor. This works because the template pipes these values through `| uj_liquify` to resolve Liquid expressions.
**Example:** The layout's default FAQ includes:
```yaml
question: "Can I import my data from {{ page.resolved.alternative.competitor.name }}?"
```
Which renders as "Can I import my data from ExampleApp?" for an ExampleApp competitor page.
### Reference Implementation
- **Minimal competitor page:** `src/defaults/dist/_alternatives/example-competitor.md` — shows the minimum frontmatter needed (competitor name + comparison features)
- **Layout with all defaults:** `src/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/alternative.html` — contains shared content for all sections
### File Locations
| Purpose | Path |
|---------|------|
| Theme layout (alternative page) | `src/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/alternative.html` |
| Theme layout (index/listing page) | `src/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/index.html` |
| Blueprint (alternative) | `src/defaults/dist/_layouts/blueprint/alternatives/alternative.html` |
| Blueprint (index) | `src/defaults/dist/_layouts/blueprint/alternatives/index.html` |
| Default page (index) | `src/defaults/dist/pages/alternatives/index.md` |
| Sample alternative | `src/defaults/dist/_alternatives/example-competitor.md` |
| CSS | `src/assets/css/pages/alternatives/alternative/index.scss` |
| JS | `src/assets/js/pages/alternatives/alternative/index.js` |
## Schema / Structured Data (JSON-LD)
UJ automatically generates JSON-LD structured data in `foot.html`. The SoftwareApplication schema with AggregateRating is opt-in via frontmatter.
### SoftwareApplication Schema
Renders a `SoftwareApplication` JSON-LD block with deterministic aggregate ratings. Enabled by blueprint layouts (index, pricing, download, alternatives/alternative) — consuming projects can override or disable per page.
**How it works:**
1. **`_config.yml`** sets fallback defaults (no `enabled` key — just field defaults like `application_category`, `price`, etc.)
2. **Blueprint layouts** set `schema.software_application.enabled: true` with page-appropriate `features`
3. **Consuming projects** can override any value in their page frontmatter, or disable with `enabled: false`
This follows the standard `page.resolved` merge: page > layout > site.
**Deterministic ratings:** Uses the `uj_hash` filter (from jekyll-uj-powertools) seeded with `site.url` by default, producing stable values across builds:
- Rating: always `4.8` or `4.9` (deterministic per seed)
- Review count: 200,000–999,999 (deterministic per seed)
- Override seed per page with `hash_seed` to get different values
**Blueprint frontmatter example:**
```yaml
### SCHEMA ###
schema:
software_application:
enabled: true
features:
- "Free to use"
- "24/7 availability"
- "User-friendly interface"
```
**Consuming project override example:**
```yaml
schema:
software_application:
application_category: "EducationalApplication"
features:
- "AI-powered solutions"
- "24/7 availability"
```
**Consuming project disable example:**
```yaml
schema:
software_application:
enabled: false
```
**Available fields:**
| Field | Default | Description |
|-------|---------|-------------|
| `enabled` | (set by blueprint) | Enable/disable the schema block |
| `name` | `site.brand.name` | Application name |
| `description` | `page.resolved.meta.description` | Application description |
| `application_category` | `WebApplication` | Schema.org application category |
| `operating_system` | `Web-based` | Target OS |
| `price` | `0` | Price (string) |
| `price_currency` | `USD` | Currency code |
| `features` | `[]` | Feature list for `featureList` field |
| `hash_seed` | `site.url` | Seed for deterministic rating/count generation |
**File locations:**
| Purpose | Path |
|---------|------|
| Schema block (rendering) | `src/defaults/dist/_includes/core/foot.html` |
| Site-level defaults | `src/defaults/src/_config.yml` (under `schema:`) |
| Blueprint activation | `src/defaults/dist/_layouts/blueprint/{index,pricing,download}.html`, `blueprint/alternatives/alternative.html` |
| Hash filter | `jekyll-uj-powertools/lib/filters/main.rb` (`uj_hash`) |
### FAQPage Schema
Renders a `FAQPage` JSON-LD block for pages with FAQ/accordion sections. Enabled by the alternatives blueprint — consuming projects can also enable it on any page with FAQ content.
**How it works:**
1. **`_config.yml`** sets `faq_page.items: []` as fallback
2. **Blueprint layouts** set `schema.faq_page.enabled: true`
3. **Items source (fallback chain):** `schema.faq_page.items` → `page.resolved.faqs.items` → `page.resolved.alternative.faqs.items`. Pages with generic `faqs.items` (like pricing) and alternatives pages both get FAQPage schema automatically without duplicating content
4. Questions/answers are processed through `uj_liquify` (supports Liquid expressions like competitor names) and `uj_json_escape`
**Blueprint activation:** Enabled by default in `blueprint/pricing.html`, `blueprint/contact.html`, `blueprint/download.html`, `blueprint/extension/index.html`, and `blueprint/alternatives/alternative.html`.
**Consuming project usage — provide items directly:**
```yaml
schema:
faq_page:
enabled: true
items:
- question: "How do I get started?"
answer: "Sign up for free and follow the onboarding guide."
- question: "Is there a free plan?"
answer: "Yes, our basic plan is completely free."
```
**Available fields:**
| Field | Default | Description |
|-------|---------|-------------|
| `enabled` | (set by blueprint) | Enable/disable the schema block |
| `items` | `[]` | Array of `{question, answer}` objects. Falls back to `alternative.faqs.items` if empty |