Skip to content

Commit cdcd9b0

Browse files
committed
Add more docs
1 parent fc1882d commit cdcd9b0

File tree

3 files changed

+838
-14
lines changed

3 files changed

+838
-14
lines changed
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
# Deco.cx Error Handling & Security Patterns
2+
3+
**Version:** 1.0
4+
**Date:** September 2025
5+
**Target Audience:** AI Coding Agents & Developers
6+
**Parent Guide:** [deco-cx-store-migration-plan.md](./deco-cx-store-migration-plan.md)
7+
8+
This document contains real-world error handling patterns and security fixes derived from analyzing recent commits in production deco.cx stores.
9+
10+
## 🛡️ Security & Input Sanitization Patterns
11+
12+
### 1. Search Parameter Sanitization
13+
14+
**Real Issue Found:** VTEX search API was receiving malformed query strings and embedded data URIs causing 500 errors.
15+
16+
**Stack Trace Example:**
17+
```
18+
SearchResult: failed to load productListingPage err=Request failed with status 500
19+
url=https://example-store.deco.site/search?q=product+data:text/html,<script>...
20+
facets= sort= count=24 query=product+data:text/html,<script>... page=0 fuzzy=enabled hideUnavailableItems=true
21+
```
22+
23+
**Solution Pattern:**
24+
```typescript
25+
// components/search/InfiniteScroll.tsx & SearchResult.tsx
26+
const sanitizeSegment = (segment: string) => {
27+
const s = segment.trim()
28+
if (!s) return ''
29+
const lower = s.toLowerCase()
30+
// Block data URIs and javascript URLs
31+
if (lower.includes('data:') || lower.includes('javascript:')) return ''
32+
if (lower.includes('data%3a') || lower.includes('javascript%3a')) return ''
33+
if (s.length > 200) return ''
34+
return s
35+
}
36+
37+
// Apply to URL path segments
38+
const sanitizedSegments = currentUrl.pathname
39+
.split('/')
40+
.map(sanitizeSegment)
41+
.filter((item) => item !== '')
42+
43+
// Also sanitize query parameters
44+
const query = searchTerm.replace(/\+/g, ' ').trim(); // Convert + to spaces
45+
```
46+
47+
**Key Protections:**
48+
- Block `data:` and `javascript:` schemes (both raw and URL-encoded)
49+
- Limit segment length to prevent oversized payloads
50+
- Proper URL encoding for search queries
51+
52+
### 2. Component Safety Guards
53+
54+
**Real Issue Found:** `BannerWithTitle` component crashing with "Cannot read properties of undefined (startsWith)"
55+
56+
**Stack Trace Pattern:**
57+
```
58+
TypeError: Cannot read properties of undefined (reading 'startsWith')
59+
at BannerWithTitle.tsx:45:23
60+
at renderToString (deno:ext/node/polyfills/_utils.ts:22:22)
61+
```
62+
63+
**Solution Pattern:**
64+
```typescript
65+
// components/ui/BannerWithTitle.tsx
66+
export default function BannerWithTitle({
67+
mobile,
68+
desktop,
69+
title
70+
}: Props) {
71+
// Guard against undefined image sources
72+
const mobileSrc = mobile?.src
73+
const desktopSrc = desktop?.src
74+
75+
// Provide safe fallback
76+
const fallbackSrc = mobileSrc || desktopSrc || '/static/placeholder.jpg'
77+
78+
return (
79+
<Picture preload>
80+
{mobileSrc && (
81+
<Source
82+
media="(max-width: 767px)"
83+
src={mobileSrc}
84+
width={mobile.width}
85+
height={mobile.height}
86+
/>
87+
)}
88+
{desktopSrc && (
89+
<Source
90+
media="(min-width: 768px)"
91+
src={desktopSrc}
92+
width={desktop.width}
93+
height={desktop.height}
94+
/>
95+
)}
96+
<img
97+
class="object-cover w-full h-full"
98+
loading="lazy"
99+
src={fallbackSrc}
100+
alt={title || "Banner"}
101+
/>
102+
</Picture>
103+
)
104+
}
105+
```
106+
107+
## 🔄 Error Handling & Logging Patterns
108+
109+
### 1. Structured Error Logging for External APIs
110+
111+
**Real Implementation from production store:**
112+
```typescript
113+
// components/search/InfiniteScroll.tsx
114+
try {
115+
const page = await invoke.vtex.loaders.intelligentSearch.productListingPage({
116+
selectedFacets: newSelectedFacets,
117+
sort: sort as SortType,
118+
count: pageInfo.recordPerPage,
119+
query,
120+
page: pageInfo.currentPage - offset,
121+
fuzzy: fuzzy ?? 'disabled',
122+
hideUnavailableItems: true,
123+
})
124+
nextPage.value = page
125+
} catch (error) {
126+
const facetsStr = (newSelectedFacets ?? [])
127+
.map((f) => `${f.key}:${f.value}`)
128+
.join('|')
129+
130+
// Clear the next page to prevent infinite loading
131+
nextPage.value = null
132+
133+
// Create structured error for monitoring
134+
const err = new Error(
135+
`InfiniteScroll: failed to fetch next page err=${
136+
(error as Error)?.message ?? 'unknown'
137+
} facets=${facetsStr} sort=${sort ?? ''} count=${pageInfo.recordPerPage} query=${
138+
query ?? ''
139+
} page=${pageInfo.currentPage - offset} fuzzy=${
140+
fuzzy ?? 'disabled'
141+
} hideUnavailableItems=true`,
142+
)
143+
144+
// Re-throw so the framework/section error boundary can capture and log it
145+
throw err
146+
}
147+
```
148+
149+
**Key Pattern Elements:**
150+
1. **Structured error messages** with all relevant parameters
151+
2. **State cleanup** before throwing (clear loading states)
152+
3. **Re-throw pattern** to let framework error boundaries handle logging
153+
4. **Consistent error format** across similar components
154+
155+
### 2. Search Result Error Handling
156+
157+
**Pattern for Search/PLP Components:**
158+
```typescript
159+
// components/search/SearchResult.tsx
160+
try {
161+
page = await ctx.invoke('vtex/loaders/intelligentSearch/productListingPage.ts', {
162+
selectedFacets: newSelectedFacets,
163+
sort: selectedSort as string,
164+
count: recordPerPage,
165+
query,
166+
page: currentPage,
167+
fuzzy: 'enabled',
168+
hideUnavailableItems: true,
169+
})
170+
} catch (error) {
171+
const facetsStr = (newSelectedFacets ?? [])
172+
.map((f) => `${f.key}:${f.value}`)
173+
.join('|')
174+
175+
throw new Error(
176+
`SearchResult: failed to load productListingPage err=${
177+
(error as Error)?.message ?? 'unknown'
178+
} url=${req.url} facets=${facetsStr} sort=${
179+
selectedSort ?? ''
180+
} count=${recordPerPage} query=${
181+
query ?? ''
182+
} page=${currentPage} fuzzy=enabled hideUnavailableItems=true`,
183+
)
184+
}
185+
```
186+
187+
### 3. UI State Management During Errors
188+
189+
**Real Implementation Pattern:**
190+
```typescript
191+
// components/search/InfiniteScroll.tsx - Button click handler
192+
onClick={async (event) => {
193+
const target = event.currentTarget as HTMLElement
194+
target.setAttribute('data-loading', '')
195+
196+
try {
197+
await fetchNextPage()
198+
// Only update UI state if successful
199+
if (typeof konfidencyLoader !== 'undefined') {
200+
konfidencyLoader.loadShowcase()
201+
}
202+
} finally {
203+
// Always clean up loading state, even on error
204+
target.removeAttribute('data-loading')
205+
}
206+
}}
207+
```
208+
209+
## 🎨 Image Optimization & Loading Patterns
210+
211+
### 1. Missing Height Warnings Fix
212+
213+
**Real Issue:** "Missing height. This image will NOT be optimized" logs in brand carousels
214+
215+
**Solution Pattern:**
216+
```typescript
217+
// components/ui/InfiniteSlider.tsx
218+
{brands?.map((brand, index) => (
219+
<div key={index} class="min-w-[120px]">
220+
<img
221+
src={brand.src}
222+
alt={brand.alt}
223+
width={120}
224+
height={120} // ✅ Explicit height prevents optimization warnings
225+
class="object-contain"
226+
loading="lazy"
227+
/>
228+
</div>
229+
))}
230+
```
231+
232+
**Critical Fix Points:**
233+
- Brand carousel images need explicit square dimensions
234+
- Product images should maintain aspect ratios with explicit dimensions
235+
- Use `object-contain` for logos, `object-cover` for banners
236+
237+
### 2. Safe Image Component Pattern
238+
239+
**Template for Image Components:**
240+
```typescript
241+
interface ImageProps {
242+
src?: string
243+
alt?: string
244+
width?: number
245+
height?: number
246+
fallback?: string
247+
}
248+
249+
export function SafeImage({ src, alt, width, height, fallback = '/static/placeholder.jpg' }: ImageProps) {
250+
const safeSrc = src || fallback
251+
252+
return (
253+
<img
254+
src={safeSrc}
255+
alt={alt || "Image"}
256+
width={width || 300}
257+
height={height || 200}
258+
loading="lazy"
259+
onError={(e) => {
260+
const target = e.target as HTMLImageElement
261+
if (target.src !== fallback) {
262+
target.src = fallback
263+
}
264+
}}
265+
/>
266+
)
267+
}
268+
```
269+
270+
## 🚀 Performance & Caching Patterns
271+
272+
### 1. SWR Caching for Heavy Middleware
273+
274+
**Real Implementation:**
275+
```typescript
276+
// sections/SEO/SeoPLPV2Middleware.tsx
277+
export const cache = 'stale-while-revalidate'
278+
279+
export default function SeoPLPV2Middleware(props: Props) {
280+
// Heavy SEO computation here...
281+
return <SEOComponent {...seoData} />
282+
}
283+
```
284+
285+
**Benefits:**
286+
- Reduces server load on category pages
287+
- Consistent SEO data across requests
288+
- Better performance for repeat visitors
289+
290+
### 2. Duplicate Loader Elimination
291+
292+
**Real Issue:** Category pages were loading product lists twice - once in breadcrumbs, once in main content
293+
294+
**Solution:**
295+
- Remove redundant `"Lista de Produtos - 20 Itens"` from breadcrumb sections
296+
- Use shared loaders instead of duplicate API calls
297+
- Consolidate data fetching at page level
298+
299+
## 🔧 Build System & Dependency Patterns
300+
301+
### 1. PostCSS Peer Dependency Resolution
302+
303+
**Real Issue Found:**
304+
```
305+
npm WARN cssnano@6.1.2 requires a peer of postcss@^8.4.31 but postcss@8.4.27 was installed
306+
```
307+
308+
**Solution Pattern:**
309+
```json
310+
// deno.json
311+
{
312+
"imports": {
313+
"postcss": "npm:postcss@8.4.38",
314+
"postcss@8.4.27": "npm:postcss@8.4.38"
315+
}
316+
}
317+
```
318+
319+
**Key Points:**
320+
- Pin PostCSS to satisfy cssnano peer requirements
321+
- Use alias mapping to redirect old versions
322+
- Test build after dependency changes
323+
324+
## 📊 Testing & Validation Checklist
325+
326+
### Error Handling Validation
327+
- [ ] Search with malformed queries (e.g., `?q=test+data:text/html`)
328+
- [ ] Components render with undefined/null image sources
329+
- [ ] API failures don't crash the entire page
330+
- [ ] Loading states are properly cleaned up on errors
331+
- [ ] Error messages include structured context for debugging
332+
333+
### Performance Validation
334+
- [ ] No "Missing height" warnings in browser console
335+
- [ ] SWR cache headers present on middleware responses
336+
- [ ] No duplicate API calls on category pages
337+
- [ ] Image optimization working properly
338+
339+
### Security Validation
340+
- [ ] Search parameters are properly sanitized
341+
- [ ] URL segments reject data URIs and javascript schemes
342+
- [ ] No XSS vectors through search or filter parameters
343+
- [ ] Error messages don't leak sensitive information
344+
345+
---
346+
347+
**Related Documents:**
348+
- [deco-cx-store-migration-plan.md](./deco-cx-store-migration-plan.md) - Main migration guide
349+
- [deco-cx-performance-optimizations.md](./deco-cx-performance-optimizations.md) - Performance patterns
350+
351+
**Last Updated:** September 2025

0 commit comments

Comments
 (0)