Currently taking new clients · Get in touch today
Case study · E-commerce platform


A bookshop where a second-hand paperback and a brand-new hardback sit in the same catalogue, the same search and the same basket. That sounds unremarkable until you try to model it — and it is the reason this was built rather than bought.

Catalogue
5,475 titles
Built with
Next.js 15 · Payload CMS 3 · PostgreSQL
Runs on
Vercel · Neon · Cloudflare R2
Status
Live · payments in progress
The problem

Two kinds of book, one shop.

Most shop platforms assume every product is the same shape. Books are not, and the differences are exactly the things a buyer cares about.

A used book has a condition

“Good” and “Like new” are the difference between a sale and a return. That has to be a first-class field with a grade, not a note in the description.

A new book does not

No grade — just stock and a price. Forcing both through one generic product type means every page either hides a condition it needs or shows one that does not apply.

They share one basket

New and used copies land in the same basket with one delivery charge, and the free-delivery threshold has to count them all. That logic lives in one place rather than two.

See it

The shop as it runs.

Every figure below is real: the title count, the genre counts, the reading-age bands. They come from the live catalogue.

BookCartSearch books, authors, ISBN…All books · New · Used · About
New ArrivalsBest SellersBy PriceKidsFictionNon-FictionFormats
Spend £27.01 more for free UK delivery
NEW · USEDYour corner for every book, every format.

Brand-new releases and second-hand bargains — one shop, one basket.

Browse all booksShop used

One shop, three conditions

New and second-hand sit in the same catalogue and the same basket — which sounds obvious until you try to model it. A used copy needs a condition grade, and a new book does not. The delivery-threshold bar is live, not decorative: it reads the basket and tells you what is left to spend.

These are drawn reconstructions of the running storefront rather than screenshots — faithful to the live design, but redrawn. No customer, order or account data appears here or anywhere on this site.

How it's built

The decisions worth defending.

Each of these was written down with its alternatives at the time, which is why they can be explained now rather than reconstructed.

One application, not two
Payload CMS 3 is mounted inside the Next.js app at /admin rather than run as a separate service. Editors get pages, posts, media, drafts and versions; developers get REST, GraphQL and a local API generated from the same schema, with TypeScript types regenerated from the database. One deploy, one thing to secure, one place where the types are true.
A catalogue that admits books differ
The Books collection carries condition and condition grade, stock, SKU and ISBN — so a second-hand paperback and a new hardback are the same entity described honestly rather than two product types bolted together. Authors, publishers and categories are relations, which is what makes an author page and a genre page possible without duplicating anything.
Faceting that queries, rather than filters
Condition, format, genre and reading age each run against the database with real counts, so 'Kids · KS2 (7-11) — 603' is a fact rather than a label. A client-side filter over the current page of results is the usual shortcut and it silently under-counts; that mistake has been made on other projects and was not repeated here.
SEO designed in, not retrofitted
A dynamic sitemap, robots rules, hand-written JSON-LD builders and Payload's SEO and redirects plugins were part of the first build rather than a later pass. For a shop whose acquisition plan is organic search, retrofitting structured data onto an existing storefront is the expensive way round.
Covers sourced automatically
A script pulls cover artwork by ISBN from Open Library and generates the social cards, because a 5,000-title catalogue cannot be illustrated by hand and a grid of grey placeholders does not sell books.
Continuous integration with a real database
Every push runs a type check and a full production build against a live Postgres service in CI, so a schema change that compiles locally but breaks the build is caught before it reaches the deploy rather than after.
Straight answers

What is finished, and what is not.

This is a build in progress, and describing it as a finished shop would be the same kind of overstatement this studio removes from its own pages.

Working, verified in production

  • Catalogue of 5,475 titles, browsable and searchable
  • Faceted browsing by condition, format, genre and reading age
  • Author, publisher, genre and individual book pages
  • Customer accounts — registration and sign-in
  • Basket, with new and used copies together
  • Checkout mechanics: orders with line items, VAT, shipping and status history
  • Order confirmation and order history
  • Content management: pages, posts, media, menus, site settings
  • Sitemap, robots, structured data and per-page meta

Not done yet

  • Payment gateway. Orders reach pending-payment and stop there. Nothing is charged.
  • Bulk catalogue import from the supplier's spreadsheet.
  • Incremental regeneration. Pages render dynamically today; caching is a deliberate later pass.
  • Its own domain. Running on a deployment URL until one is attached.
Questions

Common questions.

Why not just build it on Shopify?

Because the data model does not fit. A used book needs a condition and a condition grade, and a new book needs neither — and both have to sit in one basket with one delivery calculation. Shopify can be bent into that shape with variants and apps, but the bending is permanent and you never own the model. It stayed on the table as a fallback if the custom build had slipped.

Why not WordPress and WooCommerce?

It was considered and rejected in writing. It means a second stack in a second language to secure and update, and the catalogue logic ends up spread across plugins nobody owns. Payload gives the same WordPress-like editing experience — pages, posts, media, drafts, versions — inside the same Next.js application, so there is one deploy and one thing to patch.

Is it actually live?

Yes. It runs on Vercel with a Neon Postgres database and Cloudflare R2 for media, deploying automatically from the main branch. Customer accounts, the basket and checkout mechanics were each verified against production rather than assumed from a local run.

Can you buy a book on it today?

Not yet, and the site does not pretend otherwise. Orders are created and land in a pending-payment state with line items, VAT and shipping calculated — everything up to the moment money moves. The payment gateway is the next piece of work; until it is connected, nothing is charged.

Could you build something like this for us?

Yes, and the honest first question is whether you should. If your catalogue fits a standard product model, Shopify will be faster and cheaper and we will say so. Custom earns its keep when the model is genuinely unusual — mixed conditions, unusual fulfilment — or when owning the data outright matters. We scope that before quoting.

Build something like it

Got a catalogue that does not fit a template?

Mixed conditions, unusual fulfilment, a catalogue that changes daily — the cases where an off-the-shelf shop fights you. We will tell you honestly if a standard platform would do the job cheaper.

What the call covers
Free · 30 min
  • 01Your catalogue — how many products, and how far they differ
  • 02Where a template breaks — the specific fields that do not fit
  • 03Build or buy — including when buying is the right answer
  • 04A fixed price — in writing, within 24 hours of the call
Talk about a build →
Get a free quote
Talk to a person
We reply within one working day
A real person reads every message. No chatbot.