How I rebuilt my local bike shop’s store on Shopify in a weekend. The constraints that shaped it, the shortcuts that worked, and the decisions that made a two-day turnaround possible.

My local bike shop, Vanilla Cycles, lost its website. The old WordPress site got hacked, and the quote to rebuild it on Shopify came back at $20,000. Mark, who runs the shop, had thrown up a Squarespace stopgap himself, but he felt like the shop had lost its online identity a bit.

Mark’s a legend. And Vanilla is a genuine local institution for cyclists of all kinds, so I offered to rebuild it for him. Here’s how I was able to turn it around in a weekend.

Built for the handoff, not for me

The most important call came before I wrote a line of Liquid: the site had to be fully editable by Mark once I left. He’s not opening a code editor. Price changes, photo swaps, section reordering, all of it has to happen in the Shopify theme editor.

So every piece of copy, image and link is a section setting or a block, never hardcoded. Every custom section ships with a schema and sensible presets, and repeated things like product cards and service tiers are blocks. The test I held to: could Mark rebuild this page from the editor if it got deleted? If not, I’d hardcoded something I shouldn’t have.

It’s more work upfront. It’s also the whole point.

Designed around very little photography

The other constraint was that Mark didn’t have any professional photography. No shoot budget, no library, just whatever was already out in the wild. I ended up mining the shop’s Facebook and old Instagram for everything I could salvage.

That rules out most modern Shopify themes, which are photography-first. Big edge-to-edge heroes and full-bleed lifestyle shots look broken the moment you can’t feed them, and faking it with stock would’ve undone the whole point. So I designed for scarcity. Before building anything, I worked the direction out with Claude design: lean on typography, the brand colour and cream/sand bands to carry the pages, so a handful of imperfect real photos go further than a hundred polished ones. Settling that first meant I wasn’t redesigning around missing images mid-weekend. The photos punctuate the design; they don’t hold it up.

Horizon with custom sections

I built on Horizon and left its header, footer and announcement bar alone rather than fight them. Everything else is a set of custom vc-* sections: hero, service pricing, shop grid, X-Lab showcase, garage, visit/map, reviews, footer.

Each one is self-contained, with its markup, scoped styles and any JS in a single .liquid file. No global stylesheet, no cross-section bleed. Adding a section is one safe operation, and deleting it takes its CSS and JS with it. On a weekend, that isolation is what let me move section by section without regressions stacking up.

To make the site feel coherent and cheap to rebrand later, I defined the whole visual language once. It lives in a single snippet, vc-brand.liquid, rendered in the <head>: fonts, plus CSS custom properties for surfaces, text, type scale, layout constants and colour. Every section references those tokens and none hardcode a hex.

Keep the work that already works

Bookings don’t run on anything I built. Mark had already made his own booking app on Replit, and it worked, matching how the shop actually takes service jobs, and he understood it because he wrote it. The catch was that his app occasionally drops offline, because Replit apps sleep. A “Book a Service” button pointing at a dead page is worse than no button. So the button checks whether the app is up, and if it isn’t, quietly routes to an on-site contact page that fits the same journey.

Mechanically it’s a plain <a> to Mark’s app with a bit of vanilla JS. On page load, not on click, it fires a fetch health-check with a 4-second AbortController timeout. App answers, the link stays. Times out or errors, the script rewrites the href to /pages/book and drops the target="_blank" so the customer lands in the same tab on a page built to catch them.

Migrating out of Squarespace

Products came in via Shopify’s CSV import. The Squarespace image export URLs were a little unreliable, and at least one was simply broken and had to be fixed by hand before the import would resolve it. Good lesson to never trust an export’s asset URLs, because a bad row fails quietly and you find it later as a blank card.

What I’d do again

The store’s live at vanillacycles.com. It’s fast, secure, fully in Mark’s hands, and finally reading like the great community shop that it is.