better_seo 0.13.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -3
  3. data/README.md +299 -181
  4. data/docs/00_OVERVIEW.md +472 -0
  5. data/docs/01_CORE_AND_CONFIGURATION.md +913 -0
  6. data/docs/02_META_TAGS_AND_OPEN_GRAPH.md +251 -0
  7. data/docs/03_STRUCTURED_DATA.md +140 -0
  8. data/docs/04_SITEMAP_AND_ROBOTS.md +131 -0
  9. data/docs/05_RAILS_INTEGRATION.md +175 -0
  10. data/docs/06_I18N_PAGE_GENERATOR.md +233 -0
  11. data/docs/07_IMAGE_OPTIMIZATION.md +260 -0
  12. data/docs/DEPENDENCIES.md +383 -0
  13. data/docs/README.md +180 -0
  14. data/docs/TESTING_STRATEGY.md +663 -0
  15. data/lib/better_seo/analytics/google_analytics.rb +83 -0
  16. data/lib/better_seo/analytics/google_tag_manager.rb +74 -0
  17. data/lib/better_seo/configuration.rb +316 -0
  18. data/lib/better_seo/dsl/base.rb +86 -0
  19. data/lib/better_seo/dsl/meta_tags.rb +55 -0
  20. data/lib/better_seo/dsl/open_graph.rb +109 -0
  21. data/lib/better_seo/dsl/twitter_cards.rb +131 -0
  22. data/lib/better_seo/errors.rb +31 -0
  23. data/lib/better_seo/generators/amp_generator.rb +83 -0
  24. data/lib/better_seo/generators/breadcrumbs_generator.rb +126 -0
  25. data/lib/better_seo/generators/canonical_url_manager.rb +106 -0
  26. data/lib/better_seo/generators/meta_tags_generator.rb +100 -0
  27. data/lib/better_seo/generators/open_graph_generator.rb +110 -0
  28. data/lib/better_seo/generators/robots_txt_generator.rb +102 -0
  29. data/lib/better_seo/generators/twitter_cards_generator.rb +102 -0
  30. data/lib/better_seo/image/optimizer.rb +143 -0
  31. data/lib/better_seo/rails/helpers/controller_helpers.rb +118 -0
  32. data/lib/better_seo/rails/helpers/seo_helper.rb +176 -0
  33. data/lib/better_seo/rails/helpers/structured_data_helper.rb +123 -0
  34. data/lib/better_seo/rails/model_helpers.rb +62 -0
  35. data/lib/better_seo/rails/railtie.rb +22 -0
  36. data/lib/better_seo/sitemap/builder.rb +65 -0
  37. data/lib/better_seo/sitemap/generator.rb +57 -0
  38. data/lib/better_seo/sitemap/sitemap_index.rb +73 -0
  39. data/lib/better_seo/sitemap/url_entry.rb +157 -0
  40. data/lib/better_seo/structured_data/article.rb +55 -0
  41. data/lib/better_seo/structured_data/base.rb +73 -0
  42. data/lib/better_seo/structured_data/breadcrumb_list.rb +49 -0
  43. data/lib/better_seo/structured_data/event.rb +207 -0
  44. data/lib/better_seo/structured_data/faq_page.rb +55 -0
  45. data/lib/better_seo/structured_data/generator.rb +75 -0
  46. data/lib/better_seo/structured_data/how_to.rb +96 -0
  47. data/lib/better_seo/structured_data/local_business.rb +94 -0
  48. data/lib/better_seo/structured_data/organization.rb +67 -0
  49. data/lib/better_seo/structured_data/person.rb +51 -0
  50. data/lib/better_seo/structured_data/product.rb +123 -0
  51. data/lib/better_seo/structured_data/recipe.rb +135 -0
  52. data/lib/better_seo/validators/seo_recommendations.rb +165 -0
  53. data/lib/better_seo/validators/seo_validator.rb +195 -0
  54. data/lib/better_seo/version.rb +1 -1
  55. data/lib/better_seo.rb +5 -0
  56. data/lib/generators/better_seo/install_generator.rb +21 -0
  57. data/lib/generators/better_seo/templates/README +29 -0
  58. data/lib/generators/better_seo/templates/better_seo.rb +40 -0
  59. metadata +69 -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BetterSeo
4
- VERSION = "0.13.0"
4
+ VERSION = "1.0.0.1"
5
5
  end
data/lib/better_seo.rb CHANGED
@@ -18,6 +18,10 @@ require_relative "better_seo/generators/amp_generator"
18
18
  require_relative "better_seo/generators/canonical_url_manager"
19
19
  require_relative "better_seo/generators/robots_txt_generator"
20
20
  require_relative "better_seo/validators/seo_validator"
21
+ require_relative "better_seo/validators/seo_recommendations"
22
+ require_relative "better_seo/image/optimizer"
23
+ require_relative "better_seo/analytics/google_analytics"
24
+ require_relative "better_seo/analytics/google_tag_manager"
21
25
  require_relative "better_seo/sitemap/url_entry"
22
26
  require_relative "better_seo/sitemap/builder"
23
27
  require_relative "better_seo/sitemap/generator"
@@ -38,6 +42,7 @@ require_relative "better_seo/rails/helpers/seo_helper"
38
42
  require_relative "better_seo/rails/helpers/structured_data_helper"
39
43
  require_relative "better_seo/rails/helpers/controller_helpers"
40
44
  require_relative "better_seo/rails/model_helpers"
45
+ require_relative "better_seo/rails/railtie" if defined?(Rails::Railtie)
41
46
 
42
47
  module BetterSeo
43
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.13.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-23 00:00:00.000000000 Z
11
+ date: 2025-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mini_magick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.11'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.11'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -68,8 +82,61 @@ files:
68
82
  - LICENSE.txt
69
83
  - README.md
70
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
71
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
72
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
73
140
  - sig/better_seo.rbs
74
141
  homepage: https://github.com/alessiobussolari/better_seo
75
142
  licenses: