Dan OlekhDan Olekh
SportMagaz preview

SportMagaz

A sports-equipment store for Ukraine with its own admin panel - built, shipped and rebuilt solo

Visit site
Client
SportMagaz (sports equipment store, Kyiv)
Role
Solo full-stack engineering
Year
2024 – 2026

The brief

SportMagaz sells treadmills, exercise bikes, gym machines, padel rackets and tennis tables across Ukraine - big-ticket things people research for days before they buy. The store needed:

  • a catalog you can actually narrow down, by brand, price and the specs that matter in each category;
  • delivery the way Ukraine ships: a Nova Poshta branch picked from a list, not typed from memory;
  • an admin the owners run themselves - products, photos, prices, sales, the homepage - without calling a developer;
  • pages that feel instant when you move around the catalog.

I built all of it alone: the storefront, the admin panel, the database, the integrations and the deploys. First on Next.js in 2024, then rebuilt as a static Astro store on Cloudflare in 2026.

What it's built on

Next.js 14AstroReacttRPCDrizzle ORMPostgreSQLCloudflare WorkersEffectAWS S3TiptapTailwind CSSNova Poshta APITelegram Bot API

What was achieved

Its own admin panel, shaped around the store

Off-the-shelf CMSs think in posts and pages. A store thinks in products with specs, photo sets, sale prices that expire, and a homepage somebody curates. So the admin is its own Next.js app, sharing the database package with the storefront in one Turborepo monorepo.

The product list in the SportMagaz admin: active toggles, product codes, prices, stock and creation dates

  • Two roles - admin and product editor - checked on every server action.
  • Products: rich descriptions in a Tiptap editor, specs, price with a sale price and an end date, stock, YouTube videos, copy a product, switch it off without deleting it.
  • Catalog structure: nested categories and brands.
  • Homepage "lobby": the best seller, featured products and videos, arranged by the owners.
  • Photos live in per-product S3 folders, uploaded straight from the browser with presigned URLs and cropped in place.
  • Orders and reviews, with order statuses.
  • Publish changes rebuilds the storefront, so edits go live without a developer.

Editing a product: tabs for the basics, pricing, category, photos and videos

The homepage lobby: the best seller, the videos row and featured products

Filters that tell you what you'll get

Every product carries its specs as typed attributes - a choice, a yes/no or a number range - in one generic table, so a new category needs data, not a schema change. Each category builds its filter panel from that data: ranges get their min and max, choices get their values, and every option shows how many products you'd see if you ticked it, so nobody filters their way into an empty page.

In the 2026 rebuild the whole category is already in the HTML, and filtering happens in memory by hiding the cards that don't match - no network request, no re-render, no images loaded twice. The page still works with JavaScript off, and the filter URLs are the same as before, so old links and indexed pages keep working.

The store uses Astro's client router with view transitions. When you click a product card, the page doesn't blink and reload - the photo you clicked grows into the product page's main photo, and the header stays where it is.

Recorded on the live site and slowed down 2.5× so the morph is easy to follow.

The detail that makes it work: the name that ties the two photos together is set only on the card you clicked, right before navigating. A category page can hold hundreds of cards, and naming them all would make the browser snapshot every one of them on every click. With reduced motion turned on, the animation is skipped.

Orders, the Ukrainian way

Checkout looks up cities and Nova Poshta branches as you type. Every order sends a confirmation email to the customer, a copy to the store, and a Telegram message straight to the owners' phones. There's a one-click order for people who'd rather get a call back, and a "found it cheaper?" form.

In the 2026 rebuild checkout runs in a small Cloudflare Worker written with Effect, and it recalculates every price on the server - the total the browser sends is never trusted.

Rebuilt static, without losing search traffic

In 2026 I moved the storefront from Next.js to static Astro on Cloudflare. Every product and category page is pre-rendered; React only loads for the parts you touch, like the gallery, the cart and the filters. Publish in the admin rebuilds the store, and a scheduled job every ten minutes rebuilds it when a sale ends, so an expired price never lingers.

A storefront that quietly changes its titles or canonicals during a move pays for it in search traffic, and no functional test would catch it. So before the switch, a script compared the new build's head metadata - titles, descriptions, canonicals, structured data - against the live site, one page of every template. Product, breadcrumb and store JSON-LD, the sitemap and the Google Merchant feed all carried over.

Performance

Performance score100Good
LCP0.8sGood
CLS0.00Good
FCP0.3sGood
TBT0msGood

Lighthouse 13.4, desktop preset, homepage - measured 2026-09-17, the slower of two runs. Mobile runs with simulated 4G swing with image caching: 74-81 on the homepage, 64-100 on a product page.

By the numbers

1developer, front to back
2generations: Next.js, then Astro
65merged pull requests on the first build
~1,400product photos in the pipeline