Loading

All projects

E-Commerce · 2024

Orbit Store

A template storefront loading in 3.8s was leaking mobile revenue. We rebuilt it headless: static product pages, edge-cached catalogue, a two-step Stripe checkout and an admin panel the team runs without a developer.

Next.jsStripePrisma

Client

Orbit Store

Industry

D2C E-Commerce

Timeline

3 weeks

My role

Full-Stack Developer & UI Designer

The problem

A slow, template-based store with a 3.8s load time and a checkout that lost customers on mobile.

The solution

A headless Next.js storefront with Stripe checkout, wishlists, and an admin panel the team can run without a developer.

+41%

Conversion rate

0.9s

Load time

+2.3x

Mobile revenue

What I built

Two-step checkout

Address + payment on one screen, Apple/Google Pay first on mobile.

Edge catalogue

Products rendered statically and revalidated on webhook, so pages are instant.

Wishlists

Guest wishlists in local storage, merged into the account on sign-in.

Admin panel

Inventory, orders, discount codes and content blocks — no CMS licence.

Architecture

  • Next.js App Router with ISR product pages on Vercel edge
  • Prisma + PostgreSQL for orders, inventory and customers
  • Stripe Payment Intents with webhook-driven order state machine
  • Cloudinary for responsive AVIF/WebP product imagery

Hard problems

Cart abandonment on mobile

Cut the checkout from 4 screens to 1 and added wallet buttons; mobile completion rose 2.3x.

Stale stock counts

Inventory reserved at payment-intent creation and released by a 15-minute expiry job.

Code from the build

A few real excerpts from the repository.

app/api/checkout/route.ts
export async function POST(req: Request) {
  const cart = CartSchema.parse(await req.json());
  const items = await priceServerSide(cart);   // never trust client prices

  const intent = await stripe.paymentIntents.create({
    amount: items.reduce((s, i) => s + i.unitAmount * i.qty, 0),
    currency: "usd",
    automatic_payment_methods: { enabled: true },
    metadata: { cartId: cart.id },
  });

  await reserveStock(items, { expiresInMinutes: 15 });
  return Response.json({ clientSecret: intent.client_secret });
}
lib/webhooks/stripe.ts
switch (event.type) {
  case "payment_intent.succeeded":
    await db.order.update({
      where: { intentId: event.data.object.id },
      data: { status: "PAID", paidAt: new Date() },
    });
    await sendReceipt(event.data.object);
    break;
  case "payment_intent.payment_failed":
    await releaseStock(event.data.object.metadata.cartId);
    break;
}
"Conversions jumped 41% in three weeks and we finally stopped paying an agency to change a banner."
James R. · Founder, Orbit Store

How it was built

  • Discovery call, scope and fixed timeline agreed up front
  • Custom design system built in Figma before development
  • Typed, reviewed codebase with CI checks on every commit
  • Deployed with monitoring, backups and post-launch support

Want something like this?

Start a Project