@spree/docs 0.1.0 → 0.1.2
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.
- package/dist/api-reference/store-api/errors.md +2 -2
- package/dist/api-reference/store-api/idempotency.md +1 -1
- package/dist/api-reference/store-api/localization.md +4 -4
- package/dist/api-reference/store-api/metadata.md +2 -2
- package/dist/api-reference/store.yaml +10599 -0
- package/dist/api-reference/storefront/authentication.md +2 -2
- package/dist/api-reference/tutorials/adyen-integration-guide-for-android.md +2 -2
- package/dist/api-reference/tutorials/adyen-integration-guide-for-ios.md +2 -2
- package/dist/api-reference/tutorials/quick-checkout-with-stripe.md +8 -8
- package/dist/api-reference/v2/introduction.md +2 -2
- package/dist/api-reference/webhooks-events.md +2 -2
- package/dist/developer/admin/admin.md +18 -18
- package/dist/developer/admin/components.md +1 -1
- package/dist/developer/admin/extending-ui.md +26 -26
- package/dist/developer/admin/helper-methods.md +2 -2
- package/dist/developer/admin/navigation.md +5 -5
- package/dist/developer/admin/tables.md +4 -4
- package/dist/developer/cli/quickstart.md +2 -2
- package/dist/developer/contributing/creating-an-extension.md +12 -12
- package/dist/developer/contributing/quickstart.md +1 -1
- package/dist/developer/core-concepts/addresses.md +11 -11
- package/dist/developer/core-concepts/adjustments.md +8 -8
- package/dist/developer/core-concepts/architecture.md +21 -21
- package/dist/developer/core-concepts/calculators.md +4 -4
- package/dist/developer/core-concepts/customers.md +9 -9
- package/dist/developer/core-concepts/events.md +5 -5
- package/dist/developer/core-concepts/inventory.md +5 -5
- package/dist/developer/core-concepts/markets.md +10 -10
- package/dist/developer/core-concepts/media.md +3 -3
- package/dist/developer/core-concepts/metafields.md +6 -6
- package/dist/developer/core-concepts/orders.md +14 -14
- package/dist/developer/core-concepts/payments.md +9 -9
- package/dist/developer/core-concepts/pricing.md +10 -10
- package/dist/developer/core-concepts/products.md +10 -10
- package/dist/developer/core-concepts/promotions.md +5 -5
- package/dist/developer/core-concepts/reports.md +2 -2
- package/dist/developer/core-concepts/search-filtering.md +7 -7
- package/dist/developer/core-concepts/shipments.md +13 -13
- package/dist/developer/core-concepts/slugs.md +3 -3
- package/dist/developer/core-concepts/staff-roles.md +7 -7
- package/dist/developer/core-concepts/store-credits-gift-cards.md +4 -4
- package/dist/developer/core-concepts/stores.md +15 -15
- package/dist/developer/core-concepts/taxes.md +11 -11
- package/dist/developer/core-concepts/translations.md +6 -6
- package/dist/developer/core-concepts/users.md +12 -12
- package/dist/developer/core-concepts/webhooks.md +8 -8
- package/dist/developer/create-spree-app/quickstart.md +5 -5
- package/dist/developer/customization/api.md +2 -2
- package/dist/developer/customization/authentication.md +2 -2
- package/dist/developer/customization/checkout.md +7 -7
- package/dist/developer/customization/decorators.md +24 -24
- package/dist/developer/customization/dependencies.md +1 -1
- package/dist/developer/customization/metadata.md +3 -3
- package/dist/developer/customization/permissions.md +1 -1
- package/dist/developer/customization/quickstart.md +9 -9
- package/dist/developer/customization/v4/checkout.md +3 -3
- package/dist/developer/customization/v4/deface.md +1 -1
- package/dist/developer/deployment/assets.md +1 -1
- package/dist/developer/deployment/aws.md +5 -5
- package/dist/developer/deployment/docker.md +2 -2
- package/dist/developer/deployment/environment_variables.md +1 -1
- package/dist/developer/deployment/render.md +5 -5
- package/dist/developer/getting-started/quickstart.md +2 -2
- package/dist/developer/how-to/custom-payment-method.md +6 -6
- package/dist/developer/how-to/custom-promotion.md +7 -7
- package/dist/developer/how-to/custom-report.md +3 -3
- package/dist/developer/how-to/custom-search-provider.md +7 -7
- package/dist/developer/multi-store/quickstart.md +1 -1
- package/dist/developer/multi-tenant/quickstart.md +1 -1
- package/dist/developer/sdk/authentication.md +1 -1
- package/dist/developer/sdk/configuration.md +1 -1
- package/dist/developer/sdk/store/markets.md +3 -3
- package/dist/developer/storefront/nextjs/customization.md +1 -1
- package/dist/developer/storefront/nextjs/quickstart.md +2 -2
- package/dist/developer/tutorial/admin.md +2 -2
- package/dist/developer/tutorial/extending-models.md +15 -15
- package/dist/developer/tutorial/file-uploads.md +1 -1
- package/dist/developer/tutorial/introduction.md +7 -7
- package/dist/developer/tutorial/rich-text.md +1 -1
- package/dist/developer/tutorial/testing.md +5 -61
- package/dist/developer/upgrades/3.7-to-4.0.md +1 -1
- package/dist/developer/upgrades/4.0-to-4.1.md +1 -1
- package/dist/developer/upgrades/4.10-to-5.0.md +1 -1
- package/dist/developer/upgrades/4.5-to-4.6.md +4 -4
- package/dist/developer/upgrades/4.8-to-4.9.md +1 -1
- package/dist/developer/upgrades/4.9-to-4.10.md +1 -1
- package/dist/developer/upgrades/5.0-to-5.1.md +1 -1
- package/dist/developer/upgrades/5.1-to-5.2.md +2 -2
- package/dist/developer/upgrades/5.2-to-5.3.md +2 -2
- package/dist/developer/upgrades/5.3-to-5.4.md +5 -5
- package/dist/developer/upgrades/quickstart.md +1 -1
- package/dist/integrations/integrations.md +10 -10
- package/dist/integrations/payments/adyen.md +1 -1
- package/dist/integrations/search/meilisearch.md +2 -2
- package/package.json +7 -2
- package/dist/developer/storefront/blocks.md +0 -285
- package/dist/developer/storefront/custom-css.md +0 -260
- package/dist/developer/storefront/custom-javascript.md +0 -166
- package/dist/developer/storefront/helper-methods.md +0 -1288
- package/dist/developer/storefront/links.md +0 -298
- package/dist/developer/storefront/pages.md +0 -163
- package/dist/developer/storefront/sections.md +0 -569
- package/dist/developer/storefront/storefront.md +0 -56
- package/dist/developer/storefront/themes.md +0 -161
- package/dist/developer/tutorial/page-builder.md +0 -487
- package/dist/developer/tutorial/seo.md +0 -332
- package/dist/developer/tutorial/storefront.md +0 -352
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: SEO
|
|
3
|
-
description: Learn how to optimize your Brands feature for search engines
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
> **INFO:** This guide assumes you've completed the [Storefront](/developer/tutorial/storefront) tutorial. You should have working brand pages.
|
|
7
|
-
|
|
8
|
-
This guide covers SEO best practices for your custom Spree features, including friendly URLs, meta tags, and Open Graph data.
|
|
9
|
-
|
|
10
|
-
## SEO-Friendly URLs
|
|
11
|
-
|
|
12
|
-
Rather than using database IDs in URLs:
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
https://example.com/brands/1
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Create human-readable URLs that help with SEO:
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
https://example.com/brands/nike-sportswear
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Step 1: Add SEO Columns
|
|
25
|
-
|
|
26
|
-
Add columns for slug and meta fields:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
bin/rails g migration AddSeoFieldsToSpreeBrands slug:string:uniq meta_title:string meta_description:text
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
bin/rails db:migrate
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
This adds:
|
|
37
|
-
- `slug` - SEO-friendly URL with unique index
|
|
38
|
-
- `meta_title` - Custom page title for search engines
|
|
39
|
-
- `meta_description` - Description shown in search results
|
|
40
|
-
|
|
41
|
-
### Step 2: Add FriendlyId to the Model
|
|
42
|
-
|
|
43
|
-
Add FriendlyId to generate SEO-friendly URLs automatically:
|
|
44
|
-
|
|
45
|
-
```ruby app/models/spree/brand.rb {3-4,9}
|
|
46
|
-
module Spree
|
|
47
|
-
class Brand < Spree::Base
|
|
48
|
-
extend FriendlyId
|
|
49
|
-
friendly_id :slug_candidates, use: %i[slugged]
|
|
50
|
-
|
|
51
|
-
# ... other code ...
|
|
52
|
-
|
|
53
|
-
validates :name, presence: true
|
|
54
|
-
validates :slug, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
The `slug_candidates` method is already defined in `Spree::Base`:
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
def slug_candidates
|
|
63
|
-
[
|
|
64
|
-
:name,
|
|
65
|
-
[:name, :uuid_for_friendly_id]
|
|
66
|
-
]
|
|
67
|
-
end
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
This generates the slug from the name. If the name is taken, it appends a UUID.
|
|
71
|
-
|
|
72
|
-
### Step 3: Add SEO Fields to Admin
|
|
73
|
-
|
|
74
|
-
Spree provides a reusable SEO partial that handles slug, meta title, and meta description with a live search preview. Update your admin form:
|
|
75
|
-
|
|
76
|
-
```erb app/views/spree/admin/brands/_form.html.erb {1-2,5-12,15,25-32}
|
|
77
|
-
<div class="row" data-controller="slug-form seo-form">
|
|
78
|
-
<div class="col-lg-8">
|
|
79
|
-
<div class="card mb-6">
|
|
80
|
-
<div class="card-body">
|
|
81
|
-
<%= f.spree_text_field :name,
|
|
82
|
-
required: true,
|
|
83
|
-
data: {
|
|
84
|
-
seo_form_target: 'sourceTitleInput',
|
|
85
|
-
slug_form_target: 'name',
|
|
86
|
-
action: 'slug-form#updateUrlFromName'
|
|
87
|
-
} %>
|
|
88
|
-
<%= f.spree_rich_text_area :description,
|
|
89
|
-
data: { seo_form_target: 'sourceDescriptionInput' } %>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div class="col-lg-4">
|
|
95
|
-
<div class="card mb-6">
|
|
96
|
-
<div class="card-header">
|
|
97
|
-
<h5 class="card-title"><%= Spree.t(:logo) %></h5>
|
|
98
|
-
</div>
|
|
99
|
-
<div class="card-body">
|
|
100
|
-
<%= f.spree_file_field :logo, width: 300, height: 300, crop: true %>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
<%= render 'spree/admin/shared/seo',
|
|
105
|
-
f: f,
|
|
106
|
-
title: @brand.name,
|
|
107
|
-
meta_title: @brand.meta_title,
|
|
108
|
-
description: @brand.description,
|
|
109
|
-
slug: @brand.slug,
|
|
110
|
-
slug_path: 'brands',
|
|
111
|
-
placeholder: 'Add a name and description to see how this brand might appear in a search engine listing' %>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
The SEO partial provides:
|
|
117
|
-
- **Live preview** of how the page will appear in search results
|
|
118
|
-
- **Meta title** field with character count
|
|
119
|
-
- **Meta description** field
|
|
120
|
-
- **Slug** field with auto-generation from name
|
|
121
|
-
|
|
122
|
-
The `seo-form` Stimulus controller syncs the preview with your form inputs in real-time.
|
|
123
|
-
|
|
124
|
-
Don't forget to permit the SEO attributes in your controller:
|
|
125
|
-
|
|
126
|
-
```ruby app/controllers/spree/admin/brands_controller.rb {6-8}
|
|
127
|
-
def permitted_resource_params
|
|
128
|
-
params.require(:brand).permit(
|
|
129
|
-
:name,
|
|
130
|
-
:description,
|
|
131
|
-
:logo,
|
|
132
|
-
:slug,
|
|
133
|
-
:meta_title,
|
|
134
|
-
:meta_description)
|
|
135
|
-
end
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Step 4: Use Slugs in Storefront
|
|
139
|
-
|
|
140
|
-
Update your controller to use `friendly.find`:
|
|
141
|
-
|
|
142
|
-
```ruby app/controllers/spree/brands_controller.rb
|
|
143
|
-
def load_brand
|
|
144
|
-
@brand = Spree::Brand.friendly.find(params[:id])
|
|
145
|
-
end
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The `friendly.find` method:
|
|
149
|
-
- Finds by slug first (e.g., `/brands/nike-sportswear`)
|
|
150
|
-
- Falls back to ID if no slug matches
|
|
151
|
-
- Raises `ActiveRecord::RecordNotFound` if neither is found
|
|
152
|
-
|
|
153
|
-
### URL Helpers
|
|
154
|
-
|
|
155
|
-
Always pass the model object to path helpers:
|
|
156
|
-
|
|
157
|
-
```erb
|
|
158
|
-
<%= link_to brand.name, spree.brand_path(brand) %>
|
|
159
|
-
<%# => /brands/nike-sportswear %>
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
FriendlyId overrides `to_param` to return the slug automatically.
|
|
163
|
-
|
|
164
|
-
### Handling Slug Changes
|
|
165
|
-
|
|
166
|
-
To redirect old URLs when slugs change, enable slug history:
|
|
167
|
-
|
|
168
|
-
```ruby app/models/spree/brand.rb
|
|
169
|
-
extend FriendlyId
|
|
170
|
-
friendly_id :slug_candidates, use: %i[slugged history]
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Old slugs will automatically resolve to the current slug.
|
|
174
|
-
|
|
175
|
-
## Meta Tags & Open Graph
|
|
176
|
-
|
|
177
|
-
Spree automatically generates meta tags and Open Graph data. The system uses a helper called `object` that finds the main instance variable based on your controller name.
|
|
178
|
-
|
|
179
|
-
For `BrandsController`, Spree looks for `@brand` and generates:
|
|
180
|
-
- Meta description
|
|
181
|
-
- Open Graph tags (og:title, og:description, og:image)
|
|
182
|
-
- Twitter Card tags
|
|
183
|
-
|
|
184
|
-
### Page Title
|
|
185
|
-
|
|
186
|
-
Override `accurate_title` in your controller to set the page `<title>`:
|
|
187
|
-
|
|
188
|
-
```ruby app/controllers/spree/brands_controller.rb
|
|
189
|
-
private
|
|
190
|
-
|
|
191
|
-
def accurate_title
|
|
192
|
-
if @brand
|
|
193
|
-
@brand.meta_title.presence || @brand.name
|
|
194
|
-
else
|
|
195
|
-
Spree.t(:brands)
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Meta Description
|
|
201
|
-
|
|
202
|
-
Spree checks these sources in order:
|
|
203
|
-
|
|
204
|
-
1. `@page_description` instance variable (if set)
|
|
205
|
-
2. `@brand.meta_description` (if the model has this attribute)
|
|
206
|
-
3. `current_store.meta_description` (fallback)
|
|
207
|
-
|
|
208
|
-
#### Using the SEO Partial
|
|
209
|
-
|
|
210
|
-
If you followed [Step 1](#step-1-add-seo-columns) and [Step 3](#step-3-add-seo-fields-to-admin), your model already has `meta_description` and the admin form uses the SEO partial. Spree will automatically use `@brand.meta_description` for the page description.
|
|
211
|
-
|
|
212
|
-
#### Manual Override
|
|
213
|
-
|
|
214
|
-
For custom logic, set `@page_description` directly:
|
|
215
|
-
|
|
216
|
-
```ruby
|
|
217
|
-
def show
|
|
218
|
-
@page_description = "Shop #{@brand.name} - #{@brand.products.count} products available"
|
|
219
|
-
end
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Social Sharing Image
|
|
223
|
-
|
|
224
|
-
For Open Graph images, Spree checks:
|
|
225
|
-
|
|
226
|
-
1. `@page_image` instance variable (if set)
|
|
227
|
-
2. `@brand.image` (if the model responds to `image`)
|
|
228
|
-
3. `current_store.social_image` (fallback)
|
|
229
|
-
|
|
230
|
-
If your Brand model has `logo` but not `image`, add an alias:
|
|
231
|
-
|
|
232
|
-
```ruby app/models/spree/brand.rb
|
|
233
|
-
def image
|
|
234
|
-
logo
|
|
235
|
-
end
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Or set it manually:
|
|
239
|
-
|
|
240
|
-
```ruby
|
|
241
|
-
def show
|
|
242
|
-
@page_image = @brand.logo if @brand.logo.attached?
|
|
243
|
-
end
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Complete SEO-Optimized Model
|
|
247
|
-
|
|
248
|
-
Here's a complete Brand model with full SEO support:
|
|
249
|
-
|
|
250
|
-
```ruby app/models/spree/brand.rb
|
|
251
|
-
module Spree
|
|
252
|
-
class Brand < Spree::Base
|
|
253
|
-
extend FriendlyId
|
|
254
|
-
friendly_id :slug_candidates, use: %i[slugged]
|
|
255
|
-
|
|
256
|
-
has_many :products, class_name: 'Spree::Product', dependent: :nullify
|
|
257
|
-
|
|
258
|
-
has_one_attached :logo
|
|
259
|
-
has_rich_text :description
|
|
260
|
-
|
|
261
|
-
# Database columns: slug, meta_title, meta_description
|
|
262
|
-
|
|
263
|
-
validates :name, presence: true
|
|
264
|
-
validates :slug, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
|
|
265
|
-
|
|
266
|
-
# SEO: Use logo as Open Graph image
|
|
267
|
-
def image
|
|
268
|
-
logo
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Complete SEO-Optimized Controller
|
|
275
|
-
|
|
276
|
-
```ruby app/controllers/spree/brands_controller.rb
|
|
277
|
-
module Spree
|
|
278
|
-
class BrandsController < StoreController
|
|
279
|
-
before_action :load_brand, only: [:show]
|
|
280
|
-
|
|
281
|
-
def index
|
|
282
|
-
@brands = Spree::Brand.order(:name)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
def show
|
|
286
|
-
@products = @brand.products
|
|
287
|
-
.active(current_currency)
|
|
288
|
-
.includes(storefront_products_includes)
|
|
289
|
-
.page(params[:page])
|
|
290
|
-
.per(12)
|
|
291
|
-
|
|
292
|
-
# Optional: Custom page description
|
|
293
|
-
# @page_description = "Shop #{@brand.name} products"
|
|
294
|
-
|
|
295
|
-
# Optional: Custom social image
|
|
296
|
-
# @page_image = @brand.logo if @brand.logo.attached?
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
private
|
|
300
|
-
|
|
301
|
-
def load_brand
|
|
302
|
-
@brand = Spree::Brand.friendly.find(params[:id])
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def accurate_title
|
|
306
|
-
if @brand
|
|
307
|
-
@brand.meta_title.presence || @brand.name
|
|
308
|
-
else
|
|
309
|
-
Spree.t(:brands)
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## SEO Checklist
|
|
317
|
-
|
|
318
|
-
**Friendly URLs** - Use slugs instead of IDs (`/brands/nike` not `/brands/1`)
|
|
319
|
-
|
|
320
|
-
**Page Titles** - Override `accurate_title` for descriptive titles
|
|
321
|
-
|
|
322
|
-
**Meta Descriptions** - Add `meta_description` field or set `@page_description`
|
|
323
|
-
|
|
324
|
-
**Social Images** - Provide `image` method or set `@page_image` for Open Graph
|
|
325
|
-
|
|
326
|
-
**Slug History** - Enable FriendlyId history to handle URL changes gracefully
|
|
327
|
-
|
|
328
|
-
## Related Documentation
|
|
329
|
-
|
|
330
|
-
- [Storefront Tutorial](/developer/tutorial/storefront) - Building storefront pages
|
|
331
|
-
- [Model Tutorial](/developer/tutorial/model) - Creating the Brand model
|
|
332
|
-
- [Admin Tutorial](/developer/tutorial/admin) - Building the admin interface
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Storefront
|
|
3
|
-
description: Learn how to create storefront pages for your Brands feature
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
In this tutorial, we'll create storefront pages to display brands to your customers. We'll start with a simple Rails approach using controllers and views, then in the next guide we'll connect it to Page Builder.
|
|
7
|
-
|
|
8
|
-
> **INFO:** This guide assumes you've completed the [Model](/developer/tutorial/model), [Admin](/developer/tutorial/admin), [Rich Text](/developer/tutorial/rich-text), [File Uploads](/developer/tutorial/file-uploads), and [Extending Core Models](/developer/tutorial/extending-models) tutorials. You should have a working `Spree::Brand` model with products associated to brands.
|
|
9
|
-
|
|
10
|
-
## What We're Building
|
|
11
|
-
|
|
12
|
-
By the end of this tutorial, you'll have:
|
|
13
|
-
|
|
14
|
-
- A custom theme for your store
|
|
15
|
-
- A brands listing page at `/brands`
|
|
16
|
-
- Individual brand pages at `/brands/:id`
|
|
17
|
-
- Styled views using Tailwind CSS
|
|
18
|
-
|
|
19
|
-
## Step 1: Create a Custom Theme
|
|
20
|
-
|
|
21
|
-
Before adding custom views, create your own theme. This is important because:
|
|
22
|
-
|
|
23
|
-
- **Upgrade safety** - Your customizations won't be overwritten when updating Spree
|
|
24
|
-
- **Clean separation** - Your code stays separate from Spree's default theme
|
|
25
|
-
- **Full control** - You get a complete copy of all templates to customize
|
|
26
|
-
|
|
27
|
-
Run the theme generator:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
bin/rails g spree:storefront:theme MyStore
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
This creates:
|
|
34
|
-
- `app/views/themes/my_store/` - Copy of all storefront templates
|
|
35
|
-
- `app/models/spree/themes/my_store.rb` - Theme configuration class
|
|
36
|
-
|
|
37
|
-
The generator also updates your `config/initializers/spree.rb`:
|
|
38
|
-
|
|
39
|
-
```ruby config/initializers/spree.rb
|
|
40
|
-
Spree.page_builder.themes << Spree::Themes::MyStore
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Activate Your Theme
|
|
44
|
-
|
|
45
|
-
1. Go to **Admin → Storefront → Themes**
|
|
46
|
-
2. Find your new theme in the "Add new theme" section
|
|
47
|
-
3. Click **Add** to activate it
|
|
48
|
-
|
|
49
|
-
> **WARNING:** Always create a custom theme for production stores. Never modify the default theme directly - your changes will be lost during Spree upgrades.
|
|
50
|
-
|
|
51
|
-
## Step 2: Add Routes
|
|
52
|
-
|
|
53
|
-
Now let's add routes for our brand pages. Create or update your routes file:
|
|
54
|
-
|
|
55
|
-
```ruby config/routes.rb
|
|
56
|
-
Spree::Core::Engine.add_routes do
|
|
57
|
-
scope '(:locale)', locale: /#{Spree.available_locales.join('|')}/, defaults: { locale: nil } do
|
|
58
|
-
resources :brands, only: [:index, :show]
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
> **INFO:** The `scope '(:locale)'` wrapper enables internationalization - URLs like `/fr/brands` will work automatically if you have French locale enabled.
|
|
64
|
-
|
|
65
|
-
## Step 3: Create the Controller
|
|
66
|
-
|
|
67
|
-
Create a controller that inherits from `Spree::StoreController`. This base controller provides:
|
|
68
|
-
|
|
69
|
-
- Access to `current_store`, `current_theme`, `current_currency`
|
|
70
|
-
- User authentication helpers
|
|
71
|
-
- Storefront layout and helpers
|
|
72
|
-
- SEO and meta tag support
|
|
73
|
-
|
|
74
|
-
```ruby app/controllers/spree/brands_controller.rb
|
|
75
|
-
module Spree
|
|
76
|
-
class BrandsController < StoreController
|
|
77
|
-
before_action :load_brand, only: [:show]
|
|
78
|
-
|
|
79
|
-
def index
|
|
80
|
-
@brands = Spree::Brand.order(:name)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def show
|
|
84
|
-
@products = @brand.products.active(current_currency).includes(storefront_products_includes)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
private
|
|
88
|
-
|
|
89
|
-
def load_brand
|
|
90
|
-
@brand = Spree::Brand.find(params[:id])
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Override to set page title for SEO
|
|
94
|
-
def accurate_title
|
|
95
|
-
if @brand
|
|
96
|
-
@brand.name
|
|
97
|
-
else
|
|
98
|
-
Spree.t(:brands)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Key Points
|
|
106
|
-
|
|
107
|
-
- **Inherit from `Spree::StoreController`** - Not `ApplicationController`. This gives you access to all storefront functionality.
|
|
108
|
-
- **Use `current_store`** - Always scope queries to the current store for multi-store support.
|
|
109
|
-
- **Override `accurate_title`** - This sets the page `<title>` tag.
|
|
110
|
-
|
|
111
|
-
> **TIP:** Want SEO-friendly URLs like `/brands/nike` instead of `/brands/123`? See the [SEO](/developer/tutorial/seo) tutorial to add slug support.
|
|
112
|
-
|
|
113
|
-
## Step 4: Create the Views
|
|
114
|
-
|
|
115
|
-
Storefront views live in your theme directory. Create the brands views in your custom theme:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
mkdir -p app/views/themes/my_store/spree/brands
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Your theme's view structure:
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
app/views/themes/my_store/spree/brands/
|
|
125
|
-
├── index.html.erb
|
|
126
|
-
├── show.html.erb
|
|
127
|
-
└── _brand_card.html.erb
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Brands Listing Page
|
|
131
|
-
|
|
132
|
-
```erb app/views/themes/my_store/spree/brands/index.html.erb
|
|
133
|
-
<div class="page-container py-8">
|
|
134
|
-
<h1 class="text-2xl lg:text-3xl font-medium mb-8">
|
|
135
|
-
<%= Spree.t(:brands) %>
|
|
136
|
-
</h1>
|
|
137
|
-
|
|
138
|
-
<% if @brands.any? %>
|
|
139
|
-
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
|
140
|
-
<% @brands.each do |brand| %>
|
|
141
|
-
<%= render 'brand_card', brand: brand %>
|
|
142
|
-
<% end %>
|
|
143
|
-
</div>
|
|
144
|
-
<% else %>
|
|
145
|
-
<p class="text-neutral-600">
|
|
146
|
-
<%= Spree.t(:no_brands_found) %>
|
|
147
|
-
</p>
|
|
148
|
-
<% end %>
|
|
149
|
-
</div>
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Brand Card Partial
|
|
153
|
-
|
|
154
|
-
```erb app/views/themes/my_store/spree/brands/_brand_card.html.erb
|
|
155
|
-
<%= link_to spree.brand_path(brand),
|
|
156
|
-
class: 'block group',
|
|
157
|
-
data: { turbo_frame: '_top' } do %>
|
|
158
|
-
<div class="aspect-square bg-accent rounded-lg overflow-hidden mb-3">
|
|
159
|
-
<% if brand.logo.attached? %>
|
|
160
|
-
<%= spree_image_tag brand.logo,
|
|
161
|
-
width: 300,
|
|
162
|
-
height: 300,
|
|
163
|
-
class: 'w-full h-full object-contain p-6 group-hover:scale-105 transition-transform',
|
|
164
|
-
alt: brand.name %>
|
|
165
|
-
<% else %>
|
|
166
|
-
<div class="w-full h-full flex items-center justify-center">
|
|
167
|
-
<span class="text-4xl font-medium text-neutral-400">
|
|
168
|
-
<%= brand.name.first.upcase %>
|
|
169
|
-
</span>
|
|
170
|
-
</div>
|
|
171
|
-
<% end %>
|
|
172
|
-
</div>
|
|
173
|
-
<h2 class="font-medium group-hover:text-primary transition-colors">
|
|
174
|
-
<%= brand.name %>
|
|
175
|
-
</h2>
|
|
176
|
-
<% end %>
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Single Brand Page
|
|
180
|
-
|
|
181
|
-
```erb app/views/themes/my_store/spree/brands/show.html.erb
|
|
182
|
-
<div class="page-container py-8">
|
|
183
|
-
<%# Brand Header %>
|
|
184
|
-
<div class="flex flex-col md:flex-row gap-8 mb-12">
|
|
185
|
-
<% if @brand.logo.attached? %>
|
|
186
|
-
<div class="w-32 h-32 bg-accent rounded-lg flex-shrink-0">
|
|
187
|
-
<%= spree_image_tag @brand.logo,
|
|
188
|
-
width: 128,
|
|
189
|
-
height: 128,
|
|
190
|
-
class: 'w-full h-full object-contain p-4',
|
|
191
|
-
alt: @brand.name %>
|
|
192
|
-
</div>
|
|
193
|
-
<% end %>
|
|
194
|
-
|
|
195
|
-
<div>
|
|
196
|
-
<h1 class="text-2xl lg:text-3xl font-medium mb-4">
|
|
197
|
-
<%= @brand.name %>
|
|
198
|
-
</h1>
|
|
199
|
-
|
|
200
|
-
<% if @brand.description.present? %>
|
|
201
|
-
<div class="prose max-w-none text-neutral-600">
|
|
202
|
-
<%= @brand.description %>
|
|
203
|
-
</div>
|
|
204
|
-
<% end %>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<%# Products Grid %>
|
|
209
|
-
<% if @products.any? %>
|
|
210
|
-
<h2 class="text-xl font-medium mb-6">
|
|
211
|
-
<%= Spree.t(:products_by_brand, brand: @brand.name) %>
|
|
212
|
-
</h2>
|
|
213
|
-
|
|
214
|
-
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
|
215
|
-
<%= render 'spree/shared/products', products: @products %>
|
|
216
|
-
</div>
|
|
217
|
-
<% else %>
|
|
218
|
-
<p class="text-neutral-600">
|
|
219
|
-
<%= Spree.t(:no_products_found) %>
|
|
220
|
-
</p>
|
|
221
|
-
<% end %>
|
|
222
|
-
</div>
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## Step 5: Add Translations
|
|
226
|
-
|
|
227
|
-
Add the necessary translations:
|
|
228
|
-
|
|
229
|
-
```yaml config/locales/en.yml
|
|
230
|
-
en:
|
|
231
|
-
spree:
|
|
232
|
-
brands: Brands
|
|
233
|
-
no_brands_found: No brands found.
|
|
234
|
-
products_by_brand: "Products by %{brand}"
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Step 6: Add Navigation Link (Optional)
|
|
238
|
-
|
|
239
|
-
To add a link to brands in your header navigation, you can use the Admin Dashboard:
|
|
240
|
-
|
|
241
|
-
1. Go to **Storefront → Theme Editor**
|
|
242
|
-
2. Click on **Header** section
|
|
243
|
-
3. Add a new navigation link pointing to `/brands`
|
|
244
|
-
|
|
245
|
-
Or programmatically in your header partial:
|
|
246
|
-
|
|
247
|
-
```erb
|
|
248
|
-
<%= link_to Spree.t(:brands), spree.brands_path, class: 'nav-link' %>
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
## Understanding the View Structure
|
|
252
|
-
|
|
253
|
-
### Theme Directory
|
|
254
|
-
|
|
255
|
-
Views are organized by theme in `app/views/themes/{theme_name}/spree/`:
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
app/views/themes/my_store/spree/
|
|
259
|
-
├── brands/ # Your brand views
|
|
260
|
-
├── products/ # Product views
|
|
261
|
-
├── shared/ # Shared partials
|
|
262
|
-
└── page_sections/ # Page Builder sections
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
> **INFO:** Spree looks for views in your active theme first. If a view isn't found, it falls back to the default theme. This means you only need to copy and customize the files you want to change.
|
|
266
|
-
|
|
267
|
-
### Key CSS Classes
|
|
268
|
-
|
|
269
|
-
Spree's default theme uses these common patterns:
|
|
270
|
-
|
|
271
|
-
| Class | Purpose |
|
|
272
|
-
|-------|---------|
|
|
273
|
-
| `page-container` | Centered container with max-width and padding |
|
|
274
|
-
| `bg-accent` | Uses theme's accent background color |
|
|
275
|
-
| `text-primary` | Uses theme's primary text color |
|
|
276
|
-
| `btn-primary` | Primary button style |
|
|
277
|
-
| `btn-secondary` | Secondary button style |
|
|
278
|
-
|
|
279
|
-
### Using Spree Helpers
|
|
280
|
-
|
|
281
|
-
```erb
|
|
282
|
-
<%# Image helper with automatic optimization %>
|
|
283
|
-
<%= spree_image_tag image, width: 400, height: 400 %>
|
|
284
|
-
|
|
285
|
-
<%# URL helpers %>
|
|
286
|
-
<%= spree.brands_path %>
|
|
287
|
-
<%= spree.brand_path(brand) %>
|
|
288
|
-
|
|
289
|
-
<%# Translation helper %>
|
|
290
|
-
<%= Spree.t(:brands) %>
|
|
291
|
-
|
|
292
|
-
<%# Price display %>
|
|
293
|
-
<%= display_price(product.price_in(current_currency)) %>
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## Testing Your Pages
|
|
297
|
-
|
|
298
|
-
Start your Rails server and visit:
|
|
299
|
-
|
|
300
|
-
- `http://localhost:3000/brands` - Brand listing
|
|
301
|
-
- `http://localhost:3000/brands/1` - Single brand (using the brand's ID)
|
|
302
|
-
|
|
303
|
-
> **TIP:** Want SEO-friendly URLs like `/brands/nike` instead of `/brands/1`? See the [SEO](/developer/tutorial/seo) tutorial to add slug support, meta tags, and Open Graph data.
|
|
304
|
-
|
|
305
|
-
## What's Next?
|
|
306
|
-
|
|
307
|
-
You now have working brand pages using standard Rails MVC patterns. In the next guide, [Testing](/developer/tutorial/testing), we'll write automated tests for your feature to make sure it works as expected, also in the future when you make changes to your code.
|
|
308
|
-
|
|
309
|
-
## Complete Controller Example
|
|
310
|
-
|
|
311
|
-
Here's the complete controller with all features:
|
|
312
|
-
|
|
313
|
-
```ruby app/controllers/spree/brands_controller.rb
|
|
314
|
-
module Spree
|
|
315
|
-
class BrandsController < StoreController
|
|
316
|
-
before_action :load_brand, only: [:show]
|
|
317
|
-
|
|
318
|
-
def index
|
|
319
|
-
@brands = Spree::Brand.order(:name)
|
|
320
|
-
.page(params[:page])
|
|
321
|
-
.per(24)
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def show
|
|
325
|
-
@products = @brand.products.active(current_currency).includes(storefront_products_includes)
|
|
326
|
-
@page_description = @brand.description&.to_plain_text&.truncate(160)
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
private
|
|
330
|
-
|
|
331
|
-
def load_brand
|
|
332
|
-
@brand = Spree::Brand.find(params[:id])
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def accurate_title
|
|
336
|
-
if @brand
|
|
337
|
-
@brand.name
|
|
338
|
-
else
|
|
339
|
-
Spree.t(:brands)
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
end
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
> **INFO:** To use SEO-friendly slugs like `/brands/nike` instead of `/brands/1`, follow the [SEO](/developer/tutorial/seo) tutorial.
|
|
347
|
-
|
|
348
|
-
## Related Documentation
|
|
349
|
-
|
|
350
|
-
- [Storefront Overview](/developer/storefront/storefront) - Storefront architecture
|
|
351
|
-
- [Custom CSS](/developer/storefront/custom-css) - Styling with Tailwind
|
|
352
|
-
- [Helper Methods](/developer/storefront/helper-methods) - Available helpers
|