
Shift
A UK-focused second-hand marketplace for bikes and components, built as a full-stack Next.js MVP.
Client
Personal project
Role
Sole developer
Year
2025–2026
The Brief
Shift is a personal side project — a second-hand marketplace for bikes and components aimed at the UK cycling community. The goal was to build a focused alternative to the general-purpose classifieds sites (eBay, Facebook Marketplace) that cyclists currently rely on, with a cleaner UX and domain-specific features like a bike valuation tool. I built it as an MVP to validate the concept and keep it shippable, prioritising clarity over abstraction at every step.
My Role
I designed and built the entire application myself, from the database schema through to the UI. There were no existing designs — I made all product and architecture decisions independently. This was a solo project with no agency or client context.
Technical Approach
I chose Next.js 16 (App Router) as the foundation because server components let me keep most data-fetching logic close to the database without client-side waterfalls, which matters for a marketplace where listing pages need to be fast and crawlable. PostgreSQL via Prisma handles all persistent data, with raw SQL reserved for the one area where Prisma falls short: search. The search layer combines PostgreSQL full-text search on listing title and description with pg_trgm trigram similarity on title, brand, and model — this gives users useful fuzzy results for things like misspelled brand names without needing an external search service.
Auth0 handles authentication via Universal Login, which offloads the credential and session complexity I didn't want to own in an MVP. On sign-in, a local User record is provisioned via getOrCreateUserFromSession(), keeping Auth0's identity separate from application data. All writes validate user identity server-side from the session — the client never passes a user ID.
Images are uploaded to local filesystem storage via a StorageService abstraction backed by sharp, which resizes and converts to WebP on ingest. The abstraction is intentionally thin: the clear path to S3 is there, but the local implementation is good enough for now.
I used Zod 4 at all API boundaries for runtime validation and kept a strict rule throughout: no Prisma imports in UI components. All database access goes through API routes or server-side service functions. This boundary kept the codebase navigable as it grew.
Key Features
- Full-text + fuzzy search — PostgreSQL
tsvectorfull-text search combined withpg_trgmtrigram similarity, with boosted listings ranked first, implemented in raw SQL encapsulated inlib/services/search/ - Listing management — Create, edit, and delete listings with multi-image upload, processed through
sharp(resize to 2000px max, WebP conversion) - Messaging system — Buyer/seller threads keyed on
(listingId, buyerId, sellerId)to prevent duplicates; messages are append-only with read tracking, server-side rate limiting, and link blocking - Bike valuation tool —
/valuepage providing estimated resale values to help sellers price accurately - Listing reports — Users can flag suspicious or inappropriate listings with an optional message; admin review queue handled server-side
- Auth0 authentication — Universal Login with automatic local user provisioning on first sign-in
- Boosted listings — Paid promotion system that ranks boosted listings above organic results in search
Tech Stack
Next.js · React · TypeScript · PostgreSQL · Prisma · Auth0 · TailwindCSS · TanStack Query · Zod · sharp · Vitest