@thorprovider/create-storefront 0.1.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.
- package/README.md +119 -0
- package/bin/install.js +116 -0
- package/commands/sf-add-view.md +21 -0
- package/commands/sf-init.md +16 -0
- package/commands/sf-theme.md +15 -0
- package/commands/sf-view.md +20 -0
- package/package.json +40 -0
- package/recipes/archetype.schema.json +39 -0
- package/recipes/archetypes.json +148 -0
- package/recipes/recipe.schema.json +59 -0
- package/recipes/recipes.json +90 -0
- package/recipes/sections.json +46 -0
- package/recipes/validate.mjs +190 -0
- package/skills/building-storefronts/SKILL.md +178 -0
- package/skills/building-storefronts/references/frontend-integration.md +229 -0
- package/skills/json-render-core/SKILL.md +291 -0
- package/skills/json-render-next/SKILL.md +194 -0
- package/skills/json-render-react/SKILL.md +298 -0
- package/skills/json-render-remotion/SKILL.md +111 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/json-render-solid/SKILL.md +204 -0
- package/skills/nextjs-shadcn/SKILL.md +303 -0
- package/skills/nextjs-shadcn/references/architecture.md +499 -0
- package/skills/nextjs-shadcn/references/project-setup.md +127 -0
- package/skills/nextjs-shadcn/references/shadcn-platform.md +258 -0
- package/skills/nextjs-shadcn/references/sidebar.md +274 -0
- package/skills/nextjs-shadcn/references/styling.md +555 -0
- package/skills/sf-scaffold/SKILL.md +118 -0
- package/skills/sf-theme-gen/SKILL.md +44 -0
- package/skills/sf-view-gen/SKILL.md +94 -0
- package/skills/shadcn-component-discovery/SKILL.md +273 -0
- package/skills/shadcn-component-discovery/references/registries.md +226 -0
- package/skills/shadcn-theming/SKILL.md +104 -0
- package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
- package/skills/shadcn-theming/references/theming-guide.md +90 -0
- package/skills/storefront-best-practices/SKILL.md +421 -0
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
- package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
- package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
- package/skills/storefront-best-practices/reference/components/footer.md +112 -0
- package/skills/storefront-best-practices/reference/components/hero.md +241 -0
- package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
- package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
- package/skills/storefront-best-practices/reference/components/popups.md +221 -0
- package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
- package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
- package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
- package/skills/storefront-best-practices/reference/components/search.md +101 -0
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
- package/skills/storefront-best-practices/reference/design.md +388 -0
- package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
- package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
- package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
- package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
- package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
- package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
- package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
- package/skills/storefront-best-practices/reference/medusa.md +307 -0
- package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
- package/skills/storefront-best-practices/reference/seo.md +195 -0
- package/templates/app/app/[[...slug]]/page.tsx +17 -0
- package/templates/app/app/[[...slug]]/renderer.tsx +10 -0
- package/templates/app/app/globals.css +101 -0
- package/templates/app/app/layout.tsx +35 -0
- package/templates/app/lib/__STOREFRONT__/catalog.ts +132 -0
- package/templates/app/lib/__STOREFRONT__/handlers.ts +33 -0
- package/templates/app/lib/__STOREFRONT__/registry.tsx +134 -0
- package/templates/app/lib/__STOREFRONT__/runtime.ts +25 -0
- package/templates/app/lib/__STOREFRONT__/spec/home.ts +62 -0
- package/templates/app/lib/__STOREFRONT__/spec/index.ts +59 -0
- package/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
- package/templates/app/lib/__STOREFRONT__/state.ts +35 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Mobile Responsiveness for Ecommerce Storefronts
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Mobile Ecommerce Patterns](#mobile-ecommerce-patterns)
|
|
7
|
+
- [Touch-Friendly Interactions](#touch-friendly-interactions)
|
|
8
|
+
- [Mobile Performance](#mobile-performance)
|
|
9
|
+
- [Safe Area Insets (iOS)](#safe-area-insets-ios)
|
|
10
|
+
- [Common Mobile Mistakes](#common-mobile-mistakes)
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Over 60% of ecommerce traffic is mobile. Mobile-first design is essential for conversion.
|
|
15
|
+
|
|
16
|
+
### Key Requirements
|
|
17
|
+
|
|
18
|
+
- Mobile-first CSS (min-width media queries)
|
|
19
|
+
- 44x44px minimum touch targets
|
|
20
|
+
- Sticky header with cart access
|
|
21
|
+
- Large form inputs (48px height minimum)
|
|
22
|
+
- Optimized images for mobile
|
|
23
|
+
- Fast loading (LCP < 2.5s)
|
|
24
|
+
|
|
25
|
+
**Assumed knowledge**: AI agents already know mobile-first design principles, breakpoints, and responsive CSS. This guide focuses on ecommerce-specific mobile patterns.
|
|
26
|
+
|
|
27
|
+
## Mobile Ecommerce Patterns
|
|
28
|
+
|
|
29
|
+
### Sticky Elements (Critical for Conversion)
|
|
30
|
+
|
|
31
|
+
**Cart access always visible:**
|
|
32
|
+
- Sticky header with cart icon (top-right)
|
|
33
|
+
- Or: Sticky bottom navigation with cart
|
|
34
|
+
- Never hide cart in hamburger drawer
|
|
35
|
+
- Shows count badge, updates in real-time
|
|
36
|
+
|
|
37
|
+
**Sticky "Add to Cart" bar (product pages):**
|
|
38
|
+
- Fixed at bottom of screen
|
|
39
|
+
- Shows: Price + "Add to Cart" button
|
|
40
|
+
- Appears after scrolling past fold
|
|
41
|
+
- Always accessible without scrolling
|
|
42
|
+
- **CRITICAL: Must use `env(safe-area-inset-bottom)` for iOS devices** (see Safe Area Insets section)
|
|
43
|
+
- Significantly higher conversion rates
|
|
44
|
+
|
|
45
|
+
### Mobile Navigation Patterns
|
|
46
|
+
|
|
47
|
+
**Bottom navigation (optional pattern):**
|
|
48
|
+
- Consider for mobile-heavy stores (>70% mobile traffic)
|
|
49
|
+
- 4-5 primary actions: Home, Categories, Cart, Account, Search
|
|
50
|
+
- Fixed at bottom (easier thumb access)
|
|
51
|
+
- Icons + labels for clarity
|
|
52
|
+
|
|
53
|
+
**When to use:**
|
|
54
|
+
- Mobile-first brands (fashion, beauty)
|
|
55
|
+
- Younger demographic (18-34)
|
|
56
|
+
- App-like experience desired
|
|
57
|
+
|
|
58
|
+
**When NOT to use:**
|
|
59
|
+
- Desktop-heavy traffic
|
|
60
|
+
- Complex navigation needs (>5 items)
|
|
61
|
+
- B2B stores (desktop-focused)
|
|
62
|
+
|
|
63
|
+
### Mobile Product Browsing
|
|
64
|
+
|
|
65
|
+
**Image galleries:**
|
|
66
|
+
- Full-width swipeable carousel
|
|
67
|
+
- Pinch to zoom
|
|
68
|
+
- Tap to open full-screen view
|
|
69
|
+
- Dot indicators (1/5, 2/5)
|
|
70
|
+
|
|
71
|
+
**Filter drawer:**
|
|
72
|
+
- "Filters" button with badge count (e.g., "Filters (3)")
|
|
73
|
+
- Slide-out drawer (full-screen or 80% width)
|
|
74
|
+
- Accordion sections for filter categories
|
|
75
|
+
- "Apply" button at bottom (batch filtering)
|
|
76
|
+
- "Clear All" option at top
|
|
77
|
+
|
|
78
|
+
**Why batch filtering on mobile:**
|
|
79
|
+
- Prevents multiple re-renders on slow connections
|
|
80
|
+
- User adjusts multiple filters before applying
|
|
81
|
+
- Less disruptive mobile UX
|
|
82
|
+
|
|
83
|
+
### Mobile Checkout Optimization
|
|
84
|
+
|
|
85
|
+
**Digital wallets priority (CRITICAL for mobile conversion):**
|
|
86
|
+
- Apple Pay / Google Pay buttons prominent at top (if supported in ecommerce backend)
|
|
87
|
+
- Can improve mobile checkout conversion by 20-40%
|
|
88
|
+
- One-click payment with pre-filled shipping addresses (if supported in ecommerce backend)
|
|
89
|
+
- Consider making digital wallet the default on mobile
|
|
90
|
+
|
|
91
|
+
**Decision: Order summary placement**
|
|
92
|
+
- Collapsible at top (recommended): Saves screen space for form, expandable for review
|
|
93
|
+
- Fixed at bottom: Always visible but takes space from form
|
|
94
|
+
- Use collapsible on mobile to prioritize form completion
|
|
95
|
+
|
|
96
|
+
**Form optimizations:**
|
|
97
|
+
- Single-column layout (never multi-column on mobile)
|
|
98
|
+
- 44-48px input height minimum
|
|
99
|
+
- Proper keyboard types (`inputMode="email"`, `"numeric"`, `"tel"`)
|
|
100
|
+
- Autocomplete attributes for autofill (`autocomplete="email"`, `"name"`, `"address-line1"`)
|
|
101
|
+
- Consider single-page layout over multi-step (less friction on mobile)
|
|
102
|
+
|
|
103
|
+
## Touch-Friendly Interactions
|
|
104
|
+
|
|
105
|
+
**Standard touch targets:** 44x44px minimum for all interactive elements. Pay special attention to:
|
|
106
|
+
- Filter checkboxes on product listings
|
|
107
|
+
- Quantity +/- buttons on product pages
|
|
108
|
+
- Small action buttons on product cards
|
|
109
|
+
- Modal close buttons
|
|
110
|
+
|
|
111
|
+
**Swipe gestures for ecommerce:**
|
|
112
|
+
- Product image galleries (critical - users expect swipeable images)
|
|
113
|
+
- Related product sliders
|
|
114
|
+
- Category carousels
|
|
115
|
+
|
|
116
|
+
**Mobile input optimization:**
|
|
117
|
+
- 16px minimum font size for inputs (prevents iOS auto-zoom)
|
|
118
|
+
- Proper `inputMode` attributes: `"email"`, `"numeric"`, `"tel"`
|
|
119
|
+
- Autocomplete attributes: `autocomplete="email"`, `"name"`, `"address-line1"`
|
|
120
|
+
|
|
121
|
+
## Mobile Performance
|
|
122
|
+
|
|
123
|
+
**Ecommerce performance priorities:**
|
|
124
|
+
|
|
125
|
+
1. **Product images** (highest impact): Optimize for mobile (<500KB), lazy load below-fold, responsive images with appropriate sizes
|
|
126
|
+
2. **Optimistic UI**: Cart count updates immediately, instant feedback on add to cart
|
|
127
|
+
3. **Skeleton screens**: Show loading placeholders for product grids, not blank pages
|
|
128
|
+
|
|
129
|
+
**Critical mobile performance issues:**
|
|
130
|
+
- Unoptimized product images (>1MB) - most common issue
|
|
131
|
+
- Loading entire product catalog at once - use pagination or infinite scroll
|
|
132
|
+
- Heavy analytics scripts on checkout - defer to post-purchase
|
|
133
|
+
|
|
134
|
+
**Target**: LCP < 2.5s, mobile-optimized images, server-side rendering for product pages
|
|
135
|
+
|
|
136
|
+
## Safe Area Insets (iOS)
|
|
137
|
+
|
|
138
|
+
Use `env(safe-area-inset-*)` to handle iOS notches and rounded corners on:
|
|
139
|
+
- Sticky headers (top inset)
|
|
140
|
+
- Sticky bottom navigation or add-to-cart bars (bottom inset)
|
|
141
|
+
- Full-screen modals
|
|
142
|
+
|
|
143
|
+
**Critical for ecommerce**: Bottom "Add to Cart" bars will be cut off by iOS home indicator without bottom inset (~34px). Test on real iOS devices with notches.
|
|
144
|
+
|
|
145
|
+
## Common Mobile Mistakes
|
|
146
|
+
|
|
147
|
+
**Ecommerce-specific mobile issues:**
|
|
148
|
+
|
|
149
|
+
1. **Hiding cart in drawer** - Cart icon hidden in hamburger menu. Keep cart always visible in header (top-right).
|
|
150
|
+
|
|
151
|
+
2. **No sticky cart access** - Cart scrolls off screen on product pages. Use sticky header or sticky bottom "Add to Cart" bar.
|
|
152
|
+
|
|
153
|
+
3. **Desktop-sized images** - Serving 2MB+ product images to mobile. Use responsive images optimized for mobile (<500KB).
|
|
154
|
+
|
|
155
|
+
4. **Poor form experience** - Small inputs, wrong keyboards, no autocomplete. Use 48px inputs, proper `inputMode`, autocomplete attributes.
|
|
156
|
+
|
|
157
|
+
5. **Hover-only interactions** - Quick view, wishlist only work on hover. Add tap handlers, show on tap instead.
|
|
158
|
+
|
|
159
|
+
6. **Ignoring safe area insets** - Bottom "Add to Cart" bars cut off by iOS home indicator. Use `env(safe-area-inset-bottom)` for sticky bottom elements.
|
|
160
|
+
|
|
161
|
+
7. **No digital wallet options** - Missing Apple Pay/Google Pay on mobile checkout. Mobile users expect one-tap checkout options.
|
|
162
|
+
|
|
163
|
+
## Mobile Checklist
|
|
164
|
+
|
|
165
|
+
**Essential mobile optimizations:**
|
|
166
|
+
|
|
167
|
+
- [ ] Mobile-first CSS (min-width media queries)
|
|
168
|
+
- [ ] 44x44px minimum touch targets throughout
|
|
169
|
+
- [ ] Adequate spacing between interactive elements (8-16px)
|
|
170
|
+
- [ ] Sticky header with cart icon (always visible)
|
|
171
|
+
- [ ] Or: Sticky bottom "Add to Cart" bar on product pages
|
|
172
|
+
- [ ] Large form inputs (48px height minimum)
|
|
173
|
+
- [ ] Appropriate input types (`inputMode="email"`, `"numeric"`, `"tel"`)
|
|
174
|
+
- [ ] Swipeable image galleries on product pages
|
|
175
|
+
- [ ] Filter drawer with batch apply on product listings
|
|
176
|
+
- [ ] Digital wallets prominent in checkout (Apple Pay, Google Pay)
|
|
177
|
+
- [ ] Collapsible order summary in checkout
|
|
178
|
+
- [ ] Optimized images for mobile (<500KB)
|
|
179
|
+
- [ ] Lazy loading for below-fold content
|
|
180
|
+
- [ ] Safe area insets for iOS notches (sticky elements)
|
|
181
|
+
- [ ] 16px minimum font size (prevents iOS auto-zoom)
|
|
182
|
+
- [ ] Test on real mobile devices (not just Chrome DevTools)
|
|
183
|
+
- [ ] Core Web Vitals within targets (LCP < 2.5s, CLS < 0.1, INP < 200ms)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# SEO Optimization for Ecommerce Storefronts
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Meta Tags Requirements](#meta-tags-requirements)
|
|
7
|
+
- [Structured Data (Critical for Ecommerce)](#structured-data-critical-for-ecommerce)
|
|
8
|
+
- [Ecommerce URL Patterns](#ecommerce-url-patterns)
|
|
9
|
+
- [Product Page SEO](#product-page-seo)
|
|
10
|
+
- [Duplicate Content Issues](#duplicate-content-issues)
|
|
11
|
+
- [Dynamic Sitemaps](#dynamic-sitemaps)
|
|
12
|
+
- [Common SEO Mistakes](#common-seo-mistakes)
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
SEO is critical for ecommerce - organic search drives high-intent traffic. Proper implementation helps search engines understand products and enables rich results (star ratings, price, availability in search).
|
|
17
|
+
|
|
18
|
+
**Assumed knowledge**: AI agents know basic SEO (meta tags, Open Graph, Core Web Vitals). This guide focuses on ecommerce-specific patterns.
|
|
19
|
+
|
|
20
|
+
### Every Product Page Needs
|
|
21
|
+
|
|
22
|
+
- Unique title and description (product name + features)
|
|
23
|
+
- Product schema with price, availability, rating
|
|
24
|
+
- Breadcrumb schema (category hierarchy)
|
|
25
|
+
- Canonical URL (prevents duplicate content)
|
|
26
|
+
- Descriptive image alt text
|
|
27
|
+
- Fast load time (LCP < 2.5s)
|
|
28
|
+
|
|
29
|
+
## Meta Tags Requirements
|
|
30
|
+
|
|
31
|
+
Generate unique meta tags for every product page dynamically from product data:
|
|
32
|
+
|
|
33
|
+
- **Title**: "Product Name - Key Feature | Store Name" (50-60 characters)
|
|
34
|
+
- **Description**: Key features + USP (150-160 characters)
|
|
35
|
+
- **Open Graph** tags for social sharing (image 1200x630px)
|
|
36
|
+
- **Canonical URL** for variants and category paths
|
|
37
|
+
|
|
38
|
+
**Common mistake**: Same title/description across all products. Generate dynamically from product data.
|
|
39
|
+
|
|
40
|
+
## Structured Data (Critical for Ecommerce)
|
|
41
|
+
|
|
42
|
+
Enables rich results in search (star ratings, price, availability shown directly in search results).
|
|
43
|
+
|
|
44
|
+
### Product Schema (Required on All Product Pages)
|
|
45
|
+
|
|
46
|
+
Implement schema.org Product structured data with these critical fields:
|
|
47
|
+
|
|
48
|
+
**Essential fields:**
|
|
49
|
+
- `name`, `description`, `image`, `sku`, `brand`
|
|
50
|
+
- `offers` object with:
|
|
51
|
+
- `price`: Current price (Medusa: use as-is; other backends: check format)
|
|
52
|
+
- `priceCurrency`: ISO 4217 code (USD, EUR, GBP)
|
|
53
|
+
- `availability`: Must accurately reflect real stock status (InStock, OutOfStock, PreOrder)
|
|
54
|
+
- `priceValidUntil`: Required for Google Shopping (set 30+ days in future)
|
|
55
|
+
|
|
56
|
+
**Critical**: `availability` must be dynamic and accurate. Marking out-of-stock items as InStock violates Google's guidelines.
|
|
57
|
+
|
|
58
|
+
### AggregateRating Schema (When Reviews Exist)
|
|
59
|
+
|
|
60
|
+
Add `aggregateRating` object to Product schema when real reviews exist:
|
|
61
|
+
- `ratingValue`: Average rating (e.g., "4.5")
|
|
62
|
+
- `reviewCount`: Total number of reviews
|
|
63
|
+
- `bestRating`: "5", `worstRating`: "1"
|
|
64
|
+
|
|
65
|
+
Displays star ratings in search results - powerful for CTR. **Only use for real reviews** - fake reviews violate guidelines.
|
|
66
|
+
|
|
67
|
+
### Breadcrumb Schema (Navigation Hierarchy)
|
|
68
|
+
|
|
69
|
+
Implement schema.org BreadcrumbList showing category hierarchy:
|
|
70
|
+
- Home → Category → Product
|
|
71
|
+
- Each level has `position`, `name`, `item` URL
|
|
72
|
+
- Helps search engines understand site structure
|
|
73
|
+
|
|
74
|
+
### Organization Schema (Homepage Only)
|
|
75
|
+
|
|
76
|
+
Add on homepage only: Organization name, URL, logo, contact information. Helps establish brand identity in search.
|
|
77
|
+
|
|
78
|
+
## Ecommerce URL Patterns
|
|
79
|
+
|
|
80
|
+
**Product URLs**: Use readable slugs with hyphens (`/products/wireless-headphones-pro`). Include keywords naturally, keep short (<75 characters), never change URLs.
|
|
81
|
+
|
|
82
|
+
**Category URLs**: Choose consistent structure:
|
|
83
|
+
- Hierarchical (`/categories/electronics/laptops`) for deep catalogs
|
|
84
|
+
- Flat (`/categories/laptops`) for simpler management
|
|
85
|
+
- Don't mix both approaches
|
|
86
|
+
|
|
87
|
+
**Pagination URLs**: Use query parameters (`/products?page=2`). Implement `rel="prev"` and `rel="next"` tags. Each page is canonical to itself (NOT to page 1) so all pages can be indexed.
|
|
88
|
+
|
|
89
|
+
**Filter URLs**: Use query parameters (`/products?color=blue&size=large`).
|
|
90
|
+
|
|
91
|
+
**Canonical decision for filters:**
|
|
92
|
+
- Few filters (2-3): Index filtered pages (primary navigation)
|
|
93
|
+
- Many filters (5+): Canonical to unfiltered (prevents duplicate content)
|
|
94
|
+
- Popular combinations: Consider indexing separately
|
|
95
|
+
|
|
96
|
+
## Product Page SEO
|
|
97
|
+
|
|
98
|
+
**Title tags**: Pattern "Product Name - Key Feature | Store Name" (50-60 characters). Avoid generic titles or keyword stuffing.
|
|
99
|
+
|
|
100
|
+
**Meta descriptions**: Include 2-3 key features + USP (free shipping, returns, warranty) in 150-160 characters. Make it compelling.
|
|
101
|
+
|
|
102
|
+
**Image alt text**: Descriptive and specific. Describe what's visible, include product name and key visual attributes. Don't keyword stuff. Keep under 125 characters.
|
|
103
|
+
|
|
104
|
+
## Duplicate Content Issues
|
|
105
|
+
|
|
106
|
+
### Ecommerce Duplicate Content Challenges
|
|
107
|
+
|
|
108
|
+
**Common scenarios:**
|
|
109
|
+
1. **Product variants**: Same product in multiple colors/sizes
|
|
110
|
+
2. **Multiple categories**: Product listed in multiple categories
|
|
111
|
+
3. **Filter combinations**: Filtered views create unique URLs
|
|
112
|
+
4. **Sort parameters**: Sorted views create unique URLs
|
|
113
|
+
|
|
114
|
+
### Solution: Canonical URLs
|
|
115
|
+
|
|
116
|
+
**Variant handling:**
|
|
117
|
+
- Choose one variant as canonical (usually default)
|
|
118
|
+
- All other variants canonical to that one
|
|
119
|
+
- Example: Blue, Red, Green shirts all canonical to Blue (default)
|
|
120
|
+
|
|
121
|
+
**Multiple category paths:**
|
|
122
|
+
- Choose one category as canonical (usually primary category)
|
|
123
|
+
- Example: Product in both "Electronics" and "Laptops" → canonical to "Laptops"
|
|
124
|
+
|
|
125
|
+
**Filtered/sorted views:**
|
|
126
|
+
- Canonical to unfiltered, default-sorted page
|
|
127
|
+
- Example: `/products?color=blue&sort=price` → canonical to `/products`
|
|
128
|
+
|
|
129
|
+
**Implementation:**
|
|
130
|
+
```html
|
|
131
|
+
<!-- On variant page (Red shirt) -->
|
|
132
|
+
<link rel="canonical" href="https://yourstore.com/products/cotton-tshirt" />
|
|
133
|
+
|
|
134
|
+
<!-- On filtered page -->
|
|
135
|
+
<link rel="canonical" href="https://yourstore.com/products" />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Dynamic Sitemaps
|
|
139
|
+
|
|
140
|
+
Generate sitemaps dynamically from database to help search engines discover all products and categories.
|
|
141
|
+
|
|
142
|
+
**Requirements:**
|
|
143
|
+
- Include all public product and category pages
|
|
144
|
+
- Update `lastModified` when products change (fetch from database)
|
|
145
|
+
- Exclude `noindex` pages and filtered/sorted URLs
|
|
146
|
+
- Split into multiple sitemaps if >50,000 URLs
|
|
147
|
+
- Priority: Homepage (1.0) > Products (0.8) > Categories (0.6)
|
|
148
|
+
|
|
149
|
+
**Critical**: Regenerate sitemap when products are added/updated. Submit sitemap URL to Google Search Console.
|
|
150
|
+
|
|
151
|
+
## Common SEO Mistakes
|
|
152
|
+
|
|
153
|
+
**Ecommerce-specific SEO issues:**
|
|
154
|
+
|
|
155
|
+
1. **Duplicate content** - Same product accessible via multiple URLs (variants, categories). Use canonical URLs to consolidate signals.
|
|
156
|
+
|
|
157
|
+
2. **Missing Product schema** - No structured data on product pages. Implement Product schema for rich results (ratings, price in search).
|
|
158
|
+
|
|
159
|
+
3. **Incorrect availability status** - Marking out-of-stock items as "InStock" in schema. Dynamically set based on real stock levels (violates Google guidelines).
|
|
160
|
+
|
|
161
|
+
4. **Thin product content** - Only image and price, no description. Add detailed descriptions, specifications, reviews (200+ words).
|
|
162
|
+
|
|
163
|
+
5. **Static sitemap** - Never updates when products change. Generate dynamically from database so search engines discover new products.
|
|
164
|
+
|
|
165
|
+
6. **Poor image alt text** - Missing or generic "product image". Use descriptive alt text for image search traffic.
|
|
166
|
+
|
|
167
|
+
## SEO Checklist
|
|
168
|
+
|
|
169
|
+
### On Every Product Page
|
|
170
|
+
|
|
171
|
+
- [ ] Unique, descriptive title tag (50-60 characters)
|
|
172
|
+
- [ ] Unique, compelling meta description (150-160 characters)
|
|
173
|
+
- [ ] Open Graph and Twitter Card tags
|
|
174
|
+
- [ ] Product schema with price, availability, rating (if reviews exist)
|
|
175
|
+
- [ ] Breadcrumb schema (category hierarchy)
|
|
176
|
+
- [ ] Descriptive alt text on all images
|
|
177
|
+
- [ ] Canonical URL (especially for variants)
|
|
178
|
+
- [ ] Fast load time (LCP < 2.5s)
|
|
179
|
+
- [ ] Mobile-responsive design
|
|
180
|
+
- [ ] Internal links to related products/categories
|
|
181
|
+
- [ ] Detailed product description (200+ words ideal)
|
|
182
|
+
|
|
183
|
+
### Site-wide
|
|
184
|
+
|
|
185
|
+
- [ ] Dynamic XML sitemap submitted to search engines
|
|
186
|
+
- [ ] Robots.txt properly configured
|
|
187
|
+
- [ ] SSL certificate (HTTPS)
|
|
188
|
+
- [ ] Mobile-friendly design (44px touch targets)
|
|
189
|
+
- [ ] Organization schema on homepage
|
|
190
|
+
- [ ] Proper heading hierarchy (H1 for product title)
|
|
191
|
+
- [ ] Pagination implemented with rel="prev/next"
|
|
192
|
+
- [ ] Canonical URLs for filtered/sorted pages
|
|
193
|
+
- [ ] 404 pages with helpful navigation
|
|
194
|
+
- [ ] Image optimization (WebP, lazy loading)
|
|
195
|
+
- [ ] Core Web Vitals within targets (LCP < 2.5s, CLS < 0.1, INP < 200ms)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated by Thor Commerce sf-scaffold (template: templates/app/app/[[...slug]]/page.tsx)
|
|
2
|
+
|
|
3
|
+
import { notFound } from 'next/navigation'
|
|
4
|
+
import { __STOREFRONT_CAMEL__App } from '@/lib/__STOREFRONT__/runtime'
|
|
5
|
+
import { StorefrontRenderer } from './renderer'
|
|
6
|
+
|
|
7
|
+
export const { generateMetadata, generateStaticParams } = __STOREFRONT_CAMEL__App
|
|
8
|
+
|
|
9
|
+
export default async function Page({
|
|
10
|
+
params,
|
|
11
|
+
}: {
|
|
12
|
+
params: Promise<{ slug?: string[] }>
|
|
13
|
+
}) {
|
|
14
|
+
const data = await __STOREFRONT_CAMEL__App.getPageData({ params })
|
|
15
|
+
if (!data) return notFound()
|
|
16
|
+
return <StorefrontRenderer {...data} />
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Generated by Thor Commerce sf-scaffold (template: templates/app/app/[[...slug]]/renderer.tsx)
|
|
4
|
+
|
|
5
|
+
import { PageRenderer } from '@json-render/next'
|
|
6
|
+
import type { PageData } from '@json-render/next'
|
|
7
|
+
|
|
8
|
+
export function StorefrontRenderer(data: PageData) {
|
|
9
|
+
return <PageRenderer {...data} />
|
|
10
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "@thorprovider/blocks/styles.css";
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--color-card: var(--card);
|
|
11
|
+
--color-card-foreground: var(--card-foreground);
|
|
12
|
+
--color-popover: var(--popover);
|
|
13
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
14
|
+
--color-primary: var(--primary);
|
|
15
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
16
|
+
--color-secondary: var(--secondary);
|
|
17
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
18
|
+
--color-muted: var(--muted);
|
|
19
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
20
|
+
--color-accent: var(--accent);
|
|
21
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
22
|
+
--color-destructive: var(--destructive);
|
|
23
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
24
|
+
--color-success: var(--success);
|
|
25
|
+
--color-success-foreground: var(--success-foreground);
|
|
26
|
+
--color-warning: var(--warning);
|
|
27
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
28
|
+
--color-border: var(--border);
|
|
29
|
+
--color-input: var(--input);
|
|
30
|
+
--color-ring: var(--ring);
|
|
31
|
+
--radius-sm: var(--radius-sm);
|
|
32
|
+
--radius-md: var(--radius-md);
|
|
33
|
+
--radius-lg: var(--radius-lg);
|
|
34
|
+
--radius-xl: var(--radius-xl);
|
|
35
|
+
--font-sans: var(--font-geist-sans);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:root {
|
|
39
|
+
--background: oklch(0.98 0.002 90);
|
|
40
|
+
--foreground: oklch(0.15 0 0);
|
|
41
|
+
--card: oklch(1 0 0);
|
|
42
|
+
--card-foreground: oklch(0.15 0 0);
|
|
43
|
+
--popover: oklch(1 0 0);
|
|
44
|
+
--popover-foreground: oklch(0.15 0 0);
|
|
45
|
+
--primary: oklch(0.15 0 0);
|
|
46
|
+
--primary-foreground: oklch(0.98 0.002 90);
|
|
47
|
+
--secondary: oklch(0.96 0.003 90);
|
|
48
|
+
--secondary-foreground: oklch(0.15 0 0);
|
|
49
|
+
--muted: oklch(0.94 0.003 90);
|
|
50
|
+
--muted-foreground: oklch(0.45 0 0);
|
|
51
|
+
--accent: oklch(0.58 0.16 25);
|
|
52
|
+
--accent-foreground: oklch(1 0 0);
|
|
53
|
+
--destructive: oklch(0.55 0.2 25);
|
|
54
|
+
--destructive-foreground: oklch(0.98 0.002 90);
|
|
55
|
+
--success: oklch(0.47 0.14 155);
|
|
56
|
+
--success-foreground: oklch(0.98 0.01 155);
|
|
57
|
+
--warning: oklch(0.55 0.18 75);
|
|
58
|
+
--warning-foreground: oklch(0.98 0.02 75);
|
|
59
|
+
--border: oklch(0.88 0.003 90);
|
|
60
|
+
--input: oklch(0.92 0.003 90);
|
|
61
|
+
--ring: oklch(0.15 0 0);
|
|
62
|
+
--radius-sm: 0.25rem;
|
|
63
|
+
--radius-md: 0.375rem;
|
|
64
|
+
--radius-lg: 0.5rem;
|
|
65
|
+
--radius-xl: 0.75rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.dark {
|
|
69
|
+
--background: oklch(0.15 0 0);
|
|
70
|
+
--foreground: oklch(0.98 0.002 90);
|
|
71
|
+
--card: oklch(0.2 0 0);
|
|
72
|
+
--card-foreground: oklch(0.98 0.002 90);
|
|
73
|
+
--popover: oklch(0.2 0 0);
|
|
74
|
+
--popover-foreground: oklch(0.98 0.002 90);
|
|
75
|
+
--primary: oklch(0.98 0.002 90);
|
|
76
|
+
--primary-foreground: oklch(0.15 0 0);
|
|
77
|
+
--secondary: oklch(0.25 0.003 90);
|
|
78
|
+
--secondary-foreground: oklch(0.98 0.002 90);
|
|
79
|
+
--muted: oklch(0.25 0.003 90);
|
|
80
|
+
--muted-foreground: oklch(0.65 0 0);
|
|
81
|
+
--accent: oklch(0.65 0.15 25);
|
|
82
|
+
--accent-foreground: oklch(0.15 0 0);
|
|
83
|
+
--destructive: oklch(0.65 0.2 25);
|
|
84
|
+
--destructive-foreground: oklch(0.15 0 0);
|
|
85
|
+
--success: oklch(0.6 0.13 155);
|
|
86
|
+
--success-foreground: oklch(0.1 0.02 155);
|
|
87
|
+
--warning: oklch(0.7 0.15 75);
|
|
88
|
+
--warning-foreground: oklch(0.1 0.02 75);
|
|
89
|
+
--border: oklch(0.3 0.003 90);
|
|
90
|
+
--input: oklch(0.3 0.003 90);
|
|
91
|
+
--ring: oklch(0.85 0 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@layer base {
|
|
95
|
+
* {
|
|
96
|
+
@apply border-border;
|
|
97
|
+
}
|
|
98
|
+
body {
|
|
99
|
+
@apply bg-background text-foreground;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Generated by Thor Commerce sf-scaffold (template: templates/app/app/layout.tsx)
|
|
2
|
+
|
|
3
|
+
import type { Metadata } from 'next'
|
|
4
|
+
import { Geist } from 'next/font/google'
|
|
5
|
+
import { StorefrontProvider } from '@thorprovider/storefronts/host-glue/providers'
|
|
6
|
+
import { themeVarsToCss, themeTokensToVars } from '@thorprovider/storefronts/render-core/theme'
|
|
7
|
+
import { DEFAULT_THEME_TOKENS } from '@thorprovider/storefronts/contracts'
|
|
8
|
+
import { registry } from '@/lib/__STOREFRONT__/registry'
|
|
9
|
+
import './globals.css'
|
|
10
|
+
|
|
11
|
+
const geist = Geist({
|
|
12
|
+
variable: '--font-geist-sans',
|
|
13
|
+
subsets: ['latin'],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const themeCss = themeVarsToCss(themeTokensToVars(DEFAULT_THEME_TOKENS))
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
title: '__SITE_NAME__',
|
|
20
|
+
description: 'Tienda online de __SITE_NAME__.',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({
|
|
24
|
+
children,
|
|
25
|
+
}: Readonly<{ children: React.ReactNode }>) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang="es" className={geist.variable}>
|
|
28
|
+
<body className="font-sans antialiased">
|
|
29
|
+
<StorefrontProvider themeCss={themeCss} registry={registry}>
|
|
30
|
+
{children}
|
|
31
|
+
</StorefrontProvider>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Generated by Thor Commerce sf-scaffold (template: templates/app/lib/__STOREFRONT__/catalog.ts)
|
|
2
|
+
// json-render catalog for the base storefront. Recipe-added views extend this file.
|
|
3
|
+
// UI contracts come exclusively from @thorprovider/blocks.
|
|
4
|
+
|
|
5
|
+
import { defineCatalog } from '@json-render/core'
|
|
6
|
+
import { schema } from '@json-render/react/schema'
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { cardProductSchema } from '@thorprovider/blocks'
|
|
9
|
+
|
|
10
|
+
const ctaSchema = z.object({
|
|
11
|
+
label: z.string(),
|
|
12
|
+
href: z.string().optional(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const navLinkSchema = z.object({
|
|
16
|
+
label: z.string(),
|
|
17
|
+
href: z.string().optional(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const footerLinkSchema = z.object({
|
|
21
|
+
label: z.string(),
|
|
22
|
+
href: z.string().optional(),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const footerLinkColumnSchema = z.object({
|
|
26
|
+
title: z.string(),
|
|
27
|
+
links: z.array(footerLinkSchema),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const featuredProductSchema = z.object({
|
|
31
|
+
id: z.union([z.string(), z.number()]),
|
|
32
|
+
name: z.string(),
|
|
33
|
+
price: z.number(),
|
|
34
|
+
image: z.string(),
|
|
35
|
+
href: z.string().optional(),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const categoryCardSchema = z.object({
|
|
39
|
+
id: z.union([z.string(), z.number()]),
|
|
40
|
+
title: z.string(),
|
|
41
|
+
image: z.string(),
|
|
42
|
+
href: z.string().optional(),
|
|
43
|
+
count: z.number().optional(),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const featureItemSchema = z.object({
|
|
47
|
+
id: z.union([z.string(), z.number()]),
|
|
48
|
+
title: z.string(),
|
|
49
|
+
description: z.string(),
|
|
50
|
+
icon: z.string().optional(),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const productSchema = cardProductSchema
|
|
54
|
+
|
|
55
|
+
export const catalog = defineCatalog(schema, {
|
|
56
|
+
components: {
|
|
57
|
+
Layout: {
|
|
58
|
+
props: z.object({}),
|
|
59
|
+
slots: ['default', 'header', 'footer'],
|
|
60
|
+
description: 'Shared store layout with header and footer regions',
|
|
61
|
+
},
|
|
62
|
+
Navbar: {
|
|
63
|
+
props: z.object({
|
|
64
|
+
navLinks: z.array(navLinkSchema),
|
|
65
|
+
logoText: z.string(),
|
|
66
|
+
logoHref: z.string().optional(),
|
|
67
|
+
currentPath: z.string().optional(),
|
|
68
|
+
signIn: ctaSchema.optional(),
|
|
69
|
+
cta: ctaSchema.optional(),
|
|
70
|
+
cartCount: z.number().optional(),
|
|
71
|
+
}),
|
|
72
|
+
description: 'Store navigation bar with links and auth CTA',
|
|
73
|
+
},
|
|
74
|
+
StorefrontHero: {
|
|
75
|
+
props: z.object({
|
|
76
|
+
title: z.string(),
|
|
77
|
+
description: z.string(),
|
|
78
|
+
products: z.array(featuredProductSchema),
|
|
79
|
+
titleAccent: z.string().optional(),
|
|
80
|
+
badge: z.string().optional(),
|
|
81
|
+
primaryCta: ctaSchema.optional(),
|
|
82
|
+
secondaryCta: ctaSchema.optional(),
|
|
83
|
+
}),
|
|
84
|
+
description: 'E-commerce storefront hero with featured products',
|
|
85
|
+
},
|
|
86
|
+
Feature: {
|
|
87
|
+
props: z.object({
|
|
88
|
+
items: z.array(featureItemSchema),
|
|
89
|
+
badge: z.string().optional(),
|
|
90
|
+
title: z.string().optional(),
|
|
91
|
+
description: z.string().optional(),
|
|
92
|
+
learnMoreLabel: z.string().optional(),
|
|
93
|
+
ctaLabel: z.string().optional(),
|
|
94
|
+
}),
|
|
95
|
+
description: 'Feature benefits grid',
|
|
96
|
+
},
|
|
97
|
+
CategoryCards: {
|
|
98
|
+
props: z.object({
|
|
99
|
+
title: z.string().optional(),
|
|
100
|
+
description: z.string().optional(),
|
|
101
|
+
categories: z.array(categoryCardSchema),
|
|
102
|
+
cta: ctaSchema.optional(),
|
|
103
|
+
}),
|
|
104
|
+
description: 'Category grid with image overlay',
|
|
105
|
+
},
|
|
106
|
+
ProductCard: {
|
|
107
|
+
props: z.object({
|
|
108
|
+
products: z.array(productSchema),
|
|
109
|
+
heading: z.string().optional(),
|
|
110
|
+
maxItems: z.number().optional(),
|
|
111
|
+
}),
|
|
112
|
+
description: 'Product grid',
|
|
113
|
+
},
|
|
114
|
+
Footer: {
|
|
115
|
+
props: z.object({
|
|
116
|
+
brandName: z.string(),
|
|
117
|
+
brandDescription: z.string().optional(),
|
|
118
|
+
linkColumns: z.array(footerLinkColumnSchema),
|
|
119
|
+
newsletter: z
|
|
120
|
+
.object({
|
|
121
|
+
placeholder: z.string().optional(),
|
|
122
|
+
buttonLabel: z.string(),
|
|
123
|
+
})
|
|
124
|
+
.optional(),
|
|
125
|
+
}),
|
|
126
|
+
description: 'Store footer with link columns and newsletter',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
actions: {},
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
export type StorefrontCatalog = typeof catalog
|