better_seo 0.14.0 → 1.0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/README.md +297 -179
- data/docs/00_OVERVIEW.md +472 -0
- data/docs/01_CORE_AND_CONFIGURATION.md +913 -0
- data/docs/02_META_TAGS_AND_OPEN_GRAPH.md +251 -0
- data/docs/03_STRUCTURED_DATA.md +140 -0
- data/docs/04_SITEMAP_AND_ROBOTS.md +131 -0
- data/docs/05_RAILS_INTEGRATION.md +175 -0
- data/docs/06_I18N_PAGE_GENERATOR.md +233 -0
- data/docs/07_IMAGE_OPTIMIZATION.md +260 -0
- data/docs/DEPENDENCIES.md +383 -0
- data/docs/README.md +180 -0
- data/docs/TESTING_STRATEGY.md +663 -0
- data/lib/better_seo/analytics/google_analytics.rb +83 -0
- data/lib/better_seo/analytics/google_tag_manager.rb +74 -0
- data/lib/better_seo/configuration.rb +316 -0
- data/lib/better_seo/dsl/base.rb +86 -0
- data/lib/better_seo/dsl/meta_tags.rb +55 -0
- data/lib/better_seo/dsl/open_graph.rb +109 -0
- data/lib/better_seo/dsl/twitter_cards.rb +131 -0
- data/lib/better_seo/errors.rb +31 -0
- data/lib/better_seo/generators/amp_generator.rb +83 -0
- data/lib/better_seo/generators/breadcrumbs_generator.rb +126 -0
- data/lib/better_seo/generators/canonical_url_manager.rb +106 -0
- data/lib/better_seo/generators/meta_tags_generator.rb +100 -0
- data/lib/better_seo/generators/open_graph_generator.rb +110 -0
- data/lib/better_seo/generators/robots_txt_generator.rb +102 -0
- data/lib/better_seo/generators/twitter_cards_generator.rb +102 -0
- data/lib/better_seo/image/optimizer.rb +143 -0
- data/lib/better_seo/rails/helpers/controller_helpers.rb +118 -0
- data/lib/better_seo/rails/helpers/seo_helper.rb +176 -0
- data/lib/better_seo/rails/helpers/structured_data_helper.rb +123 -0
- data/lib/better_seo/rails/model_helpers.rb +62 -0
- data/lib/better_seo/rails/railtie.rb +22 -0
- data/lib/better_seo/sitemap/builder.rb +65 -0
- data/lib/better_seo/sitemap/generator.rb +57 -0
- data/lib/better_seo/sitemap/sitemap_index.rb +73 -0
- data/lib/better_seo/sitemap/url_entry.rb +157 -0
- data/lib/better_seo/structured_data/article.rb +55 -0
- data/lib/better_seo/structured_data/base.rb +73 -0
- data/lib/better_seo/structured_data/breadcrumb_list.rb +49 -0
- data/lib/better_seo/structured_data/event.rb +207 -0
- data/lib/better_seo/structured_data/faq_page.rb +55 -0
- data/lib/better_seo/structured_data/generator.rb +75 -0
- data/lib/better_seo/structured_data/how_to.rb +96 -0
- data/lib/better_seo/structured_data/local_business.rb +94 -0
- data/lib/better_seo/structured_data/organization.rb +67 -0
- data/lib/better_seo/structured_data/person.rb +51 -0
- data/lib/better_seo/structured_data/product.rb +123 -0
- data/lib/better_seo/structured_data/recipe.rb +135 -0
- data/lib/better_seo/validators/seo_recommendations.rb +165 -0
- data/lib/better_seo/validators/seo_validator.rb +195 -0
- data/lib/better_seo/version.rb +1 -1
- data/lib/better_seo.rb +1 -0
- data/lib/generators/better_seo/install_generator.rb +21 -0
- data/lib/generators/better_seo/templates/README +29 -0
- data/lib/generators/better_seo/templates/better_seo.rb +40 -0
- metadata +55 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterSeo
|
|
4
|
+
module Validators
|
|
5
|
+
class SeoRecommendations
|
|
6
|
+
def generate_recommendations(validation_result)
|
|
7
|
+
recommendations = []
|
|
8
|
+
|
|
9
|
+
recommendations += recommend_title_improvements(validation_result[:title])
|
|
10
|
+
recommendations += recommend_description_improvements(validation_result[:description])
|
|
11
|
+
recommendations += recommend_heading_improvements(validation_result[:headings])
|
|
12
|
+
recommendations += recommend_image_improvements(validation_result[:images])
|
|
13
|
+
|
|
14
|
+
# Sort by priority: high, medium, low
|
|
15
|
+
priority_order = { high: 0, medium: 1, low: 2 }
|
|
16
|
+
recommendations.sort_by { |r| priority_order[r[:priority]] }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def recommend_title_improvements(title_result)
|
|
20
|
+
return [] if title_result[:valid]
|
|
21
|
+
|
|
22
|
+
recommendations = []
|
|
23
|
+
length = title_result[:length]
|
|
24
|
+
|
|
25
|
+
if length == 0
|
|
26
|
+
recommendations << {
|
|
27
|
+
category: "Title",
|
|
28
|
+
priority: :high,
|
|
29
|
+
action: "Add a page title",
|
|
30
|
+
details: "A title tag is required for SEO. Aim for 30-60 characters."
|
|
31
|
+
}
|
|
32
|
+
elsif length < 30
|
|
33
|
+
needed = 30 - length
|
|
34
|
+
recommendations << {
|
|
35
|
+
category: "Title",
|
|
36
|
+
priority: :high,
|
|
37
|
+
action: "Make title longer",
|
|
38
|
+
details: "Add approximately #{needed} more characters to reach the optimal length (30-60 chars)."
|
|
39
|
+
}
|
|
40
|
+
elsif length > 60
|
|
41
|
+
excess = length - 60
|
|
42
|
+
recommendations << {
|
|
43
|
+
category: "Title",
|
|
44
|
+
priority: :medium,
|
|
45
|
+
action: "Make title shorter",
|
|
46
|
+
details: "Reduce by approximately #{excess} characters to stay within optimal length (30-60 chars)."
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
recommendations
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def recommend_description_improvements(desc_result)
|
|
54
|
+
return [] if desc_result[:valid]
|
|
55
|
+
|
|
56
|
+
recommendations = []
|
|
57
|
+
length = desc_result[:length]
|
|
58
|
+
|
|
59
|
+
if length == 0
|
|
60
|
+
recommendations << {
|
|
61
|
+
category: "Meta Description",
|
|
62
|
+
priority: :high,
|
|
63
|
+
action: "Add a meta description",
|
|
64
|
+
details: "Meta descriptions help search engines understand your content. Aim for 120-160 characters."
|
|
65
|
+
}
|
|
66
|
+
elsif length < 120
|
|
67
|
+
needed = 120 - length
|
|
68
|
+
recommendations << {
|
|
69
|
+
category: "Meta Description",
|
|
70
|
+
priority: :medium,
|
|
71
|
+
action: "Add more detail to description",
|
|
72
|
+
details: "Add approximately #{needed} more characters to provide more context (optimal: 120-160 chars)."
|
|
73
|
+
}
|
|
74
|
+
elsif length > 160
|
|
75
|
+
excess = length - 160
|
|
76
|
+
recommendations << {
|
|
77
|
+
category: "Meta Description",
|
|
78
|
+
priority: :medium,
|
|
79
|
+
action: "Condense description",
|
|
80
|
+
details: "Reduce by approximately #{excess} characters to prevent truncation in search results."
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
recommendations
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def recommend_heading_improvements(headings_result)
|
|
88
|
+
return [] if headings_result[:valid]
|
|
89
|
+
|
|
90
|
+
recommendations = []
|
|
91
|
+
h1_count = headings_result[:h1_count]
|
|
92
|
+
|
|
93
|
+
if h1_count == 0
|
|
94
|
+
recommendations << {
|
|
95
|
+
category: "Headings",
|
|
96
|
+
priority: :high,
|
|
97
|
+
action: "Add an H1 heading",
|
|
98
|
+
details: "Every page should have exactly one H1 tag that describes the main topic."
|
|
99
|
+
}
|
|
100
|
+
elsif h1_count > 1
|
|
101
|
+
recommendations << {
|
|
102
|
+
category: "Headings",
|
|
103
|
+
priority: :high,
|
|
104
|
+
action: "Use only one H1 heading",
|
|
105
|
+
details: "Found #{h1_count} H1 tags. Use H2-H6 for subheadings instead."
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if headings_result[:total_headings] == 0
|
|
110
|
+
recommendations << {
|
|
111
|
+
category: "Headings",
|
|
112
|
+
priority: :medium,
|
|
113
|
+
action: "Add heading structure",
|
|
114
|
+
details: "Use headings (H1-H6) to organize content and improve readability."
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
recommendations
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def recommend_image_improvements(images_result)
|
|
122
|
+
return [] if images_result[:valid]
|
|
123
|
+
|
|
124
|
+
recommendations = []
|
|
125
|
+
|
|
126
|
+
if images_result[:images_without_alt] > 0
|
|
127
|
+
count = images_result[:images_without_alt]
|
|
128
|
+
recommendations << {
|
|
129
|
+
category: "Images",
|
|
130
|
+
priority: :medium,
|
|
131
|
+
action: "Add alt text to images",
|
|
132
|
+
details: "#{count} image#{count > 1 ? 's' : ''} missing alt text. Alt text improves accessibility and SEO."
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
recommendations
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def format_recommendations(recommendations)
|
|
140
|
+
return "# SEO Recommendations\n\nNo recommendations - your SEO is optimal! ✓" if recommendations.empty?
|
|
141
|
+
|
|
142
|
+
output = []
|
|
143
|
+
output << "# SEO Recommendations\n"
|
|
144
|
+
|
|
145
|
+
# Group by priority
|
|
146
|
+
grouped = recommendations.group_by { |r| r[:priority] }
|
|
147
|
+
|
|
148
|
+
[:high, :medium, :low].each do |priority|
|
|
149
|
+
next unless grouped[priority]
|
|
150
|
+
|
|
151
|
+
output << "## #{priority.to_s.capitalize} Priority\n"
|
|
152
|
+
|
|
153
|
+
grouped[priority].each do |rec|
|
|
154
|
+
output << "### #{rec[:category]}"
|
|
155
|
+
output << "**Action:** #{rec[:action]}\n"
|
|
156
|
+
output << "**Details:** #{rec[:details]}\n" if rec[:details]
|
|
157
|
+
output << ""
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
output.join("\n")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterSeo
|
|
4
|
+
module Validators
|
|
5
|
+
class SeoValidator
|
|
6
|
+
TITLE_MIN_LENGTH = 30
|
|
7
|
+
TITLE_MAX_LENGTH = 60
|
|
8
|
+
DESCRIPTION_MIN_LENGTH = 120
|
|
9
|
+
DESCRIPTION_MAX_LENGTH = 160
|
|
10
|
+
|
|
11
|
+
def check_title(title)
|
|
12
|
+
return { valid: false, score: 0, message: "Title is required", length: 0 } if title.nil? || title.empty?
|
|
13
|
+
|
|
14
|
+
length = title.length
|
|
15
|
+
|
|
16
|
+
if length >= TITLE_MIN_LENGTH && length <= TITLE_MAX_LENGTH
|
|
17
|
+
{ valid: true, score: 100, message: "Title length is optimal", length: length }
|
|
18
|
+
elsif length < TITLE_MIN_LENGTH
|
|
19
|
+
score = (length.to_f / TITLE_MIN_LENGTH * 70).to_i
|
|
20
|
+
{ valid: false, score: score, message: "Title is too short (minimum #{TITLE_MIN_LENGTH} characters recommended)", length: length }
|
|
21
|
+
else
|
|
22
|
+
score = [100 - (length - TITLE_MAX_LENGTH) * 2, 50].max
|
|
23
|
+
{ valid: false, score: score, message: "Title is too long (maximum #{TITLE_MAX_LENGTH} characters recommended)", length: length }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def check_description(description)
|
|
28
|
+
return { valid: false, score: 0, message: "Description is required", length: 0 } if description.nil? || description.empty?
|
|
29
|
+
|
|
30
|
+
length = description.length
|
|
31
|
+
|
|
32
|
+
if length >= DESCRIPTION_MIN_LENGTH && length <= DESCRIPTION_MAX_LENGTH
|
|
33
|
+
{ valid: true, score: 100, message: "Description length is optimal", length: length }
|
|
34
|
+
elsif length < DESCRIPTION_MIN_LENGTH
|
|
35
|
+
score = (length.to_f / DESCRIPTION_MIN_LENGTH * 70).to_i
|
|
36
|
+
{ valid: false, score: score, message: "Description is too short (minimum #{DESCRIPTION_MIN_LENGTH} characters recommended)", length: length }
|
|
37
|
+
else
|
|
38
|
+
score = [100 - (length - DESCRIPTION_MAX_LENGTH), 50].max
|
|
39
|
+
{ valid: false, score: score, message: "Description is too long (maximum #{DESCRIPTION_MAX_LENGTH} characters recommended)", length: length }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def check_headings(html)
|
|
44
|
+
h1_count = html.scan(/<h1[^>]*>/).size
|
|
45
|
+
h2_count = html.scan(/<h2[^>]*>/).size
|
|
46
|
+
h3_count = html.scan(/<h3[^>]*>/).size
|
|
47
|
+
h4_count = html.scan(/<h4[^>]*>/).size
|
|
48
|
+
h5_count = html.scan(/<h5[^>]*>/).size
|
|
49
|
+
h6_count = html.scan(/<h6[^>]*>/).size
|
|
50
|
+
|
|
51
|
+
total = h1_count + h2_count + h3_count + h4_count + h5_count + h6_count
|
|
52
|
+
|
|
53
|
+
result = {
|
|
54
|
+
h1_count: h1_count,
|
|
55
|
+
h2_count: h2_count,
|
|
56
|
+
h3_count: h3_count,
|
|
57
|
+
total_headings: total
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if h1_count == 0
|
|
61
|
+
result.merge(valid: false, score: 50, message: "Missing H1 heading (exactly one H1 required)")
|
|
62
|
+
elsif h1_count > 1
|
|
63
|
+
result.merge(valid: false, score: 70, message: "Multiple H1 headings found (only one H1 recommended)")
|
|
64
|
+
elsif total == 0
|
|
65
|
+
result.merge(valid: false, score: 0, message: "No headings found")
|
|
66
|
+
else
|
|
67
|
+
result.merge(valid: true, score: 100, message: "Heading structure is optimal")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def check_images(html)
|
|
72
|
+
# Find all img tags
|
|
73
|
+
images = html.scan(/<img[^>]*>/)
|
|
74
|
+
total = images.size
|
|
75
|
+
|
|
76
|
+
return { valid: true, score: 100, total_images: 0, images_with_alt: 0, images_without_alt: 0, message: "No images to validate" } if total.zero?
|
|
77
|
+
|
|
78
|
+
# Count images with alt text
|
|
79
|
+
images_with_alt = images.count { |img| img =~ /alt=["'][^"']+["']/ }
|
|
80
|
+
images_without_alt = total - images_with_alt
|
|
81
|
+
|
|
82
|
+
result = {
|
|
83
|
+
total_images: total,
|
|
84
|
+
images_with_alt: images_with_alt,
|
|
85
|
+
images_without_alt: images_without_alt
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if images_without_alt.zero?
|
|
89
|
+
result.merge(valid: true, score: 100, message: "All images have alt text")
|
|
90
|
+
else
|
|
91
|
+
score = (images_with_alt.to_f / total * 100).to_i
|
|
92
|
+
result.merge(valid: false, score: score, message: "#{images_without_alt} image(s) missing alt text")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_page(html)
|
|
97
|
+
# Extract title
|
|
98
|
+
title_match = html.match(/<title[^>]*>(.*?)<\/title>/m)
|
|
99
|
+
title = title_match ? title_match[1].strip : nil
|
|
100
|
+
|
|
101
|
+
# Extract meta description
|
|
102
|
+
desc_match = html.match(/<meta\s+name=["']description["']\s+content=["'](.*?)["']/i)
|
|
103
|
+
description = desc_match ? desc_match[1] : nil
|
|
104
|
+
|
|
105
|
+
# Run all checks
|
|
106
|
+
title_result = check_title(title)
|
|
107
|
+
description_result = check_description(description)
|
|
108
|
+
headings_result = check_headings(html)
|
|
109
|
+
images_result = check_images(html)
|
|
110
|
+
|
|
111
|
+
# Calculate overall score (weighted average)
|
|
112
|
+
overall_score = (
|
|
113
|
+
title_result[:score] * 0.3 +
|
|
114
|
+
description_result[:score] * 0.3 +
|
|
115
|
+
headings_result[:score] * 0.2 +
|
|
116
|
+
images_result[:score] * 0.2
|
|
117
|
+
).to_i
|
|
118
|
+
|
|
119
|
+
# Collect issues
|
|
120
|
+
issues = []
|
|
121
|
+
issues << "Title: #{title_result[:message]}" unless title_result[:valid]
|
|
122
|
+
issues << "Description: #{description_result[:message]}" unless description_result[:valid]
|
|
123
|
+
issues << "Headings: #{headings_result[:message]}" unless headings_result[:valid]
|
|
124
|
+
issues << "Images: #{images_result[:message]}" unless images_result[:valid]
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
overall_score: overall_score,
|
|
128
|
+
title: title_result,
|
|
129
|
+
description: description_result,
|
|
130
|
+
headings: headings_result,
|
|
131
|
+
images: images_result,
|
|
132
|
+
issues: issues
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def generate_report(validation)
|
|
137
|
+
report = []
|
|
138
|
+
report << "=" * 60
|
|
139
|
+
report << "SEO Validation Report"
|
|
140
|
+
report << "=" * 60
|
|
141
|
+
report << ""
|
|
142
|
+
report << "Overall Score: #{validation[:overall_score]}/100"
|
|
143
|
+
report << ""
|
|
144
|
+
|
|
145
|
+
# Title section
|
|
146
|
+
report << "Title:"
|
|
147
|
+
report << " Status: #{validation[:title][:valid] ? '✓' : '✗'}"
|
|
148
|
+
report << " Score: #{validation[:title][:score]}/100"
|
|
149
|
+
report << " Length: #{validation[:title][:length]} chars"
|
|
150
|
+
report << " Message: #{validation[:title][:message]}"
|
|
151
|
+
report << ""
|
|
152
|
+
|
|
153
|
+
# Description section
|
|
154
|
+
report << "Description:"
|
|
155
|
+
report << " Status: #{validation[:description][:valid] ? '✓' : '✗'}"
|
|
156
|
+
report << " Score: #{validation[:description][:score]}/100"
|
|
157
|
+
report << " Length: #{validation[:description][:length]} chars"
|
|
158
|
+
report << " Message: #{validation[:description][:message]}"
|
|
159
|
+
report << ""
|
|
160
|
+
|
|
161
|
+
# Headings section
|
|
162
|
+
report << "Headings:"
|
|
163
|
+
report << " Status: #{validation[:headings][:valid] ? '✓' : '✗'}"
|
|
164
|
+
report << " Score: #{validation[:headings][:score]}/100"
|
|
165
|
+
report << " H1 Count: #{validation[:headings][:h1_count]}"
|
|
166
|
+
report << " Total Headings: #{validation[:headings][:total_headings]}"
|
|
167
|
+
report << " Message: #{validation[:headings][:message]}"
|
|
168
|
+
report << ""
|
|
169
|
+
|
|
170
|
+
# Images section
|
|
171
|
+
report << "Images:"
|
|
172
|
+
report << " Status: #{validation[:images][:valid] ? '✓' : '✗'}"
|
|
173
|
+
report << " Score: #{validation[:images][:score]}/100"
|
|
174
|
+
report << " Total Images: #{validation[:images][:total_images]}"
|
|
175
|
+
report << " With Alt: #{validation[:images][:images_with_alt]}"
|
|
176
|
+
report << " Without Alt: #{validation[:images][:images_without_alt]}"
|
|
177
|
+
report << " Message: #{validation[:images][:message]}"
|
|
178
|
+
report << ""
|
|
179
|
+
|
|
180
|
+
# Issues section
|
|
181
|
+
if validation[:issues].any?
|
|
182
|
+
report << "Issues:"
|
|
183
|
+
validation[:issues].each do |issue|
|
|
184
|
+
report << " - #{issue}"
|
|
185
|
+
end
|
|
186
|
+
report << ""
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
report << "=" * 60
|
|
190
|
+
|
|
191
|
+
report.join("\n")
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
data/lib/better_seo/version.rb
CHANGED
data/lib/better_seo.rb
CHANGED
|
@@ -42,6 +42,7 @@ require_relative "better_seo/rails/helpers/seo_helper"
|
|
|
42
42
|
require_relative "better_seo/rails/helpers/structured_data_helper"
|
|
43
43
|
require_relative "better_seo/rails/helpers/controller_helpers"
|
|
44
44
|
require_relative "better_seo/rails/model_helpers"
|
|
45
|
+
require_relative "better_seo/rails/railtie" if defined?(Rails::Railtie)
|
|
45
46
|
|
|
46
47
|
module BetterSeo
|
|
47
48
|
class << self
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module BetterSeo
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Creates a BetterSeo initializer file"
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template "better_seo.rb", "config/initializers/better_seo.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def show_readme
|
|
17
|
+
readme "README" if behavior == :invoke
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
===============================================================================
|
|
2
|
+
|
|
3
|
+
BetterSeo has been installed successfully!
|
|
4
|
+
|
|
5
|
+
Configuration file created at: config/initializers/better_seo.rb
|
|
6
|
+
|
|
7
|
+
Next steps:
|
|
8
|
+
|
|
9
|
+
1. Edit the configuration file to match your site settings
|
|
10
|
+
2. Include BetterSeo::Rails::Helpers::ControllerHelpers in your controllers
|
|
11
|
+
(or use ApplicationController if you want it available everywhere)
|
|
12
|
+
|
|
13
|
+
3. Use the helpers in your views:
|
|
14
|
+
- <%= seo_tags %> for complete SEO tags
|
|
15
|
+
- <%= seo_meta_tags %> for meta tags only
|
|
16
|
+
- <%= seo_open_graph_tags %> for Open Graph tags
|
|
17
|
+
- <%= seo_twitter_tags %> for Twitter Cards
|
|
18
|
+
- <%= structured_data_tag(:organization, ...) %> for structured data
|
|
19
|
+
|
|
20
|
+
4. In your controllers, set page-specific SEO data:
|
|
21
|
+
- set_page_title("My Page")
|
|
22
|
+
- set_page_description("Description")
|
|
23
|
+
- set_page_keywords(["keyword1", "keyword2"])
|
|
24
|
+
- set_page_image("https://example.com/image.jpg")
|
|
25
|
+
- set_canonical("https://example.com/canonical-url")
|
|
26
|
+
|
|
27
|
+
For more information, visit: https://github.com/alessiobussolari/better_seo
|
|
28
|
+
|
|
29
|
+
===============================================================================
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
BetterSeo.configure do |config|
|
|
4
|
+
# Site-wide settings
|
|
5
|
+
config.site_name = "My Site"
|
|
6
|
+
config.default_locale = :en
|
|
7
|
+
config.available_locales = [:en, :it, :fr, :de, :es]
|
|
8
|
+
|
|
9
|
+
# Meta tags configuration
|
|
10
|
+
config.meta_tags.default_title = "Default Title"
|
|
11
|
+
config.meta_tags.title_separator = " | "
|
|
12
|
+
config.meta_tags.append_site_name = true
|
|
13
|
+
config.meta_tags.default_description = "Default description for your website"
|
|
14
|
+
config.meta_tags.default_keywords = ["keyword1", "keyword2", "keyword3"]
|
|
15
|
+
config.meta_tags.default_author = "Your Name or Company"
|
|
16
|
+
|
|
17
|
+
# Open Graph configuration
|
|
18
|
+
config.open_graph.enabled = true
|
|
19
|
+
config.open_graph.site_name = "My Site"
|
|
20
|
+
config.open_graph.default_type = "website"
|
|
21
|
+
config.open_graph.default_locale = "en_US"
|
|
22
|
+
config.open_graph.default_image.url = "https://example.com/default-og-image.jpg"
|
|
23
|
+
config.open_graph.default_image.width = 1200
|
|
24
|
+
config.open_graph.default_image.height = 630
|
|
25
|
+
config.open_graph.default_image.alt = "Default image description"
|
|
26
|
+
|
|
27
|
+
# Twitter Cards configuration
|
|
28
|
+
config.twitter.enabled = true
|
|
29
|
+
config.twitter.site = "@yoursite"
|
|
30
|
+
config.twitter.creator = "@yourcreator"
|
|
31
|
+
config.twitter.card_type = "summary_large_image"
|
|
32
|
+
|
|
33
|
+
# Structured Data configuration
|
|
34
|
+
config.structured_data.enabled = true
|
|
35
|
+
# config.structured_data.organization = {
|
|
36
|
+
# name: "My Organization",
|
|
37
|
+
# url: "https://example.com",
|
|
38
|
+
# logo: "https://example.com/logo.png"
|
|
39
|
+
# }
|
|
40
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_seo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- alessiobussolari
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -82,8 +82,61 @@ files:
|
|
|
82
82
|
- LICENSE.txt
|
|
83
83
|
- README.md
|
|
84
84
|
- Rakefile
|
|
85
|
+
- docs/00_OVERVIEW.md
|
|
86
|
+
- docs/01_CORE_AND_CONFIGURATION.md
|
|
87
|
+
- docs/02_META_TAGS_AND_OPEN_GRAPH.md
|
|
88
|
+
- docs/03_STRUCTURED_DATA.md
|
|
89
|
+
- docs/04_SITEMAP_AND_ROBOTS.md
|
|
90
|
+
- docs/05_RAILS_INTEGRATION.md
|
|
91
|
+
- docs/06_I18N_PAGE_GENERATOR.md
|
|
92
|
+
- docs/07_IMAGE_OPTIMIZATION.md
|
|
93
|
+
- docs/DEPENDENCIES.md
|
|
94
|
+
- docs/README.md
|
|
95
|
+
- docs/TESTING_STRATEGY.md
|
|
85
96
|
- lib/better_seo.rb
|
|
97
|
+
- lib/better_seo/analytics/google_analytics.rb
|
|
98
|
+
- lib/better_seo/analytics/google_tag_manager.rb
|
|
99
|
+
- lib/better_seo/configuration.rb
|
|
100
|
+
- lib/better_seo/dsl/base.rb
|
|
101
|
+
- lib/better_seo/dsl/meta_tags.rb
|
|
102
|
+
- lib/better_seo/dsl/open_graph.rb
|
|
103
|
+
- lib/better_seo/dsl/twitter_cards.rb
|
|
104
|
+
- lib/better_seo/errors.rb
|
|
105
|
+
- lib/better_seo/generators/amp_generator.rb
|
|
106
|
+
- lib/better_seo/generators/breadcrumbs_generator.rb
|
|
107
|
+
- lib/better_seo/generators/canonical_url_manager.rb
|
|
108
|
+
- lib/better_seo/generators/meta_tags_generator.rb
|
|
109
|
+
- lib/better_seo/generators/open_graph_generator.rb
|
|
110
|
+
- lib/better_seo/generators/robots_txt_generator.rb
|
|
111
|
+
- lib/better_seo/generators/twitter_cards_generator.rb
|
|
112
|
+
- lib/better_seo/image/optimizer.rb
|
|
113
|
+
- lib/better_seo/rails/helpers/controller_helpers.rb
|
|
114
|
+
- lib/better_seo/rails/helpers/seo_helper.rb
|
|
115
|
+
- lib/better_seo/rails/helpers/structured_data_helper.rb
|
|
116
|
+
- lib/better_seo/rails/model_helpers.rb
|
|
117
|
+
- lib/better_seo/rails/railtie.rb
|
|
118
|
+
- lib/better_seo/sitemap/builder.rb
|
|
119
|
+
- lib/better_seo/sitemap/generator.rb
|
|
120
|
+
- lib/better_seo/sitemap/sitemap_index.rb
|
|
121
|
+
- lib/better_seo/sitemap/url_entry.rb
|
|
122
|
+
- lib/better_seo/structured_data/article.rb
|
|
123
|
+
- lib/better_seo/structured_data/base.rb
|
|
124
|
+
- lib/better_seo/structured_data/breadcrumb_list.rb
|
|
125
|
+
- lib/better_seo/structured_data/event.rb
|
|
126
|
+
- lib/better_seo/structured_data/faq_page.rb
|
|
127
|
+
- lib/better_seo/structured_data/generator.rb
|
|
128
|
+
- lib/better_seo/structured_data/how_to.rb
|
|
129
|
+
- lib/better_seo/structured_data/local_business.rb
|
|
130
|
+
- lib/better_seo/structured_data/organization.rb
|
|
131
|
+
- lib/better_seo/structured_data/person.rb
|
|
132
|
+
- lib/better_seo/structured_data/product.rb
|
|
133
|
+
- lib/better_seo/structured_data/recipe.rb
|
|
134
|
+
- lib/better_seo/validators/seo_recommendations.rb
|
|
135
|
+
- lib/better_seo/validators/seo_validator.rb
|
|
86
136
|
- lib/better_seo/version.rb
|
|
137
|
+
- lib/generators/better_seo/install_generator.rb
|
|
138
|
+
- lib/generators/better_seo/templates/README
|
|
139
|
+
- lib/generators/better_seo/templates/better_seo.rb
|
|
87
140
|
- sig/better_seo.rbs
|
|
88
141
|
homepage: https://github.com/alessiobussolari/better_seo
|
|
89
142
|
licenses:
|