SEO Usage Scenarios
This guide covers five different approaches to using Stati’s SEO features, from zero-configuration automatic injection to full custom control. Choose the approach that best fits your project’s requirements.
Table of Contents
- Scenario 1: Automatic SEO
- Scenario 2: Enhanced SEO
- Scenario 3: Hybrid Mode
- Scenario 4: Selective Manual Control
- Scenario 5: Full Custom Control
- Comparison Table
- Testing & Validation
Scenario 1: Automatic SEO
Best for: Projects that want comprehensive SEO with zero configuration and minimal maintenance.
Overview
Stati’s default SEO behavior provides comprehensive SEO tags automatically without any configuration or template changes. This is the recommended starting point for most projects.
Features:
- Automatic SEO injection enabled (default behavior)
- Basic meta tags generated from site config and page content
- Sitemap generation available (requires baseUrl configured)
- No code changes required
Configuration
The minimal Stati configuration with automatic SEO:
// stati.config.ts
import { defineConfig } from '@stati/core';
export default defineConfig({
site: {
title: 'My Site',
baseUrl: 'https://example.com',
},
});
How It Works
With automatic SEO, a simple page template like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>My Page</h1>
<p>Welcome to my page content.</p>
</body>
</html>
Gets these SEO tags automatically injected:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Auto-injected by Stati -->
<title>My Page | My Site</title>
<meta name="description" content="Welcome to my page content.">
<link rel="canonical" href="https://example.com/my-page">
<meta property="og:title" content="My Page">
<meta property="og:description" content="Welcome to my page content.">
<meta property="og:url" content="https://example.com/my-page">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="My Page">
<meta name="twitter:description" content="Welcome to my page content.">
</head>
<body>
<h1>My Page</h1>
<p>Welcome to my page content.</p>
</body>
</html>
Optional: Enable Sitemap
Add sitemap generation to your configuration:
export default defineConfig({
site: {
title: 'My Site',
baseUrl: 'https://example.com', // Required for sitemap URLs
},
sitemap: {
enabled: true,
},
});
When to Use
- New projects that need SEO quickly
- Projects with simple SEO requirements
- Teams that prefer convention over configuration
- Projects where developers have limited SEO expertise
Scenario 2: Enhanced SEO
Best for: Projects that want better SEO without changing templates.
Overview
Keep automatic injection enabled and enhance SEO through configuration and frontmatter. This approach provides granular control over SEO metadata on a per-page basis without touching template code.
Global Configuration
// stati.config.ts
import { defineConfig } from '@stati/core';
export default defineConfig({
site: {
title: 'My Awesome Site',
description: 'Building amazing static sites with Stati',
baseUrl: 'https://example.com',
},
seo: {
autoInject: true, // Keep automatic injection enabled
defaultAuthor: {
name: 'Jane Doe',
email: 'jane@example.com',
url: 'https://janedoe.com',
},
},
sitemap: {
enabled: true,
defaultPriority: 0.5,
defaultChangeFreq: 'weekly',
},
robots: {
enabled: true,
disallow: ['/admin/', '/draft/'],
},
});
Per-Page Enhancement
Add SEO frontmatter to high-value pages:
---
title: Complete Guide to Static Sites
description: Learn everything about static site generation with this comprehensive guide
seo:
keywords: [static sites, jamstack, web development]
openGraph:
image:
url: https://example.com/images/og-guide.jpg
alt: Static Site Guide Cover
width: 1200
height: 630
type: article
article:
publishedTime: 2025-01-15T10:00:00Z
author: Jane Doe
section: Web Development
tags: [tutorial, static-sites, jamstack]
twitter:
card: summary_large_image
image: https://example.com/images/twitter-guide.jpg
imageAlt: Static Site Guide
structuredData:
'@context': https://schema.org
'@type': Article
headline: Complete Guide to Static Sites
datePublished: 2025-01-15T10:00:00Z
author:
'@type': Person
name: Jane Doe
sitemap:
priority: 0.9
changefreq: weekly
---
# Complete Guide to Static Sites
Your content here...
Sitemap Control
For pages you want to exclude or deprioritize:
---
title: Draft Page
sitemap:
exclude: true # Exclude from sitemap
---
---
title: Archive Page
sitemap:
priority: 0.3
changefreq: yearly
---
When to Use
- Projects where SEO needs vary by page
- Content-heavy sites (blogs, documentation)
- Teams where content authors control SEO
- Projects with evolving SEO requirements
- Sites with mixed content importance
Scenario 3: Hybrid Mode
Best for: Projects that need custom control over specific tags while automating the rest. This is the recommended approach for projects with advanced SEO requirements.
Concept
Keep auto-injection enabled but manually override specific SEO tags when needed. Stati detects existing tags and skips auto-generating them. This gives you fine-grained control where you need it while maintaining automatic fallbacks everywhere else.
Example 1: Custom Title, Auto-Generate Rest
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<!-- Manual title with custom format -->
<title><%= stati.page.title %> - <%= stati.site.title %> | Premium Guides</title>
<!-- Stati auto-generates: description, canonical, OG tags, Twitter, etc. -->
</head>
<body>
<%~ it.content %>
</body>
</html>
Result:
- Your custom title is used
- All other SEO tags are auto-generated (description, canonical, OG, Twitter, etc.)
Example 2: Custom OG Image, Manual OG Tags
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<!-- Custom OG image tag -->
<meta property="og:image" content="<%= stati.site.baseUrl %>/custom-og.jpg">
<!-- Stati detects OG tag and SKIPS all Open Graph generation -->
<!-- You must add other OG tags manually -->
<meta property="og:title" content="<%= stati.page.title %>">
<meta property="og:description" content="<%= stati.page.description %>">
<meta property="og:url" content="<%= stati.site.baseUrl %><%= stati.page.url %>">
<!-- Stati still auto-generates: title, description, canonical, Twitter, etc. -->
</head>
<body>
<%~ it.content %>
</body>
</html>
Important: Open Graph tags are a group. If you add any og:* tag manually, Stati skips auto-generating all Open Graph tags. The same applies to Twitter Card tags.
Example 3: Conditional Custom Tags
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<% if (stati.page.customTitle) { %>
<!-- Use custom title format for specific pages -->
<title><%= stati.page.customTitle %></title>
<% } %>
<% if (stati.page.author) { %>
<!-- Custom author meta tag -->
<meta name="author" content="<%= stati.page.author %>">
<% } %>
<!-- Stati auto-generates all missing tags -->
</head>
<body>
<%~ it.content %>
</body>
</html>
Example 4: Dynamic OG Images
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<% if (stati.page.ogImage) { %>
<!-- Complete custom Open Graph section -->
<meta property="og:image" content="<%= stati.page.ogImage %>">
<meta property="og:image:alt" content="<%= stati.page.ogImageAlt || stati.page.title %>">
<meta property="og:title" content="<%= stati.page.title %>">
<meta property="og:description" content="<%= stati.page.description %>">
<meta property="og:url" content="<%= stati.site.baseUrl %><%= stati.page.url %>">
<meta property="og:type" content="article">
<% } %>
<!-- Stati auto-generates: title, description, canonical, Twitter (if no OG) -->
</head>
<body>
<%~ it.content %>
</body>
</html>
Tag Groups
Understanding tag groups is critical for hybrid mode:
| Group | Behavior |
|---|---|
| Open Graph | If any og:* tag exists, all OG tags are skipped |
| Twitter Cards | If any twitter:* tag exists, all Twitter tags are skipped |
| Individual Tags | Title, description, keywords, author, robots, canonical are independent |
When to Use
- Projects with varying SEO requirements across sections
- Sites that need custom logic for certain pages
- Projects transitioning from custom SEO to automatic
- Teams that want safety nets while customizing
- Sites with complex title or description requirements
Scenario 4: Selective Manual Control
Best for: Projects that want explicit control over which tags are generated.
Overview
Use the stati.generateSEO() helper with specific tag types to control exactly which tags Stati generates. This approach is more explicit than hybrid mode and prevents any automatic tag generation.
Configuration
Disable auto-injection to avoid conflicts:
// stati.config.ts
export default defineConfig({
seo: {
autoInject: false, // Disable automatic injection
defaultAuthor: {
name: 'John Doe',
email: 'john@example.com',
},
},
});
Example 1: Generate Only Title and Description
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<!-- Generate only title and description -->
<%~ stati.generateSEO(['title', 'description']) %>
<!-- Add custom tags -->
<link rel="canonical" href="<%= stati.site.baseUrl %><%= stati.page.url %>">
<meta name="custom-meta" content="custom value">
</head>
<body>
<%~ it.content %>
</body>
</html>
Example 2: Generate Basic SEO + Custom Social
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<!-- Generate basic SEO tags -->
<%~ stati.generateSEO(['title', 'description', 'canonical', 'author']) %>
<!-- Custom Open Graph -->
<meta property="og:title" content="<%= stati.page.title %> | <%= stati.site.title %>">
<meta property="og:image" content="<%= stati.site.baseUrl %>/images/og-<%= stati.page.slug %>.jpg">
<meta property="og:type" content="article">
<!-- Custom Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@mysite">
<meta name="twitter:image" content="<%= stati.site.baseUrl %>/images/twitter-<%= stati.page.slug %>.jpg">
</head>
<body>
<%~ it.content %>
</body>
</html>
Example 3: Generate Multiple Tag Types
<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
<head>
<!-- Generate multiple specific tag types -->
<%~ stati.generateSEO(['title', 'description', 'canonical', 'opengraph']) %>
<!-- Add custom tags -->
<meta name="theme-color" content="#000000">
</head>
<body>
<%~ it.content %>
</body>
</html>
Available Tag Types
title- Page title tagdescription- Meta description tagkeywords- Meta keywords tagauthor- Meta author tagrobots- Meta robots tagcanonical- Canonical link tagopengraph- All Open Graph tagstwitter- All Twitter Card tags
When to Use
- Projects that need explicit control over tag generation
- Sites with strict SEO governance
- Projects where only certain tags are needed
- Teams that prefer predictable, explicit behavior
- Sites with custom SEO validation requirements
Scenario 5: Full Custom Control
Best for: Projects with complex custom SEO requirements or specific SEO systems.
Overview
Disable automatic injection and manually implement all SEO tags. This gives you complete control over every aspect of SEO tag generation.
Configuration
// stati.config.ts
export default defineConfig({
seo: {
autoInject: false, // Completely disable automatic injection
},
});
Manual Implementation
<!-- site/layout.eta -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Manual title with custom logic -->
<title>
<% if (stati.page.seoTitle) { %>
<%= stati.page.seoTitle %>
<% } else if (stati.page.title) { %>
<%= stati.page.title %> | <%= stati.site.title %>
<% } else { %>
<%= stati.site.title %>
<% } %>
</title>
<!-- Manual meta description -->
<% if (stati.page.description) { %>
<meta name="description" content="<%= stati.page.description %>">
<% } %>
<!-- Manual keywords -->
<% if (stati.page.tags && stati.page.tags.length > 0) { %>
<meta name="keywords" content="<%= stati.page.tags.join(', ') %>">
<% } %>
<!-- Manual canonical -->
<link rel="canonical" href="<%= stati.site.baseUrl %><%= stati.page.url %>">
<!-- Manual Open Graph -->
<meta property="og:title" content="<%= stati.page.title || stati.site.title %>">
<% if (stati.page.description) { %>
<meta property="og:description" content="<%= stati.page.description %>">
<% } %>
<meta property="og:url" content="<%= stati.site.baseUrl %><%= stati.page.url %>">
<meta property="og:type" content="<%= stati.page.type || 'website' %>">
<% if (stati.page.ogImage) { %>
<meta property="og:image" content="<%= stati.site.baseUrl %><%= stati.page.ogImage %>">
<% if (stati.page.ogImageWidth) { %>
<meta property="og:image:width" content="<%= stati.page.ogImageWidth %>">
<% } %>
<% if (stati.page.ogImageHeight) { %>
<meta property="og:image:height" content="<%= stati.page.ogImageHeight %>">
<% } %>
<% } %>
<!-- Manual Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@mysite">
<meta name="twitter:title" content="<%= stati.page.title %>">
<% if (stati.page.description) { %>
<meta name="twitter:description" content="<%= stati.page.description %>">
<% } %>
<% if (stati.page.twitterImage || stati.page.ogImage) { %>
<meta name="twitter:image" content="<%= stati.site.baseUrl %><%= stati.page.twitterImage || stati.page.ogImage %>">
<% } %>
<!-- Manual structured data -->
<% if (stati.page.schema) { %>
<script type="application/ld+json">
<%~ JSON.stringify(stati.page.schema, null, 2) %>
</script>
<% } %>
<!-- Your other custom tags -->
</head>
<body>
<%~ it.content %>
</body>
</html>
When to Use
- Projects with highly specific SEO requirements
- Sites with custom SEO validation logic
- Teams with dedicated SEO specialists
- Projects integrating with external SEO systems
- Sites that need non-standard SEO implementations
Trade-offs
Advantages:
- Complete control over every tag
- Custom logic for complex requirements
- No automatic behavior to understand
- Everything is explicit
Disadvantages:
- More code to maintain
- Manual escaping required for user input
- No automatic fallbacks
- Higher chance of errors (missing tags, typos)
- More testing required
Comparison Table
| Feature | Scenario 1: Automatic | Scenario 2: Enhanced | Scenario 3: Hybrid | Scenario 4: Selective | Scenario 5: Full Custom |
|---|---|---|---|---|---|
| Setup Effort | None | Low | Low | Medium | High |
| Code Changes | None | None | Minimal | Medium | Extensive |
| Control Level | Low | Medium | High | High | Complete |
| Auto-Injection | Yes | Yes | Yes | No | No |
| Custom Tags | No | Frontmatter | Template | Template | Full |
| Fallback | Always | Always | Partial | No | No |
| Maintenance | Minimal | Low | Low | Medium | High |
| Best For | New projects | Most projects | Advanced needs | Specific control | Complex SEO |
Testing & Validation
Checklist
After implementing SEO, validate your setup:
- Build succeeds without errors
- View page source - verify SEO tags are present
- Check title tags - correct format and no duplicates
- Check meta descriptions - present and under 160 characters
- Check canonical URLs - absolute URLs, correct protocol
- Check Open Graph tags - og:title, og:description, og:image, og:url
- Check Twitter Cards - twitter:card, twitter:title, twitter:image
- Check sitemap.xml - exists at
/sitemap.xml, valid XML - Check robots.txt - exists at
/robots.txt, correct directives - Validate structured data - use Google Rich Results Test
- Test social sharing - use Facebook Debugger and Twitter Card Validator
Validation Tools
HTML & SEO:
Social Media:
Search Console:
Common Issues
Issue: Open Graph tags not generated
Solution: Check for existing og:* tags in your HTML (any OG tag skips all OG generation)
Issue: Sitemap not generated
Solution: Ensure sitemap is enabled and baseUrl is set:
export default defineConfig({
site: {
baseUrl: 'https://example.com' // Required for sitemap URLs
},
sitemap: {
enabled: true,
}
});
Issue: Structured data validation errors
Solution: Validate frontmatter JSON structure and required fields for your schema type
Recommended Approach
For most projects, we recommend Scenario 3: Hybrid Mode because it offers:
- Best of both worlds - automatic fallback + custom control
- Low maintenance - minimal code to maintain
- Flexible - override only what you need
- Safe - Stati handles escaping and validation
- Gradual - start automatic, add custom tags as needed
Start with Scenario 1 (Automatic) and move to Scenario 3 (Hybrid) as your SEO requirements evolve.
Next Steps
- SEO Configuration Guide - Complete SEO configuration reference
- SEO API Reference - Detailed API documentation
Questions? Check the SEO Configuration documentation or open a GitHub issue.