Engineering Freshco’s Online Retail Platform for Web and In-Store Kiosks

How I built a single online platform that powers customer web ordering and tablet-based in-store kiosks, with real-time payments and automated receipt printing.

Live Demo5 min read
#next.js#system-design#iot#pwa
Engineering Freshco’s Online Retail Platform for Web and In-Store Kiosks

#1. From Pen-and-Paper to Automated Retail

When Freshco began its digital transformation, the goal wasn’t just to have a website. They needed to replace a fully manual retail workflow built on handwritten orders, manual receipts, and slow fulfillment with a system that could keep up with real-world store operations.

Every delay at the counter affected customer satisfaction. Every handwritten receipt introduced room for error. As a Frontend Engineer, my responsibility was to design a digital system that didn’t merely replicate existing processes, but actively removed friction from the physical store itself.


#2. The Core Challenge: Two Very Different User Journeys

Freshco operates in two realities at once:

  • Online customers, who expect accounts, order history, and scheduled delivery
  • In-store walk-in customers, who care only about speed

The challenge was to support both experiences from a single technical foundation, without duplicating logic or increasing operational complexity.


#3. User Flow 1: Online Storefront

Domain: freshco.id

This is the primary customer-facing experience.

  • Users can register, manage profiles, and track past orders
  • The focus is long-term retention and brand trust
  • Stability and consistency matter more than raw speed

From a business perspective, this channel builds loyalty and repeat purchases.


#4. User Flow 2: In-Store Kiosk (Digital Signage)

Domain: signage.freshco.id

This flow is designed for tablets inside the physical store.

  • No login
  • No account creation
  • Straight to ordering and payment

The priority here is transactional speed.
Every extra step increases queue time and staff workload.


#5. Architecture Decision: One Codebase, Two Runtime Paths

To avoid maintaining two separate applications, I implemented a single Next.js codebase with different runtime entry points.

Internally, the kiosk flow is identified using a base path (/ds), allowing shared UI and business logic while enabling a distinct “guest-only” experience.

#5.1 Why this matters (non-technical view)

This approach:

  • Reduces development cost
  • Keeps behavior consistent across channels
  • Allows features to ship faster without duplication

#6. Deployment Strategy: Dual-Container Isolation

To ensure stability in a busy store environment, the application runs as two containers:

  • Port A: Public storefront (freshco.id)
  • Port B: High-frequency kiosk traffic (signage.freshco.id)

An Nginx reverse proxy routes traffic based on domain, while Next.js rewrites handle internal navigation.

This setup isolates kiosk traffic, preventing in-store spikes from impacting online customers.


#6.1 Next.ts Rewrite Configuration

// next.config.ts
async rewrites() {
  return [
    {
      source: `/ds/:path*`,
      destination: `/:path*`
    }
  ]
}

#7. Real-Time Payment Feedback with TanStack Query

In a physical store, waiting for payment confirmation is not acceptable. Staff need to act immediately once a transaction is completed.

Instead of introducing WebSockets (which can be fragile on unstable store networks), I used TanStack Query’s controlled polling.

#7.1 Why polling instead of WebSockets?

  • More resilient to intermittent Wi-Fi
  • Automatic retry and error handling
  • Easier operational debugging

From a business standpoint, this ensures the system reacts reliably, even in imperfect network conditions.

const { data } = useQuery({
  queryKey: ["order_status", orderId],
  queryFn: () => fetchOrderStatus(orderId),
  refetchInterval: (data) => (data?.status === "PAID" ? false : 3000),
  onSettled: (data) => {
    if (data?.status === "PAID") {
      printReceipt(orderId)
    }
  },
})

#8. Automated Printing: Removing the Final Manual Step

The most visible impact of this system is the automated receipt printing.

Once payment is confirmed, the system:

  • Checks printer availability
  • Generates a receipt digitally
  • Prints it automatically without staff interaction

This removes an entire manual step from the workflow.

#8.1 Automated Print Flow

For store staff, this means:

  • No typing
  • No printing delays
  • Fewer mistakes

#9. Measured Business Impact

MetricBeforeAfter
User OnboardingAssisted manuallyZero-login kiosk flow
Receipt Generation~60 secondsInstant
Order Processing Time5–7 minutes< 2 minutes
Operational ErrorsFrequentSignificantly reduced
System StabilitySingle runtimeIsolated containers

#10. Final Takeaway

This project reinforced a core belief of mine:

Frontend engineering isn’t just about interfaces. It’s about designing systems people can rely on in real-world operations.

By combining thoughtful architecture, pragmatic trade-offs, and seamless hardware integration, I helped Freshco transition from a manual workflow to a scalable, digital-first retail platform while preserving speed on the shop floor.

Share this article