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,251 @@
|
|
|
1
|
+
# Step 02: Meta Tags e Open Graph
|
|
2
|
+
|
|
3
|
+
**Versione Target**: 0.3.0
|
|
4
|
+
**Durata Stimata**: 2-3 settimane
|
|
5
|
+
**Priorità**: 🔴 CRITICA (Essential SEO)
|
|
6
|
+
**Dipendenze**: Step 01 completato
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Obiettivi dello Step
|
|
11
|
+
|
|
12
|
+
Implementare funzionalità SEO essenziali:
|
|
13
|
+
|
|
14
|
+
1. ✅ Meta tags HTML (title, description, keywords, robots, canonical)
|
|
15
|
+
2. ✅ Open Graph protocol completo
|
|
16
|
+
3. ✅ Twitter Cards
|
|
17
|
+
4. ✅ Validatori meta tags
|
|
18
|
+
5. ✅ HTML generators
|
|
19
|
+
6. ✅ Rails helpers base
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## File da Creare
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
lib/better_seo/dsl/meta_tags.rb
|
|
27
|
+
lib/better_seo/dsl/open_graph.rb
|
|
28
|
+
lib/better_seo/dsl/twitter_cards.rb
|
|
29
|
+
lib/better_seo/generators/meta_tags_generator.rb
|
|
30
|
+
lib/better_seo/generators/open_graph_generator.rb
|
|
31
|
+
lib/better_seo/generators/twitter_cards_generator.rb
|
|
32
|
+
lib/better_seo/validators/meta_validator.rb
|
|
33
|
+
lib/better_seo/rails/helpers/meta_tags_helper.rb (partial)
|
|
34
|
+
lib/better_seo/rails/helpers/open_graph_helper.rb (partial)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Implementazione: DSL Meta Tags
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# lib/better_seo/dsl/meta_tags.rb
|
|
43
|
+
module BetterSeo
|
|
44
|
+
module DSL
|
|
45
|
+
class MetaTags < Base
|
|
46
|
+
def title(value = nil)
|
|
47
|
+
value ? set(:title, value) : get(:title)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def description(value = nil)
|
|
51
|
+
value ? set(:description, value) : get(:description)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def keywords(*values)
|
|
55
|
+
values.any? ? set(:keywords, values.flatten) : get(:keywords)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def author(value = nil)
|
|
59
|
+
value ? set(:author, value) : get(:author)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def canonical(value = nil)
|
|
63
|
+
value ? set(:canonical, value) : get(:canonical)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def robots(index: true, follow: true, **options)
|
|
67
|
+
set(:robots, { index: index, follow: follow }.merge(options))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def viewport(value = "width=device-width, initial-scale=1.0")
|
|
71
|
+
set(:viewport, value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def charset(value = "UTF-8")
|
|
75
|
+
set(:charset, value)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
|
|
80
|
+
def validate!
|
|
81
|
+
errors = []
|
|
82
|
+
|
|
83
|
+
if config[:title] && config[:title].length > 60
|
|
84
|
+
errors << "Title too long (#{config[:title].length} chars, max 60 recommended)"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if config[:description] && config[:description].length > 160
|
|
88
|
+
errors << "Description too long (#{config[:description].length} chars, max 160 recommended)"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
raise ValidationError, errors.join(", ") if errors.any?
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Implementazione: Generator Meta Tags
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
# lib/better_seo/generators/meta_tags_generator.rb
|
|
104
|
+
module BetterSeo
|
|
105
|
+
module Generators
|
|
106
|
+
class MetaTagsGenerator
|
|
107
|
+
def initialize(config)
|
|
108
|
+
@config = config
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def generate
|
|
112
|
+
tags = []
|
|
113
|
+
|
|
114
|
+
tags << charset_tag if @config[:charset]
|
|
115
|
+
tags << viewport_tag if @config[:viewport]
|
|
116
|
+
tags << title_tag if @config[:title]
|
|
117
|
+
tags << description_tag if @config[:description]
|
|
118
|
+
tags << keywords_tag if @config[:keywords]&.any?
|
|
119
|
+
tags << author_tag if @config[:author]
|
|
120
|
+
tags << robots_tag if @config[:robots]
|
|
121
|
+
tags << canonical_tag if @config[:canonical]
|
|
122
|
+
|
|
123
|
+
tags.compact.join("\n")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def charset_tag
|
|
129
|
+
%(<meta charset="#{escape(@config[:charset])}">)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def viewport_tag
|
|
133
|
+
%(<meta name="viewport" content="#{escape(@config[:viewport])}">)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def title_tag
|
|
137
|
+
%(<title>#{escape(@config[:title])}</title>)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def description_tag
|
|
141
|
+
%(<meta name="description" content="#{escape(@config[:description])}">)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def keywords_tag
|
|
145
|
+
keywords = Array(@config[:keywords]).join(", ")
|
|
146
|
+
%(<meta name="keywords" content="#{escape(keywords)}">)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def author_tag
|
|
150
|
+
%(<meta name="author" content="#{escape(@config[:author])}">)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def robots_tag
|
|
154
|
+
robots = @config[:robots]
|
|
155
|
+
parts = []
|
|
156
|
+
parts << "index" if robots[:index]
|
|
157
|
+
parts << "noindex" unless robots[:index]
|
|
158
|
+
parts << "follow" if robots[:follow]
|
|
159
|
+
parts << "nofollow" unless robots[:follow]
|
|
160
|
+
|
|
161
|
+
%(<meta name="robots" content="#{parts.join(", ")}">)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def canonical_tag
|
|
165
|
+
%(<link rel="canonical" href="#{escape(@config[:canonical])}">)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def escape(text)
|
|
169
|
+
text.to_s.gsub('"', '"').gsub('<', '<').gsub('>', '>')
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Test Suite
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
# spec/dsl/meta_tags_spec.rb
|
|
182
|
+
RSpec.describe BetterSeo::DSL::MetaTags do
|
|
183
|
+
subject(:meta) { described_class.new }
|
|
184
|
+
|
|
185
|
+
it "sets title" do
|
|
186
|
+
meta.title "My Page Title"
|
|
187
|
+
expect(meta.title).to eq("My Page Title")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "validates title length" do
|
|
191
|
+
meta.title "A" * 80
|
|
192
|
+
expect { meta.build }.to raise_error(BetterSeo::ValidationError, /too long/)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "sets robots directives" do
|
|
196
|
+
meta.robots index: true, follow: false
|
|
197
|
+
expect(meta.robots).to eq({ index: true, follow: false })
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# spec/generators/meta_tags_generator_spec.rb
|
|
202
|
+
RSpec.describe BetterSeo::Generators::MetaTagsGenerator do
|
|
203
|
+
let(:config) do
|
|
204
|
+
{
|
|
205
|
+
charset: "UTF-8",
|
|
206
|
+
title: "Test Page",
|
|
207
|
+
description: "Test description",
|
|
208
|
+
canonical: "https://example.com/test"
|
|
209
|
+
}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
subject(:generator) { described_class.new(config) }
|
|
213
|
+
|
|
214
|
+
describe "#generate" do
|
|
215
|
+
it "generates HTML meta tags" do
|
|
216
|
+
html = generator.generate
|
|
217
|
+
|
|
218
|
+
expect(html).to include('<meta charset="UTF-8">')
|
|
219
|
+
expect(html).to include('<title>Test Page</title>')
|
|
220
|
+
expect(html).to include('name="description"')
|
|
221
|
+
expect(html).to include('rel="canonical"')
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "escapes HTML entities" do
|
|
225
|
+
config[:title] = 'Test <script>alert("xss")</script>'
|
|
226
|
+
html = generator.generate
|
|
227
|
+
|
|
228
|
+
expect(html).to include('<script>')
|
|
229
|
+
expect(html).not_to include('<script>')
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Checklist Completamento
|
|
238
|
+
|
|
239
|
+
- [ ] DSL MetaTags implementato e testato
|
|
240
|
+
- [ ] DSL OpenGraph implementato e testato
|
|
241
|
+
- [ ] DSL TwitterCards implementato e testato
|
|
242
|
+
- [ ] Generator MetaTags con HTML output
|
|
243
|
+
- [ ] Generator OpenGraph con property tags
|
|
244
|
+
- [ ] Generator TwitterCards
|
|
245
|
+
- [ ] Validatori per lunghezze ottimali
|
|
246
|
+
- [ ] Test coverage > 90%
|
|
247
|
+
- [ ] Integration con Step 01 Configuration
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
**Prossimi Passi**: Step 03 - Structured Data
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Step 03: Structured Data (JSON-LD)
|
|
2
|
+
|
|
3
|
+
**Versione Target**: 0.4.0
|
|
4
|
+
**Durata Stimata**: 2-3 settimane
|
|
5
|
+
**Priorità**: 🟡 MEDIA
|
|
6
|
+
**Dipendenze**: Step 01
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Obiettivi
|
|
11
|
+
|
|
12
|
+
1. ✅ Schema.org types (Article, Product, Organization, Person, Breadcrumb)
|
|
13
|
+
2. ✅ JSON-LD generator
|
|
14
|
+
3. ✅ DSL per structured data
|
|
15
|
+
4. ✅ Schema validator
|
|
16
|
+
5. ✅ Multiple structured data per page
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## File da Creare
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
lib/better_seo/schema/base.rb
|
|
24
|
+
lib/better_seo/schema/article.rb
|
|
25
|
+
lib/better_seo/schema/product.rb
|
|
26
|
+
lib/better_seo/schema/organization.rb
|
|
27
|
+
lib/better_seo/schema/person.rb
|
|
28
|
+
lib/better_seo/schema/breadcrumb.rb
|
|
29
|
+
lib/better_seo/generators/json_ld_generator.rb
|
|
30
|
+
lib/better_seo/dsl/structured_data.rb
|
|
31
|
+
lib/better_seo/validators/schema_validator.rb
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Implementazione: Schema Article
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# lib/better_seo/schema/article.rb
|
|
40
|
+
module BetterSeo
|
|
41
|
+
module Schema
|
|
42
|
+
class Article < Base
|
|
43
|
+
def schema_type
|
|
44
|
+
"Article"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def headline(value = nil)
|
|
48
|
+
value ? set(:headline, value) : get(:headline)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def date_published(value = nil)
|
|
52
|
+
value ? set(:datePublished, format_date(value)) : get(:datePublished)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def date_modified(value = nil)
|
|
56
|
+
value ? set(:dateModified, format_date(value)) : get(:dateModified)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def author(&block)
|
|
60
|
+
if block_given?
|
|
61
|
+
person = Person.new
|
|
62
|
+
person.evaluate(&block)
|
|
63
|
+
set(:author, person.to_schema)
|
|
64
|
+
else
|
|
65
|
+
get(:author)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def publisher(&block)
|
|
70
|
+
if block_given?
|
|
71
|
+
org = Organization.new
|
|
72
|
+
org.evaluate(&block)
|
|
73
|
+
set(:publisher, org.to_schema)
|
|
74
|
+
else
|
|
75
|
+
get(:publisher)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_schema
|
|
80
|
+
{
|
|
81
|
+
"@context" => "https://schema.org",
|
|
82
|
+
"@type" => schema_type
|
|
83
|
+
}.merge(config)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def format_date(value)
|
|
89
|
+
return value if value.is_a?(String)
|
|
90
|
+
value.respond_to?(:iso8601) ? value.iso8601 : value.to_s
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Implementazione: JSON-LD Generator
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
# lib/better_seo/generators/json_ld_generator.rb
|
|
103
|
+
require "json"
|
|
104
|
+
|
|
105
|
+
module BetterSeo
|
|
106
|
+
module Generators
|
|
107
|
+
class JsonLdGenerator
|
|
108
|
+
def initialize(schema_data)
|
|
109
|
+
@schema_data = Array(schema_data)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def generate
|
|
113
|
+
return "" if @schema_data.empty?
|
|
114
|
+
|
|
115
|
+
scripts = @schema_data.map do |schema|
|
|
116
|
+
json = JSON.pretty_generate(schema)
|
|
117
|
+
%(<script type="application/ld+json">\n#{json}\n</script>)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
scripts.join("\n")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Checklist
|
|
130
|
+
|
|
131
|
+
- [ ] Schema base types (Article, Product, Organization, Person, Breadcrumb)
|
|
132
|
+
- [ ] JSON-LD generator
|
|
133
|
+
- [ ] DSL structured_data
|
|
134
|
+
- [ ] Schema validator (schema.org compliance)
|
|
135
|
+
- [ ] Support multiple schemas per page
|
|
136
|
+
- [ ] Test coverage > 90%
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
**Prossimi Passi**: Step 04 - Sitemap & Robots
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Step 04: Sitemap XML e Robots.txt
|
|
2
|
+
|
|
3
|
+
**Versione Target**: 0.5.0
|
|
4
|
+
**Durata Stimata**: 1-2 settimane
|
|
5
|
+
**Priorità**: 🟡 MEDIA
|
|
6
|
+
**Dipendenze**: Step 01
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Obiettivi
|
|
11
|
+
|
|
12
|
+
1. ✅ Sitemap XML generator
|
|
13
|
+
2. ✅ Dynamic sitemap from Rails models
|
|
14
|
+
3. ✅ Sitemap index (>50k URLs)
|
|
15
|
+
4. ✅ Robots.txt generator
|
|
16
|
+
5. ✅ Ping search engines
|
|
17
|
+
6. ✅ Rake tasks
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## File da Creare
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
lib/better_seo/generators/sitemap_generator.rb
|
|
25
|
+
lib/better_seo/generators/robots_generator.rb
|
|
26
|
+
lib/better_seo/tasks/sitemap.rake
|
|
27
|
+
lib/better_seo/tasks/robots.rake
|
|
28
|
+
lib/better_seo/sitemap/url_builder.rb
|
|
29
|
+
lib/better_seo/sitemap/index_builder.rb
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Implementazione: Sitemap Generator
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
# lib/better_seo/generators/sitemap_generator.rb
|
|
38
|
+
require "builder"
|
|
39
|
+
|
|
40
|
+
module BetterSeo
|
|
41
|
+
module Generators
|
|
42
|
+
class SitemapGenerator
|
|
43
|
+
def initialize(urls, options = {})
|
|
44
|
+
@urls = urls
|
|
45
|
+
@options = options
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def generate
|
|
49
|
+
xml = Builder::XmlMarkup.new(indent: 2)
|
|
50
|
+
xml.instruct! :xml, version: "1.0", encoding: "UTF-8"
|
|
51
|
+
|
|
52
|
+
xml.urlset xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9" do
|
|
53
|
+
@urls.each do |url_data|
|
|
54
|
+
xml.url do
|
|
55
|
+
xml.loc url_data[:loc]
|
|
56
|
+
xml.lastmod url_data[:lastmod] if url_data[:lastmod]
|
|
57
|
+
xml.changefreq url_data[:changefreq] if url_data[:changefreq]
|
|
58
|
+
xml.priority url_data[:priority] if url_data[:priority]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
xml.target!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def save(path)
|
|
67
|
+
File.write(path, generate)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Rake Tasks
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# lib/better_seo/tasks/sitemap.rake
|
|
80
|
+
namespace :better_seo do
|
|
81
|
+
namespace :sitemap do
|
|
82
|
+
desc "Generate sitemap.xml"
|
|
83
|
+
task generate: :environment do
|
|
84
|
+
config = BetterSeo.configuration.sitemap
|
|
85
|
+
|
|
86
|
+
urls = []
|
|
87
|
+
|
|
88
|
+
# Add static URLs
|
|
89
|
+
urls << { loc: "#{config.host}/", priority: 1.0, changefreq: "daily" }
|
|
90
|
+
|
|
91
|
+
# Add dynamic URLs from models (configured in better_seo.yml)
|
|
92
|
+
# Article.published.each do |article|
|
|
93
|
+
# urls << {
|
|
94
|
+
# loc: "#{config.host}#{article_path(article)}",
|
|
95
|
+
# lastmod: article.updated_at.iso8601,
|
|
96
|
+
# changefreq: "weekly",
|
|
97
|
+
# priority: 0.8
|
|
98
|
+
# }
|
|
99
|
+
# end
|
|
100
|
+
|
|
101
|
+
generator = BetterSeo::Generators::SitemapGenerator.new(urls)
|
|
102
|
+
generator.save(config.output_path)
|
|
103
|
+
|
|
104
|
+
puts "✓ Sitemap generated: #{config.output_path}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
desc "Generate and ping search engines"
|
|
108
|
+
task generate_and_ping: :generate do
|
|
109
|
+
# Ping Google, Bing, etc.
|
|
110
|
+
puts "✓ Pinged search engines"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Checklist
|
|
119
|
+
|
|
120
|
+
- [ ] Sitemap XML generator con Builder
|
|
121
|
+
- [ ] Dynamic URLs from models
|
|
122
|
+
- [ ] Sitemap index per >50k URLs
|
|
123
|
+
- [ ] Robots.txt generator
|
|
124
|
+
- [ ] Rake tasks (generate, ping)
|
|
125
|
+
- [ ] Gzip compression opzionale
|
|
126
|
+
- [ ] Image sitemap support
|
|
127
|
+
- [ ] Test coverage > 85%
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
**Prossimi Passi**: Step 05 - Rails Integration
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Step 05: Rails Integration Completa
|
|
2
|
+
|
|
3
|
+
**Versione Target**: 0.6.0
|
|
4
|
+
**Durata Stimata**: 2 settimane
|
|
5
|
+
**Priorità**: 🔴 ALTA
|
|
6
|
+
**Dipendenze**: Step 01, 02, 03, 04
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Obiettivi
|
|
11
|
+
|
|
12
|
+
1. ✅ View helpers completi (meta_tags, open_graph, structured_data)
|
|
13
|
+
2. ✅ Controller concerns (SeoAware)
|
|
14
|
+
3. ✅ Rails generators (install, config, sitemap, robots)
|
|
15
|
+
4. ✅ Rails Engine
|
|
16
|
+
5. ✅ Routes integration
|
|
17
|
+
6. ✅ Middleware (optional)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## File da Creare
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
lib/better_seo/rails/helpers/meta_tags_helper.rb
|
|
25
|
+
lib/better_seo/rails/helpers/open_graph_helper.rb
|
|
26
|
+
lib/better_seo/rails/helpers/structured_data_helper.rb
|
|
27
|
+
lib/better_seo/rails/concerns/seo_aware.rb
|
|
28
|
+
lib/better_seo/rails/engine.rb
|
|
29
|
+
lib/generators/better_seo/install_generator.rb
|
|
30
|
+
lib/generators/better_seo/config_generator.rb
|
|
31
|
+
lib/generators/better_seo/page_generator.rb (parziale, completato in Step 06)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Implementazione: MetaTagsHelper
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# lib/better_seo/rails/helpers/meta_tags_helper.rb
|
|
40
|
+
module BetterSeo
|
|
41
|
+
module Rails
|
|
42
|
+
module Helpers
|
|
43
|
+
module MetaTagsHelper
|
|
44
|
+
def seo_meta_tags(options = {}, &block)
|
|
45
|
+
meta_dsl = BetterSeo::DSL::MetaTags.new
|
|
46
|
+
|
|
47
|
+
# Load defaults from configuration
|
|
48
|
+
config = BetterSeo.configuration.meta_tags
|
|
49
|
+
meta_dsl.merge!(config.to_h)
|
|
50
|
+
|
|
51
|
+
# Load from controller if set
|
|
52
|
+
if controller.respond_to?(:current_seo_meta) && controller.current_seo_meta
|
|
53
|
+
meta_dsl.merge!(controller.current_seo_meta)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Apply block overrides
|
|
57
|
+
meta_dsl.evaluate(&block) if block_given?
|
|
58
|
+
|
|
59
|
+
# Apply options hash
|
|
60
|
+
meta_dsl.merge!(options) if options.any?
|
|
61
|
+
|
|
62
|
+
# Generate HTML
|
|
63
|
+
generator = BetterSeo::Generators::MetaTagsGenerator.new(meta_dsl.build)
|
|
64
|
+
generator.generate.html_safe
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def render_seo_tags
|
|
68
|
+
tags = []
|
|
69
|
+
tags << seo_meta_tags
|
|
70
|
+
tags << open_graph_tags if BetterSeo.configuration.open_graph_enabled?
|
|
71
|
+
tags << twitter_card_tags if BetterSeo.configuration.twitter_enabled?
|
|
72
|
+
tags << structured_data_tags if BetterSeo.configuration.structured_data_enabled?
|
|
73
|
+
|
|
74
|
+
safe_join(tags, "\n")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Implementazione: SeoAware Concern
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# lib/better_seo/rails/concerns/seo_aware.rb
|
|
88
|
+
module BetterSeo
|
|
89
|
+
module Rails
|
|
90
|
+
module Concerns
|
|
91
|
+
module SeoAware
|
|
92
|
+
extend ActiveSupport::Concern
|
|
93
|
+
|
|
94
|
+
included do
|
|
95
|
+
helper_method :current_seo_meta, :current_seo_og, :current_seo_structured_data
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set_seo(&block)
|
|
99
|
+
@_seo_builder = BetterSeo::DSL::MetaTags.new
|
|
100
|
+
@_seo_builder.evaluate(&block) if block_given?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def set_seo_from_locale(page_name)
|
|
104
|
+
# Implementato in Step 06
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def current_seo_meta
|
|
108
|
+
@_seo_builder&.to_h || {}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def current_seo_og
|
|
112
|
+
@_seo_og || {}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def current_seo_structured_data
|
|
116
|
+
@_seo_structured_data || []
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Rails Generator: Install
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# lib/generators/better_seo/install_generator.rb
|
|
130
|
+
module BetterSeo
|
|
131
|
+
module Generators
|
|
132
|
+
class InstallGenerator < Rails::Generators::Base
|
|
133
|
+
source_root File.expand_path("templates", __dir__)
|
|
134
|
+
|
|
135
|
+
desc "Install BetterSeo in your Rails application"
|
|
136
|
+
|
|
137
|
+
def create_initializer
|
|
138
|
+
template "initializer.rb", "config/initializers/better_seo.rb"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def create_config
|
|
142
|
+
template "better_seo.yml", "config/better_seo.yml"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def create_locale_directories
|
|
146
|
+
empty_directory "config/locales/seo/it"
|
|
147
|
+
empty_directory "config/locales/seo/en"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def show_readme
|
|
151
|
+
readme "README" if behavior == :invoke
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Checklist
|
|
161
|
+
|
|
162
|
+
- [ ] View helpers (meta_tags, open_graph, structured_data)
|
|
163
|
+
- [ ] Helper `render_seo_tags` (all-in-one)
|
|
164
|
+
- [ ] Controller concern `SeoAware`
|
|
165
|
+
- [ ] Method `set_seo` DSL in controllers
|
|
166
|
+
- [ ] Rails generators (install, config)
|
|
167
|
+
- [ ] Generator templates
|
|
168
|
+
- [ ] Rails Engine per routes
|
|
169
|
+
- [ ] Auto-include helpers in ApplicationHelper
|
|
170
|
+
- [ ] Auto-include concerns in ApplicationController
|
|
171
|
+
- [ ] Test coverage > 90%
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
**Prossimi Passi**: Step 06 - i18n Page Generator
|