DEV Community

dhruv
dhruv

Posted on

Offline-First Kiosk: Lessons from the Field

When "Impossible" Becomes "Deployed"

Picture this: You have a tight deadline to build a production-ready kiosk application. It needs to work offline, handle outdated hardware, be completely locked down for security, and provide a smooth user experience. Oh, and it needs to be deployed to physical devices that won't play nice with modern Android features.

Sound impossible? That's what I thought too.

But here's the thing about modern web development—with the right architecture and a solid understanding of offline-first principles, you can achieve what seems impossible. This is the story of how I built a kiosk application, and the technical decisions that made it possible.


The Mission: Offline-First in a Connected World

The brief was straightforward: create an information kiosk that could display data, handle user interactions, and most importantly, keep working even when the internet doesn't.

Why Offline-First Matters

In the world of kiosks, internet connectivity is a luxury, not a guarantee. Whether it's a venue with spotty WiFi, a remote location, or just network hiccups during peak usage, your app needs to be resilient. Users don't care about your excuses—they expect it to work, period.

The offline-first philosophy flips traditional thinking on its head:

  • Assume the network is unreliable (because it is)
  • Cache aggressively (but intelligently)
  • Provide immediate feedback (even if the server hasn't responded)
  • Sync when possible (but never block the user)

The Tech Stack: Choosing Speed Without Sacrificing Quality

With a tight deadline, every technology choice mattered. Here's what made the cut:

The Core Players

Vite - When you're iterating rapidly, waiting for builds kills momentum. Vite's lightning-fast hot module replacement meant changes appeared instantly.

React + Ionic Framework - Cross-platform UI components that look native and feel natural on touch devices. No need to reinvent the wheel for buttons, modals, and navigation.

Capacitor - The bridge between web and native. Need to lock down the device? Pin the app? Access hidden settings? Capacitor made it possible without writing Java.

TailwindCSS - Utility classes meant styling components in seconds, not hours.

TanStack Query - The secret weapon. This library handled caching, background refetching, optimistic updates, and error recovery with minimal configuration.

The Configuration That Changed Everything

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // Data stays fresh for 5 minutes
      refetchOnWindowFocus: false, // Don't refetch when window regains focus
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Offline-First Kiosk: Lessons from the Field

This Simple Configuration Meant

  • Data loaded once and stayed cached for 5 minutes
  • No unnecessary network requests
  • Instant UI updates from cache
  • Background refetching kept data current

The Hardware Plot Twist

Just when you think you've got everything figured out, reality throws a curveball.


The Challenge

The kiosk devices presented several obstacles:

  • Outdated Android version with limited Play Services
  • Locked-down settings requiring administrator access
  • Performance constraints that would make a modern smartphone weep
  • No easy way to install standard apps

The Creative Solutions

Problem #1: Can't access device settings

Solution: Activity Launcher app to access hidden administrator menus

Problem #2: Can't install apps from Play Store

Solution: Sideloading APKs manually

Problem #3: Performance limitations

Solution: Aggressive optimization everywhere

// Code splitting for heavy components
const EventInfo = lazy(() => import('../pages/EventInfo'));

// WebP images instead of PNG/JPEG
// Async font loading
// Minimal JavaScript bundles
// Tree-shaking with ES modules
Enter fullscreen mode Exit fullscreen mode

Security: Locking It Down Tight

A kiosk app is only useful if users can't escape it or access things they shouldn't.

Multi-Layered Security

Layer 1: Android App Pinning
The nuclear option — users physically cannot leave the app without administrative action.

Layer 2: Custom Passcode System
Protected administrative functions with a custom implementation when built-in options weren't flexible enough.

Layer 3: Navigation Lockdown

  • Disabled back button
  • Blocked system gestures
  • Prevented accidental exits

Layer 4: Device Administrator Privileges
Used Activity Launcher to access device admin settings and override restrictions.


The Service Worker That Wasn't

Here's the ironic part: despite the emphasis on offline-first, the final implementation didn't use traditional service workers.

Why Not?

  • Time constraints — Service workers require careful lifecycle management and testing
  • Native deployment — Capacitor's native HTTP plugin provided caching out of the box
  • Complexity vs. benefit — TanStack Query handled most offline scenarios elegantly

The Offline-First Principles Remained

Even without a service worker, the architecture embodied offline-first thinking:

  • Local-first data with aggressive caching
  • Optimistic UI updates for instant feedback
  • Native caching mechanisms via Capacitor
  • Graceful degradation when network unavailable

Sometimes the best solution is the one that works with your constraints, not against them.


Data Management: The Smart Way

TanStack Query as Your Sync Engine

This library solved multiple problems:

  • Automatic Background Refetching: Data stayed current without manual intervention
  • Optimistic Updates: Click a button, see the result immediately, sync with server in background
  • Retry Logic: Automatic retries with exponential backoff
  • Cache Invalidation: Smart synchronization that knows when to refetch and when to use cached data

State Management Philosophy

Instead of Redux or MobX, the app used:

  • TanStack Query for server state
  • React Context for global UI state
  • URL parameters for navigation state
  • Local component state for UI interactions

Simple. Effective. Easy to reason about.


Lessons from the Trenches

What Worked Brilliantly

  • Vite for fast iteration
  • Ionic + Capacitor for native capabilities without native complexity
  • TailwindCSS for rapid UI development
  • TypeScript for fewer runtime bugs
  • Component architecture for extensibility

What I'd Do Differently

  • More time for testing edge cases
  • Add React error boundaries
  • Integrate performance monitoring early
  • Push progressive enhancement further

Checklist: Building Offline-First Apps

  • Choose Your Caching Strategy

    • Service workers for web apps
    • Native plugins for hybrid apps
    • Aggressive but intelligent caching
  • Embrace Optimistic UI

    • Update UI immediately and handle rollback on failure
  • Test Offline Scenarios

    • Complete offline mode
    • Slow 3G
    • Intermittent connectivity
    • Server errors
  • Plan for Sync Conflicts

    • Last write wins?
    • Server authority?
    • User resolution?
  • Monitor Performance

    • Bundle size
    • Image formats
    • Code splitting
    • Lazy loading
  • Secure It Properly

    • Lock down navigation
    • Protect admin functions
    • Use device-level security
    • Test escape attempts

The Final Result

After focused development the kiosk app shipped:

  • Smooth, responsive UI
  • Offline-capable
  • Secure and locked-down
  • Handles outdated hardware gracefully
  • Easy to update and maintain

Conclusion: Offline-First Is a Mindset

Offline-first is not just service workers or IndexedDB. It's about designing for unreliable networks and building resilience at every layer.

Tools that helped

  • TanStack Query
  • Capacitor
  • Modern web frameworks
  • Fast build tools

What are your offline-first war stories? Drop them below.


Resources

Top comments (0)