Skip to content

Commit 910c531

Browse files
authored
Improve homepage structured data and fix schema issues (#16182)
* Fix structured data issues and enhance homepage schemas Critical Fixes: - Fix headline extraction to use h1 parameter instead of title - Remove speakable schemas (not needed for developer tools) - Fix inaccurate product data (remove hardcoded dates/versions) - Remove generic 'about Pulumi CLI' from all articles Homepage Enhancements: - Add complete Organization schema with address, founders, founding date - Add knowledge graph links (Wikidata Q122864080, Wikipedia, Crunchbase) - Represent Pulumi as platform (not just CLI) with all pricing tiers - Add Infrastructure as Code entity relationships Results: - Headlines now match visible content (e.g., 'Configure access to AWS' vs 'Configure access') - Better knowledge panel eligibility with complete corporate data - Accurate product pricing and requirements - Valid JSON-LD that passes schema.org validation * Add breadcrumb support for Insights, ESC, Copilot, and CrossGuard sections - Add Pulumi Insights breadcrumb for /docs/insights/ pages - Add Pulumi ESC breadcrumb for /docs/esc/ pages - Add Pulumi Copilot breadcrumb for /docs/copilot/ pages - Add Pulumi CrossGuard breadcrumb for /docs/crossguard/ pages - Fix nested if-else structure for proper section detection This ensures proper breadcrumb hierarchy for all documentation sections: - Before: Home > Documentation > [Page Title] - After: Home > Documentation > [Product Section] > [Page Title] Validated with Insights page showing correct 4-level breadcrumb. * Refine structured data implementation based on thorough review Headlines: - Revert to using .Title instead of .Params.h1 for more specific headlines - H1 is often too broad (e.g., 'Get started with Pulumi and AWS' vs 'Configure access') - Google recommends concise, specific headlines Date Fields: - Remove datePublished/dateModified from docs and tutorials (evergreen content) - Keep dates for blog posts where they're meaningful - Google confirms dates are optional, only useful when meaningful Organization Schema: - Change from array type to single 'Corporation' type (more specific per Google) - Corporation inherits all Organization properties - Maintains all rich details (address, founders, knowledge graph links) Breadcrumbs: - Fix Copilot path (actually at /docs/pulumi-cloud/copilot/) - Replace with Pulumi Cloud breadcrumb for correct hierarchy HowTo Steps: - Remove generic, unhelpful steps from tutorials - Better to have no steps than fake generic ones - Future improvement: extract real steps from content structure Results: - More accurate, specific headlines for better search visibility - Cleaner schema without meaningless dates on docs - Proper breadcrumb hierarchy for all sections - No misleading generic content * Fix structured data issues identified in PR review - Fix softwareVersion to read from static file instead of missing config param Changed from .Site.Params.latest_version to (readFile "static/latest-version") This matches how the site handles versions throughout via shortcodes - Add clarifying comments to breadcrumb nesting logic Makes the complex nested structure more maintainable Clearly separates main section logic from IaC Clouds additional nesting - YouTube URL verified as correct (using permanent channel ID format) These fixes address all issues identified in PR #16182 review: ✓ Critical: Fixed missing site parameter issue ✓ Minor: Added comments for breadcrumb clarity ✓ Verified: YouTube URL is using stable channel ID format * Enhance structured data for improved AI citations - Implement FAQPage schema for all FAQ pages (critical for AI visibility) - Integrate existing VideoObject schema into @graph structure - Add SOC 2 Type II certification as trust signal to Organization - Extract real HowTo steps from markdown content instead of placeholders - Auto-detect FAQ pages by URL pattern for future-proofing These changes optimize structured data for citations in AI tools (ChatGPT, Perplexity, Google AI Overviews) by providing detailed, factual schema markup that these systems can understand and cite. * Fix trim function syntax errors in Hugo templates Changed all trim function calls to strings.TrimSpace which doesn't require a cutset argument. This fixes the Hugo build error: 'wrong number of args for trim: want 2 got 1' * Fix Hugo 0.135.0 compatibility - use trim with whitespace cutset The CI uses Hugo 0.135.0 but strings.TrimSpace was introduced in v0.136.3. Replaced all strings.TrimSpace calls with trim " \t\n\r" which provides the same functionality and is compatible with Hugo 0.135.0. This fixes the build error: 'can't evaluate field TrimSpace in type interface {}'
1 parent b714f62 commit 910c531

File tree

9 files changed

+466
-152
lines changed

9 files changed

+466
-152
lines changed

layouts/partials/schema/collectors/article-entity.html

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,7 @@
1717
"inLanguage" "en-US"
1818
}}
1919

20-
{{/* Add dates with proper fallback handling */}}
21-
{{ $publishDate := .PublishDate }}
22-
{{ $modifiedDate := .Lastmod }}
23-
24-
{{/* Fix invalid dates */}}
25-
{{ if or (not $publishDate) (eq ($publishDate.Format "2006-01-02") "0001-01-01") }}
26-
{{ if and .GitInfo .GitInfo.AuthorDate }}
27-
{{ $publishDate = .GitInfo.AuthorDate }}
28-
{{ else }}
29-
{{ $publishDate = now }}
30-
{{ end }}
31-
{{ end }}
32-
33-
{{ if or (not $modifiedDate) (eq ($modifiedDate.Format "2006-01-02") "0001-01-01") }}
34-
{{ if and .GitInfo .GitInfo.AuthorDate }}
35-
{{ $modifiedDate = .GitInfo.AuthorDate }}
36-
{{ else }}
37-
{{ $modifiedDate = $publishDate }}
38-
{{ end }}
39-
{{ end }}
40-
41-
{{ $schema = merge $schema (dict
42-
"datePublished" ($publishDate.Format "2006-01-02T15:04:05Z07:00")
43-
"dateModified" ($modifiedDate.Format "2006-01-02T15:04:05Z07:00")
44-
) }}
20+
{{/* Note: Dates omitted for documentation pages - they're evergreen content without meaningful publication dates */}}
4521

4622
{{/* Add description */}}
4723
{{ with (or .Params.meta_desc .Summary) }}
@@ -87,19 +63,8 @@
8763
"publisher" (dict "@id" "https://www.pulumi.com/#organization")
8864
) }}
8965

90-
{{/* Add speakable for voice search */}}
91-
{{ $schema = merge $schema (dict "speakable" (dict
92-
"@type" "SpeakableSpecification"
93-
"cssSelector" (slice "h1" "h2" "p:first-of-type")
94-
)) }}
95-
96-
{{/* Add about field */}}
97-
{{ $schema = merge $schema (dict
98-
"about" (dict
99-
"@type" "SoftwareApplication"
100-
"@id" "https://www.pulumi.com/#pulumi-cli"
101-
)
102-
) }}
66+
{{/* Note: Removed speakable schema - not needed for developer tools */}}
67+
{{/* Note: Removed generic about field - was incorrectly claiming all articles are about Pulumi CLI */}}
10368

10469
{{/* Add article body (truncated for schema) */}}
10570
{{ $articleBody := $content | truncate 5000 }}

layouts/partials/schema/collectors/blog-entity.html

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,8 @@
103103
"publisher" (dict "@id" "https://www.pulumi.com/#organization")
104104
) }}
105105

106-
{{/* Add speakable for voice search */}}
107-
{{ $schema = merge $schema (dict "speakable" (dict
108-
"@type" "SpeakableSpecification"
109-
"cssSelector" (slice "article h1" "article h2" "article p:first-of-type")
110-
)) }}
111-
112-
{{/* Add about field for main topic */}}
113-
{{ $schema = merge $schema (dict
114-
"about" (dict
115-
"@type" "Thing"
116-
"@id" "https://www.pulumi.com/#pulumi-cli"
117-
"name" "Infrastructure as Code"
118-
)
119-
) }}
106+
{{/* Note: Removed speakable schema - not needed for developer tools */}}
107+
{{/* Note: Removed generic about field - was incorrectly claiming all blog posts are about Pulumi CLI */}}
120108

121109
{{/* Add article body (truncated for schema) */}}
122110
{{ $articleBody := $content | truncate 5000 }}

layouts/partials/schema/collectors/breadcrumb-entity.html

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,51 @@
5151
"item" "https://www.pulumi.com/docs/iac/"
5252
) }}
5353

54-
{{/* Add Clouds if in clouds section */}}
55-
{{ if in .RelPermalink "/docs/iac/clouds/" }}
54+
{{/* Add Pulumi Insights if in the docs/insights section */}}
55+
{{ else if in .RelPermalink "/docs/insights/" }}
56+
{{ $position = add $position 1 }}
57+
{{ $breadcrumbs = $breadcrumbs | append (dict
58+
"@type" "ListItem"
59+
"position" $position
60+
"name" "Pulumi Insights"
61+
"item" "https://www.pulumi.com/docs/insights/"
62+
) }}
63+
64+
{{/* Add Pulumi ESC if in the docs/esc section */}}
65+
{{ else if in .RelPermalink "/docs/esc/" }}
66+
{{ $position = add $position 1 }}
67+
{{ $breadcrumbs = $breadcrumbs | append (dict
68+
"@type" "ListItem"
69+
"position" $position
70+
"name" "Pulumi ESC"
71+
"item" "https://www.pulumi.com/docs/esc/"
72+
) }}
73+
74+
{{/* Add Pulumi Cloud if in the docs/pulumi-cloud section */}}
75+
{{ else if in .RelPermalink "/docs/pulumi-cloud/" }}
76+
{{ $position = add $position 1 }}
77+
{{ $breadcrumbs = $breadcrumbs | append (dict
78+
"@type" "ListItem"
79+
"position" $position
80+
"name" "Pulumi Cloud"
81+
"item" "https://www.pulumi.com/docs/pulumi-cloud/"
82+
) }}
83+
84+
{{/* Add Pulumi CrossGuard if in the docs/crossguard section */}}
85+
{{ else if in .RelPermalink "/docs/crossguard/" }}
86+
{{ $position = add $position 1 }}
87+
{{ $breadcrumbs = $breadcrumbs | append (dict
88+
"@type" "ListItem"
89+
"position" $position
90+
"name" "Pulumi CrossGuard"
91+
"item" "https://www.pulumi.com/docs/crossguard/"
92+
) }}
93+
94+
{{/* End of main docs sections (IaC, Insights, ESC, Pulumi Cloud, CrossGuard) */}}
95+
{{ end }}
96+
97+
{{/* Additional nesting for IaC Clouds section only - this is separate from the main section logic above */}}
98+
{{ if and (in .RelPermalink "/docs/iac/") (in .RelPermalink "/docs/iac/clouds/") }}
5699
{{ $position = add $position 1 }}
57100
{{ $breadcrumbs = $breadcrumbs | append (dict
58101
"@type" "ListItem"
@@ -82,8 +125,9 @@
82125
) }}
83126
{{ end }}
84127
{{ end }}
128+
129+
{{/* End of IaC Clouds section additional nesting */}}
85130
{{ end }}
86-
{{ end }}
87131

88132
{{ else if eq .Section "product" }}
89133
{{ $position = add $position 1 }}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
{{/* Returns FAQPage entity for FAQ pages in @graph */}}
2+
3+
{{/* Initialize FAQ schema */}}
4+
{{ $schema := dict
5+
"@type" "FAQPage"
6+
"@id" "#main-content"
7+
"url" .Permalink
8+
"name" (or .Params.title_tag .Title)
9+
"inLanguage" "en-US"
10+
}}
11+
12+
{{/* Add description if available */}}
13+
{{ with (or .Params.meta_desc .Summary) }}
14+
{{ $schema = merge $schema (dict "description" .) }}
15+
{{ end }}
16+
17+
{{/* Extract Q&A pairs from content */}}
18+
{{ $questions := slice }}
19+
{{ $rawContent := .RawContent }}
20+
21+
{{/* Parse markdown content for questions and answers */}}
22+
{{/* Questions are typically ### headers, answers are the content following them */}}
23+
{{ $lines := split $rawContent "\n" }}
24+
{{ $currentQuestion := "" }}
25+
{{ $currentAnswer := slice }}
26+
{{ $inQuestion := false }}
27+
28+
{{ range $index, $line := $lines }}
29+
{{/* Check if this is a question (### header) */}}
30+
{{ if strings.HasPrefix $line "### " }}
31+
{{/* Save previous Q&A if exists */}}
32+
{{ if and $currentQuestion (gt (len $currentAnswer) 0) }}
33+
{{ $answerText := delimit $currentAnswer " " | replaceRE "\\s+" " " | trim " \t\n\r" }}
34+
{{ if $answerText }}
35+
{{ $qa := dict
36+
"@type" "Question"
37+
"name" (strings.TrimPrefix "### " $currentQuestion | trim " \t\n\r")
38+
"acceptedAnswer" (dict
39+
"@type" "Answer"
40+
"text" $answerText
41+
)
42+
}}
43+
{{ $questions = $questions | append $qa }}
44+
{{ end }}
45+
{{ end }}
46+
{{/* Start new question */}}
47+
{{ $currentQuestion = $line }}
48+
{{ $currentAnswer = slice }}
49+
{{ $inQuestion = true }}
50+
{{ else if strings.HasPrefix $line "## " }}
51+
{{/* New section, save previous Q&A if exists */}}
52+
{{ if and $currentQuestion (gt (len $currentAnswer) 0) }}
53+
{{ $answerText := delimit $currentAnswer " " | replaceRE "\\s+" " " | trim " \t\n\r" }}
54+
{{ if $answerText }}
55+
{{ $qa := dict
56+
"@type" "Question"
57+
"name" (strings.TrimPrefix "### " $currentQuestion | trim " \t\n\r")
58+
"acceptedAnswer" (dict
59+
"@type" "Answer"
60+
"text" $answerText
61+
)
62+
}}
63+
{{ $questions = $questions | append $qa }}
64+
{{ end }}
65+
{{ end }}
66+
{{ $currentQuestion = "" }}
67+
{{ $currentAnswer = slice }}
68+
{{ $inQuestion = false }}
69+
{{ else if $inQuestion }}
70+
{{/* Add to current answer if we're in a question and line isn't empty */}}
71+
{{ $trimmedLine := trim $line " \t\n\r" }}
72+
{{ if $trimmedLine }}
73+
{{/* Skip markdown links to other FAQ sections */}}
74+
{{ if not (and (strings.Contains $trimmedLine "[") (strings.Contains $trimmedLine "/faq/")) }}
75+
{{ $currentAnswer = $currentAnswer | append $trimmedLine }}
76+
{{ end }}
77+
{{ end }}
78+
{{ end }}
79+
{{ end }}
80+
81+
{{/* Save last Q&A if exists */}}
82+
{{ if and $currentQuestion (gt (len $currentAnswer) 0) }}
83+
{{ $answerText := delimit $currentAnswer " " | replaceRE "\\s+" " " | trim " \t\n\r" }}
84+
{{ if $answerText }}
85+
{{ $qa := dict
86+
"@type" "Question"
87+
"name" (strings.TrimPrefix "### " $currentQuestion | trim " \t\n\r")
88+
"acceptedAnswer" (dict
89+
"@type" "Answer"
90+
"text" $answerText
91+
)
92+
}}
93+
{{ $questions = $questions | append $qa }}
94+
{{ end }}
95+
{{ end }}
96+
97+
{{/* Add mainEntity with questions if we found any */}}
98+
{{ if $questions }}
99+
{{ $schema = merge $schema (dict "mainEntity" $questions) }}
100+
{{ end }}
101+
102+
{{/* Add publisher reference */}}
103+
{{ $schema = merge $schema (dict
104+
"publisher" (dict "@id" "https://www.pulumi.com/#organization")
105+
) }}
106+
107+
{{/* Add breadcrumb reference if it exists */}}
108+
{{ $schema = merge $schema (dict
109+
"breadcrumb" (dict "@id" (printf "%s#breadcrumb" .Permalink))
110+
) }}
111+
112+
{{ return $schema }}

0 commit comments

Comments
 (0)