@spree/docs 0.1.0

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 (183) hide show
  1. package/README.md +54 -0
  2. package/dist/api-reference/platform/authentication.md +38 -0
  3. package/dist/api-reference/store-api/authentication.md +188 -0
  4. package/dist/api-reference/store-api/errors.md +277 -0
  5. package/dist/api-reference/store-api/idempotency.md +129 -0
  6. package/dist/api-reference/store-api/introduction.md +34 -0
  7. package/dist/api-reference/store-api/localization.md +279 -0
  8. package/dist/api-reference/store-api/metadata.md +160 -0
  9. package/dist/api-reference/store-api/monetary-amounts.md +65 -0
  10. package/dist/api-reference/store-api/querying.md +399 -0
  11. package/dist/api-reference/store-api/rate-limitting.md +103 -0
  12. package/dist/api-reference/store-api/relations.md +185 -0
  13. package/dist/api-reference/storefront/authentication.md +88 -0
  14. package/dist/api-reference/tutorials/adyen-integration-guide-for-android.md +165 -0
  15. package/dist/api-reference/tutorials/adyen-integration-guide-for-ios.md +194 -0
  16. package/dist/api-reference/tutorials/quick-checkout-with-stripe.md +248 -0
  17. package/dist/api-reference/v2/fetching-multiple-resources.md +26 -0
  18. package/dist/api-reference/v2/filtering-and-sorting.md +53 -0
  19. package/dist/api-reference/v2/introduction.md +22 -0
  20. package/dist/api-reference/v2/pagination.md +37 -0
  21. package/dist/api-reference/webhooks-events.md +883 -0
  22. package/dist/developer/admin/admin.md +205 -0
  23. package/dist/developer/admin/authentication.md +59 -0
  24. package/dist/developer/admin/components.md +711 -0
  25. package/dist/developer/admin/custom-css.md +243 -0
  26. package/dist/developer/admin/custom-javascript.md +116 -0
  27. package/dist/developer/admin/extending-ui.md +1964 -0
  28. package/dist/developer/admin/form-builder.md +444 -0
  29. package/dist/developer/admin/helper-methods.md +531 -0
  30. package/dist/developer/admin/navigation.md +805 -0
  31. package/dist/developer/admin/tables.md +491 -0
  32. package/dist/developer/advanced/adding_spree_to_rails_app.md +106 -0
  33. package/dist/developer/cli/quickstart.md +137 -0
  34. package/dist/developer/contributing/creating-an-extension.md +258 -0
  35. package/dist/developer/contributing/developing-spree.md +339 -0
  36. package/dist/developer/contributing/quickstart.md +32 -0
  37. package/dist/developer/contributing/updating-extensions.md +67 -0
  38. package/dist/developer/core-concepts/addresses.md +265 -0
  39. package/dist/developer/core-concepts/adjustments.md +107 -0
  40. package/dist/developer/core-concepts/architecture.md +177 -0
  41. package/dist/developer/core-concepts/calculators.md +323 -0
  42. package/dist/developer/core-concepts/customers.md +230 -0
  43. package/dist/developer/core-concepts/events.md +624 -0
  44. package/dist/developer/core-concepts/imports-exports.md +698 -0
  45. package/dist/developer/core-concepts/inventory.md +191 -0
  46. package/dist/developer/core-concepts/markets.md +250 -0
  47. package/dist/developer/core-concepts/media.md +167 -0
  48. package/dist/developer/core-concepts/metafields.md +187 -0
  49. package/dist/developer/core-concepts/orders.md +328 -0
  50. package/dist/developer/core-concepts/payments.md +710 -0
  51. package/dist/developer/core-concepts/pricing.md +163 -0
  52. package/dist/developer/core-concepts/products.md +360 -0
  53. package/dist/developer/core-concepts/promotions.md +322 -0
  54. package/dist/developer/core-concepts/reports.md +206 -0
  55. package/dist/developer/core-concepts/search-filtering.md +237 -0
  56. package/dist/developer/core-concepts/shipments.md +212 -0
  57. package/dist/developer/core-concepts/slugs.md +111 -0
  58. package/dist/developer/core-concepts/staff-roles.md +123 -0
  59. package/dist/developer/core-concepts/store-credits-gift-cards.md +317 -0
  60. package/dist/developer/core-concepts/stores.md +117 -0
  61. package/dist/developer/core-concepts/taxes.md +135 -0
  62. package/dist/developer/core-concepts/translations.md +120 -0
  63. package/dist/developer/core-concepts/users.md +299 -0
  64. package/dist/developer/core-concepts/webhooks.md +378 -0
  65. package/dist/developer/create-spree-app/quickstart.md +158 -0
  66. package/dist/developer/customization/api.md +93 -0
  67. package/dist/developer/customization/authentication.md +88 -0
  68. package/dist/developer/customization/checkout.md +204 -0
  69. package/dist/developer/customization/configuration.md +55 -0
  70. package/dist/developer/customization/decorators.md +523 -0
  71. package/dist/developer/customization/dependencies.md +232 -0
  72. package/dist/developer/customization/emails.md +21 -0
  73. package/dist/developer/customization/extensions.md +92 -0
  74. package/dist/developer/customization/metadata.md +236 -0
  75. package/dist/developer/customization/model-preferences.md +130 -0
  76. package/dist/developer/customization/permissions.md +265 -0
  77. package/dist/developer/customization/quickstart.md +229 -0
  78. package/dist/developer/customization/routes.md +24 -0
  79. package/dist/developer/customization/v4/admin-panel.md +78 -0
  80. package/dist/developer/customization/v4/authentication.md +210 -0
  81. package/dist/developer/customization/v4/checkout.md +212 -0
  82. package/dist/developer/customization/v4/deface.md +251 -0
  83. package/dist/developer/customization/v4/images.md +86 -0
  84. package/dist/developer/customization/v4/storefront.md +450 -0
  85. package/dist/developer/deployment/assets.md +87 -0
  86. package/dist/developer/deployment/aws.md +335 -0
  87. package/dist/developer/deployment/caching.md +27 -0
  88. package/dist/developer/deployment/cdn.md +39 -0
  89. package/dist/developer/deployment/database.md +155 -0
  90. package/dist/developer/deployment/docker.md +128 -0
  91. package/dist/developer/deployment/emails.md +77 -0
  92. package/dist/developer/deployment/environment_variables.md +111 -0
  93. package/dist/developer/deployment/heroku.md +51 -0
  94. package/dist/developer/deployment/render.md +95 -0
  95. package/dist/developer/getting-started/quickstart.md +82 -0
  96. package/dist/developer/how-to/custom-payment-method.md +374 -0
  97. package/dist/developer/how-to/custom-promotion.md +373 -0
  98. package/dist/developer/how-to/custom-report.md +387 -0
  99. package/dist/developer/how-to/custom-search-provider.md +230 -0
  100. package/dist/developer/multi-store/quickstart.md +71 -0
  101. package/dist/developer/multi-store/setup.md +38 -0
  102. package/dist/developer/multi-tenant/configuration.md +41 -0
  103. package/dist/developer/multi-tenant/core-concepts.md +75 -0
  104. package/dist/developer/multi-tenant/installation.md +96 -0
  105. package/dist/developer/multi-tenant/quickstart.md +20 -0
  106. package/dist/developer/multi-vendor/installation.md +45 -0
  107. package/dist/developer/multi-vendor/quickstart.md +17 -0
  108. package/dist/developer/sdk/admin/quickstart.md +22 -0
  109. package/dist/developer/sdk/authentication.md +89 -0
  110. package/dist/developer/sdk/configuration.md +225 -0
  111. package/dist/developer/sdk/quickstart.md +82 -0
  112. package/dist/developer/sdk/store/account.md +67 -0
  113. package/dist/developer/sdk/store/cart-checkout.md +140 -0
  114. package/dist/developer/sdk/store/markets.md +151 -0
  115. package/dist/developer/sdk/store/payments.md +96 -0
  116. package/dist/developer/sdk/store/products.md +149 -0
  117. package/dist/developer/sdk/store/wishlists.md +52 -0
  118. package/dist/developer/security/pci_compliance.md +15 -0
  119. package/dist/developer/security/security_policy.md +68 -0
  120. package/dist/developer/storefront/blocks.md +285 -0
  121. package/dist/developer/storefront/custom-css.md +260 -0
  122. package/dist/developer/storefront/custom-javascript.md +166 -0
  123. package/dist/developer/storefront/helper-methods.md +1288 -0
  124. package/dist/developer/storefront/links.md +298 -0
  125. package/dist/developer/storefront/nextjs/architecture.md +150 -0
  126. package/dist/developer/storefront/nextjs/customization.md +141 -0
  127. package/dist/developer/storefront/nextjs/deployment.md +180 -0
  128. package/dist/developer/storefront/nextjs/quickstart.md +92 -0
  129. package/dist/developer/storefront/nextjs/spree-next-package.md +314 -0
  130. package/dist/developer/storefront/pages.md +163 -0
  131. package/dist/developer/storefront/sections.md +569 -0
  132. package/dist/developer/storefront/storefront.md +56 -0
  133. package/dist/developer/storefront/themes.md +161 -0
  134. package/dist/developer/tutorial/admin.md +134 -0
  135. package/dist/developer/tutorial/extending-models.md +380 -0
  136. package/dist/developer/tutorial/file-uploads.md +121 -0
  137. package/dist/developer/tutorial/introduction.md +33 -0
  138. package/dist/developer/tutorial/model.md +41 -0
  139. package/dist/developer/tutorial/page-builder.md +487 -0
  140. package/dist/developer/tutorial/rich-text.md +73 -0
  141. package/dist/developer/tutorial/seo.md +332 -0
  142. package/dist/developer/tutorial/storefront.md +352 -0
  143. package/dist/developer/tutorial/testing.md +558 -0
  144. package/dist/developer/upgrades/2.0-to-2.1.md +46 -0
  145. package/dist/developer/upgrades/2.1-to-2.2.md +59 -0
  146. package/dist/developer/upgrades/2.2-to-2.3.md +44 -0
  147. package/dist/developer/upgrades/2.3-to-2.4.md +42 -0
  148. package/dist/developer/upgrades/3.0-to-3.1.md +47 -0
  149. package/dist/developer/upgrades/3.1-to-3.2.md +34 -0
  150. package/dist/developer/upgrades/3.2-to-3.3.md +70 -0
  151. package/dist/developer/upgrades/3.3-to-3.4.md +36 -0
  152. package/dist/developer/upgrades/3.4-to-3.5.md +44 -0
  153. package/dist/developer/upgrades/3.5-to-3.6.md +40 -0
  154. package/dist/developer/upgrades/3.6-to-3.7.md +62 -0
  155. package/dist/developer/upgrades/3.7-to-4.0.md +152 -0
  156. package/dist/developer/upgrades/4.0-to-4.1.md +92 -0
  157. package/dist/developer/upgrades/4.1-to-4.2.md +109 -0
  158. package/dist/developer/upgrades/4.10-to-5.0.md +129 -0
  159. package/dist/developer/upgrades/4.2-to-4.3.md +100 -0
  160. package/dist/developer/upgrades/4.3-to-4.4.md +125 -0
  161. package/dist/developer/upgrades/4.4-to-4.5.md +94 -0
  162. package/dist/developer/upgrades/4.5-to-4.6.md +119 -0
  163. package/dist/developer/upgrades/4.6-to-4.7.md +39 -0
  164. package/dist/developer/upgrades/4.8-to-4.9.md +24 -0
  165. package/dist/developer/upgrades/4.9-to-4.10.md +24 -0
  166. package/dist/developer/upgrades/4.x-to-4.8.md +52 -0
  167. package/dist/developer/upgrades/5.0-to-5.1.md +28 -0
  168. package/dist/developer/upgrades/5.1-to-5.2.md +127 -0
  169. package/dist/developer/upgrades/5.2-to-5.3.md +338 -0
  170. package/dist/developer/upgrades/5.3-to-5.4.md +248 -0
  171. package/dist/developer/upgrades/quickstart.md +36 -0
  172. package/dist/integrations/analytics/google-analytics.md +64 -0
  173. package/dist/integrations/analytics/google-tag-manager.md +78 -0
  174. package/dist/integrations/integrations.md +39 -0
  175. package/dist/integrations/marketing/klaviyo.md +99 -0
  176. package/dist/integrations/payments/adyen.md +90 -0
  177. package/dist/integrations/payments/paypal.md +41 -0
  178. package/dist/integrations/payments/razorpay.md +45 -0
  179. package/dist/integrations/payments/stripe.md +109 -0
  180. package/dist/integrations/search/meilisearch.md +236 -0
  181. package/dist/integrations/sso-mfa-social-login/admin-dashboard.md +57 -0
  182. package/dist/integrations/sso-mfa-social-login/storefront.md +56 -0
  183. package/package.json +27 -0
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Rich Text
3
+ description: Learn how to add rich text content to the Brands feature using Action Text
4
+ ---
5
+
6
+ > **INFO:** This guide assumes you've completed the [Model](/developer/tutorial/model) and [Admin](/developer/tutorial/admin) tutorials.
7
+
8
+ Our Brand model has a `name` attribute to store the brand name. However it's missing a description. That's because we're going to use [Action Text](https://guides.rubyonrails.org/action_text_overview.html) to handle the rich text content.
9
+
10
+ ## Step 1: Add Action Text to the Model
11
+
12
+ ```ruby app/models/spree/brand.rb {4-5}
13
+ module Spree
14
+ class Brand < Spree::Base
15
+ # Action text for rich text content
16
+ has_rich_text :description
17
+
18
+ # ... other code ...
19
+ end
20
+ end
21
+ ```
22
+
23
+ You don't need to run any migrations, as we won't be adding any new columns to the `spree_brands` table. That's because [Action Text](https://guides.rubyonrails.org/action_text_overview.html) stores all the rich text content in a separate table called `action_text_rich_texts`.
24
+
25
+ You can access the description content in code like this:
26
+
27
+ ```ruby
28
+ brand.description.to_s
29
+ # => "<h1>Hello</h1><p>World</p>"
30
+ ```
31
+
32
+ Or if you prefer plain text:
33
+
34
+ ```ruby
35
+ brand.description.to_plain_text
36
+ # => "Hello World"
37
+ ```
38
+
39
+ ## Step 2: Add a WYSIWYG editor to the Admin Dashboard
40
+
41
+ In the Admin dashboard we want to use a WYSIWYG editor to add formatted text to the brand description. To do this, edit the `app/views/spree/admin/brands/_form.html.erb` file and add the following code:
42
+
43
+ ```erb app/views/spree/admin/brands/_form.html.erb {4}
44
+ <div class="card mb-6">
45
+ <div class="card-body">
46
+ <%= f.spree_text_field :name %>
47
+ <%= f.spree_rich_text_area :description %>
48
+ </div>
49
+ </div>
50
+ ```
51
+
52
+ This will render a WYSIWYG editor to the brand description field in the Admin dashboard.
53
+
54
+ ## Step 3: Update permitted parameters in controller
55
+
56
+ To permit the `description` attribute to be saved, we need to update the permitted parameters in the controller. file and add the following code:
57
+
58
+ ```ruby app/controllers/spree/admin/brands_controller.rb {9}
59
+ module Spree
60
+ module Admin
61
+ class BrandsController < ResourceController
62
+ private
63
+
64
+ def permitted_resource_params
65
+ params.require(:brand).permit(
66
+ :name,
67
+ :description
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ ```
@@ -0,0 +1,332 @@
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