Loading...
Loading...
Fashion e-commerce is 80% visual and 20% transactional. Here's how we built search and recommendations for a platform where style is the primary query language.
Field Note — ThriftbyZee
---
Fashion e-commerce is not like selling books or electronics. The product is subjective. A "boho summer dress" means different things to different people. The same item might be described as "vintage," "retro," "y2k," or "thrift" depending on who's listing it. Standard search — exact match on title and description — fails completely in this domain.
ThriftbyZee was built to solve this: a curated thrift and vintage fashion platform where search understands style, recommendations improve with limited data, and image quality matters more than feature count.
---
Traditional e-commerce search is deterministic: user queries "iPhone 15 Pro 256GB," system finds exact match. Fashion search is probabilistic: user queries "boho summer dress," system finds items that might match based on description, tags, image features, and purchase history.
We built a hybrid search system that combines three signals:
1. Full-text search on PostgreSQL: Fast, exact-ish matching on title, description, and tags. Handles "red dress" and "vintage jacket" well.
2. Vector search on embeddings: We generate text embeddings for every listing using a lightweight sentence transformer. Queries are embedded and matched via cosine similarity. Handles "boho" and "y2k" — terms that might not appear literally in the description but are semantically close.
3. Collaborative filtering: As users browse and purchase, we build a user-item interaction matrix and recommend items that similar users liked.
The search pipeline works like this:
User Query → Parse → Full-Text Search (PostgreSQL) → Vector Search (Embeddings) → Merge & Rank → Collaborative Filter Boost → ResultsEach layer contributes a score. Full-text contributes precision ("I said red, give me red"). Vector search contributes recall ("I said boho, give me things that feel boho even if the word isn't there"). Collaborative filtering contributes personalization ("people like you bought this").
PostgreSQL's built-in full-text search is surprisingly capable for moderate-scale e-commerce. We use tsvector and tsquery with custom dictionaries:
CREATE INDEX idx_listing_search ON listings
USING GIN (to_tsvector('english', title || ' ' || description || ' ' || tags));Queries use ts_rank_cd for relevance scoring:
SELECT *, ts_rank_cd(search_vector, query) AS rank
FROM listings
WHERE search_vector @@ to_tsquery('english', 'boho & summer & dress')
ORDER BY rank DESC;The tradeoff is that tsvector indexes are large and slow to update. We update them asynchronously via Celery when a listing changes, not synchronously in the request path.
We generate embeddings using sentence-transformers/all-MiniLM-L6-v2 — small enough to run on CPU, good enough for fashion semantics. Every listing's title, description, and tags are concatenated and embedded at creation time. The embedding is stored in a separate ListingEmbedding table and updated via a Celery task.
Vector similarity search is done in Python, not PostgreSQL (we evaluated pgvector but it wasn't stable enough at the time). For a catalog of ~10,000 items, brute-force cosine similarity is fast enough (<50ms). If the catalog grows beyond 100,000, we'll migrate to pgvector or a dedicated vector database.
Cold start is the hardest problem in recommendations. A new user has no purchase history. A new item has no interaction data. We handle this with a hybrid strategy:
The recommendation quality improves dramatically with more data. After 50 interactions, the model is usable. After 500, it's genuinely good. The challenge is getting users to their 50th interaction before they churn.
---
Fashion is visual. A listing with blurry photos doesn't sell. But high-resolution fashion photography is large — 5MB+ per image. Serving these unoptimized would destroy page load times.
We built a custom image pipeline:
1. Upload: Sellers upload original images to AWS S3.
2. Processing: A Celery task generates responsive variants: thumbnail (200px), medium (800px), large (1600px), and WebP versions of each.
3. Delivery: Next.js Image component serves the appropriate variant based on device pixel density and viewport size. Blur placeholders are generated from low-quality image previews for perceived performance.
We evaluated Cloudinary and Imgix but chose a custom pipeline for cost control at launch. The tradeoff is operational overhead — we maintain the image workers, monitor disk usage, and handle format support. For a small catalog, this is manageable. For a large catalog, a managed CDN is the better choice.
The seller dashboard is where ThriftbyZee differentiates from generic platforms. Sellers need to see:
We built these analytics from the event stream: every view, click, inquiry, and purchase is logged to a ListingEvent table. The dashboard queries aggregated views of this table, updated hourly via Celery.
---
Collaborative filtering needs data. At launch, we had none. We solved this by:
1. Seeding with manual curation: The founding team curated 200 "starter" listings with rich tags and descriptions. These became the seed for initial recommendations.
2. Incentivizing interaction: We added "style quizzes" — users answer 5 questions about their preferences and get instant recommendations. This generates interaction data without requiring a purchase.
3. Cross-category exploration: We intentionally show users items outside their usual categories to diversify the interaction matrix. A user who only browses dresses might see a vintage jacket recommendation. If they click, the matrix learns.
Fashion images are large and numerous. A single listing might have 8 images. A search results page shows 20 listings. That's 160 images. Loading them all at full resolution would be catastrophic.
Our solution:
---
Fashion e-commerce is heavily visual — image quality matters more than features. A platform with perfect search but blurry photos fails. A platform with decent search and stunning photos wins. We spent 30% of our engineering time on image optimization and it was the highest-ROI work we did.
Seller onboarding is the biggest growth bottleneck. You can have the best search and recommendations in the world, but if sellers can't easily list their items, you have no inventory. We simplified listing creation to 4 fields and photo upload. Every additional field — size chart, material details, shipping dimensions — dropped completion rate by 8%.
Recommendation quality improves dramatically with more interaction data. The first 100 user interactions teach you almost nothing. The first 1,000 teach you something. The first 10,000 teach you patterns. Don't expect good recommendations at launch. Design for the long tail of data accumulation.
---
ThriftbyZee is live at [thriftbyzee.com](https://thriftbyzee.com)