Appearance
Agent Landing Pages — Content Guide
How to create and maintain the expanded SEO content that powers /ai-agents/{slug} pages.
Overview
Each agent landing page is built from two layers:
Core config — defined in
useAgentPurposes.ts. Provides the hero, features, FAQs, conversation demo, and ideal-for personas. Required for every agent type. You do not author this layer here.Expanded content — a TypeScript file at
app/data/agents/{slug}.ts. Provides additional sections (How It Works, Why Conversations, Related Agents), extra FAQs, extra features, custom sections, and structured data for rich results.
The expanded content layer exists to differentiate each agent page for organic search. A booking assistant page and a lead collector page must not look or read the same. Google penalizes near-duplicate pages. This system makes structural and topical differentiation systematic.
When to create an expanded content file: Every agent type with a live landing page should have one. A page without expanded content ranks lower and provides less value to visitors than one with it.
Content File Structure
All content files live at app/data/agents/{slug}.ts. The slug must exactly match an AgentPurposeSlug value.
Minimal valid file:
typescript
import type { AgentExpandedContent } from './types'
const content: AgentExpandedContent = {
slug: 'customer-support',
lastUpdated: '2026-03-06',
}
export default contentComplete file anatomy (all optional fields shown):
typescript
import type { AgentExpandedContent } from './types'
const content: AgentExpandedContent = {
// Required
slug: 'booking-assistant',
lastUpdated: '2026-03-05', // ISO date string — update when content changes
// Override default section headings
sectionTitleOverrides: { ... },
// Merge with core FAQs (appended after)
additionalFAQs: [ ... ],
// Merge with core features (appended after)
additionalFeatures: [ ... ],
// Adds "How It Works" section (3-5 steps)
howItWorks: [ ... ],
// Adds "Why Conversations" section (3-4 points)
whyConversations: [ ... ],
// Adds "Related Agents" section (3-5 links)
relatedAgents: [ ... ],
// Adds custom sections between Features and IdealFor
customSections: [ ... ],
// JSON-LD HowTo schema steps (separate from visual howItWorks)
howToSteps: [ ... ],
// Stats shown in custom stats-highlight sections
statHighlights: [ ... ],
// Internal link registry for tracking coverage
internalLinks: [ ... ],
}
export default contentAfter creating the file, register it in app/data/agents/index.ts:
typescript
const contentModules = {
// ... existing entries
'your-slug': () => import('./your-slug'),
}Type Contracts and Constraints
AgentExpandedContent
| Field | Type | Required | Notes |
|---|---|---|---|
slug | AgentPurposeSlug | Yes | Must match an existing agent purpose slug |
lastUpdated | string | Recommended | ISO date string, e.g. '2026-03-05'. Signals content freshness. |
sectionTitleOverrides | SectionTitleOverrides | No | Replaces category-computed defaults for named sections |
additionalFAQs | FAQItem[] | No | Merged after core FAQs. Target total: 10-15 across core + additional |
additionalFeatures | UseCaseFeature[] | No | Merged after core features |
howItWorks | HowItWorksStep[] | No | 3-5 steps. Section hidden if absent |
whyConversations | WhyConversationsPoint[] | No | 3-4 points. Section hidden if absent |
relatedAgents | RelatedAgentLink[] | No | 3-5 links. Section hidden if absent |
customSections | CustomSectionContent[] | No | Inserted between Features and IdealFor |
howToSteps | HowToSchemaStep[] | No | JSON-LD only — not rendered visually |
statHighlights | StatHighlight[] | No | Used in stats-highlight custom sections |
internalLinks | Array<{anchorText, href, context}> | No | Registry for auditing internal link coverage |
Hard Constraints
No testimonials or fabricated reviews.
Do not add testimonial quotes, star ratings, or any content presented as a real user review. Google's spam policies can penalise AI-generated review content. If you want to show social proof, use StatHighlight with a cited source.
StatHighlight.source is optional but strongly recommended.
Any statistic should include a source string (e.g. "HBR, 2024") for credibility. Uncited statistics erode trust.
FAQ questions must be globally unique across all agent pages.
Duplicate FAQ questions across different agent pages are treated as duplicate content by Google and suppress rankings for both pages. Before adding a question, verify it does not appear in any other agent's FAQs.
HowItWorksStep.stepNumber must start at 1 and be sequential.
The step number is rendered in the numbered circle. Non-sequential numbers (e.g. 1, 2, 5) will display incorrectly.
CustomSectionContent uses a discriminated union — type determines the required data shape.
TypeScript will catch mismatches at compile time, but verify the data structure matches the type you declared.
Content Guidelines
Word Counts
| Section | Target |
|---|---|
HowItWorksStep.description | 30-60 words |
WhyConversationsPoint.description | 40-70 words |
WhyConversationsPoint.formComparison | 15-30 words |
RelatedAgentLink.reason | 20-40 words |
FAQItem.answer | 60-120 words |
IndustryUseCaseItem.description | 50-80 words |
Uniqueness
Every piece of content in an agent file should be specific to that agent's use case. Avoid generic phrases that could apply to any agent.
Bad (generic): "Our AI helps you save time and work more efficiently."
Good (specific to booking-assistant): "Visitors describe what they need and the AI recommends the right service, duration, and price. No scrolling through menus or guessing which option fits."
Internal Links
Each agent page should contain 5-8 internal links total across all content sections. Include links naturally inside FAQ answers, contextSentence fields, and any custom section HTML.
Useful internal link targets:
| Destination | Path |
|---|---|
| All AI agents listing | /ai-agents |
| Specific agent landing pages | /ai-agents/{slug} |
| Pricing page | /pricing |
| Agent builder (by type) | /build/{slug} |
| Guides | /guides/{guide-slug} |
Track your internal links in the internalLinks array. This field has no runtime effect — it serves as an audit trail.
HowTo Steps vs. Visual Steps
Two separate fields exist for step-by-step content:
howItWorks— the visual timeline rendered byUseCaseHowItWorks. Describes the product experience in 3-5 user-facing steps.howToSteps— the JSON-LD HowTo schema injected into<head>for Google rich results. Describes actionable instructions a user follows to complete a task. These can differ in tone and detail from the visual steps.
Both are optional but having howToSteps (3-5 steps) qualifies the page for HowTo rich results in Google Search.
How to Create Content for a New Agent
Follow this checklist when writing an expanded content file for a new or unfinished agent page.
Before you start:
- [ ] Confirm the agent slug exists in
AgentPurposeSlugtype - [ ] Read the core config entry in
useAgentPurposes.tsto understand what is already covered - [ ] Read two existing content files (e.g.
booking-assistant.ts,customer-support.ts) to calibrate tone and depth
Write the content file:
- [ ] Create
app/data/agents/{slug}.ts - [ ] Set
slugto match exactly - [ ] Set
lastUpdatedto today's date (ISO format) - [ ] Write
sectionTitleOverridesfor at leastfeatures,faq, andwhyConversations - [ ] Write
howItWorkswith 3-5 steps specific to this agent - [ ] Write
whyConversationswith 3-4 points that argue for conversations over forms (agent-specific) - [ ] Write
relatedAgentswith 3-5 links to complementary agent pages - [ ] Write
additionalFAQswith 5-8 questions not already covered by core config - [ ] Check that no FAQ question duplicates questions in other agent files
- [ ] Add at least one
customSection(e.g.industry-use-casesorcomparison) - [ ] Write
howToStepsfor JSON-LD (3-5 actionable steps) - [ ] Populate
internalLinkswith all links embedded in the content - [ ] Verify link count: 5-8 internal links total
Register and verify:
- [ ] Add entry to
contentModulesinapp/data/agents/index.ts - [ ] Run
npx nuxi typecheckto verify no TypeScript errors - [ ] Open the page locally at
/ai-agents/{slug}and visually inspect each section
Component Mapping
This table shows which AgentExpandedContent fields drive which rendered sections on the page.
| Content Field | Rendered Section | Component | Condition |
|---|---|---|---|
whyConversations | Why Conversations | UseCaseWhyConversations | Shown if field is present and non-empty |
howItWorks | How It Works | UseCaseHowItWorks | Shown if field is present and non-empty |
relatedAgents | Related Agents | UseCaseRelatedAgents | Shown if field is present and non-empty |
customSections | Custom section(s) | Dynamic by type | Each item renders one section |
additionalFAQs | FAQ section | Merged into core FAQ list | Always present; additional items appended |
additionalFeatures | Features section | Merged into core features list | Always present; additional items appended |
sectionTitleOverrides | Multiple sections | Replaces computed defaults | Applied to each named section if override exists |
howToSteps | None (schema only) | JSON-LD in <head> | Injected when present |
Custom section types and their components:
type | What renders |
|---|---|
how-it-works | Another UseCaseHowItWorks instance with custom title |
why-conversations | Another UseCaseWhyConversations instance with custom title |
stats-highlight | Statistics grid with optional source attribution |
industry-use-cases | Industry-specific use case cards |
comparison | Side-by-side traditional vs. Gnosari comparison table |
custom | Raw HTML block (use sparingly; ensure content is sanitized) |
Merge Utility Behavior
getExpandedContent(slug) in app/data/agents/index.ts dynamically imports the content file for the given slug. The module is lazy-loaded — it is only bundled into the page chunk for the specific agent, not all pages.
import('app/data/agents/booking-assistant.ts')
→ { default: AgentExpandedContent }If no entry exists in contentModules for the slug, the function returns null. The page renders with core config only — no errors.
Merge rules applied in [slug].vue:
| Core data | Expanded data | Merged result |
|---|---|---|
core.faq | expanded.additionalFAQs | [...core.faq, ...expanded.additionalFAQs] |
core.features | expanded.additionalFeatures | [...core.features, ...expanded.additionalFeatures] |
core.sectionTitles[x] | expanded.sectionTitleOverrides[x] | Override wins if present, core default otherwise |
| — | expanded.howItWorks | Section rendered if present |
| — | expanded.whyConversations | Section rendered if present |
| — | expanded.relatedAgents | Resolved to RelatedAgentDisplay[], section rendered if present |
| — | expanded.customSections | Each item renders in order, inserted between Features and IdealFor |
The page renders the same 10-section structure regardless of whether expanded content exists — additional sections simply appear or are skipped based on presence of data.
Troubleshooting
TypeScript: Type error on slug
Type '"my-new-agent"' is not assignable to type 'AgentPurposeSlug'The slug must first be added to the AgentPurposeSlug union type in app/types/domains/agent-purpose.ts. Add it there, then use it in your content file.
TypeScript: Type error on customSections[].data
Each CustomSectionContent variant has a different required data shape. The type field is the discriminant. Example:
typescript
// Correct
{ type: 'industry-use-cases', data: { items: [...] } }
// Wrong — 'items' is not valid for type 'how-it-works'
{ type: 'how-it-works', data: { items: [...] } }
// Should be:
{ type: 'how-it-works', data: { steps: [...] } }Section not rendering despite data being present
Check that:
- The content file is registered in
contentModulesinapp/data/agents/index.ts - The field array is non-empty (an empty array
[]causes the section to be skipped) - The slug in the content file matches the URL slug exactly
RelatedAgents section shows wrong names or icons
relatedAgents in the content file stores only slug and reason. The page resolves them to RelatedAgentDisplay (name, tagline, icon, href) by looking up each slug in useAgentPurposes. If a slug does not exist in useAgentPurposes, that agent is silently dropped from the related agents section. Verify each related agent slug is a valid AgentPurposeSlug.
FAQ duplicate content concern
If two agent pages have the same or very similar FAQ question, Google may apply a duplicate content signal to both pages. Each FAQ question must be answerable only from the context of that specific agent. If you find yourself writing the same question for two agents, rewrite it to be agent-specific or remove one instance.