Skip to main content
Back to Blog
Engineering3 min read

How We Built a PWA That Feels Native

Mario Fernandez

Mario Fernandez

CEO · Mar 23, 2026 · 3 min read

Event crew members work on different job sites every week. They use whatever phone they have. Some are on the latest iPhone, others on a three-year-old Android. Asking them to download a native app from the App Store or Google Play introduces friction at the worst possible moment, right when they need to check their schedule or confirm a gig. A Progressive Web App removes that friction entirely. Tap a link, and you are in. No download, no update cycle, no storage anxiety.

Why PWA Over Native

The decision to build JamCrew as a PWA was driven by the reality of how crew members actually use technology on the job. A stagehand at a convention center does not have time to search the App Store, wait for a download, create an account, and navigate onboarding. They need to see their call time for tomorrow. Right now.

A PWA loads in the browser like any website but behaves like a native app once installed. It supports full-screen mode, home screen icons, push notifications, and offline access. The install flow is a single tap on an "Add to Home Screen" prompt. No app store review process, no 200MB download, no version fragmentation. Every user always runs the latest version because the service worker handles updates in the background.

The cross-platform story matters too. One codebase serves iOS, Android, and desktop. We do not maintain separate Swift and Kotlin projects. We do not wait two weeks for App Store review when we ship a bug fix. The update is live the moment we deploy.

Service Workers and Offline Capability

Event venues have notoriously unreliable WiFi. A ballroom full of 500 attendees streaming video will saturate any access point. Crew members need to access their schedules, venue details, and check-in information whether or not they have a signal.

Our service worker caches critical assets and data on first load. The app shell, including navigation, icons, and layout, is cached immediately. Schedule data and gig details are cached as crew members view them, so returning to a previously loaded gig works even in airplane mode. When connectivity returns, any actions taken offline, like confirming availability or logging hours, sync automatically.

The caching strategy is intentional. We use a cache-first approach for static assets and a network-first approach for dynamic data. This means the app always feels fast because the shell loads from cache instantly, while fresh data is fetched in the background whenever a connection is available.

Safari Quirks and iOS Considerations

Building a PWA that works well on iOS requires navigating a set of Safari-specific behaviors. The viewport height calculation is the most notorious. Safari's dynamic toolbar means 100vh does not actually equal the visible viewport height. We use 100dvh (dynamic viewport height) everywhere to account for the toolbar appearing and disappearing as users scroll.

Safe area insets are another consideration. Modern iPhones have a notch at the top and a home indicator at the bottom. Our layout respects env(safe-area-inset-top) and env(safe-area-inset-bottom) so content never gets clipped behind hardware features. The bottom tab bar sits above the home indicator, not behind it.

Push notifications on iOS arrived with Safari 16.4, and we support them through the standard Web Push API. The permission prompt appears after a crew member interacts with the app meaningfully, not on first visit. Nobody grants notification permissions to a site they just opened for the first time.

Performance on Event Site WiFi

Real-world performance testing cannot happen on a developer's home fiber connection. We test on throttled connections that simulate the conditions crew members actually face: congested venue WiFi, spotty cellular in a basement loading dock, or a rural fairground with one cell tower.

Code splitting ensures that crew members only download the JavaScript they need for the screen they are viewing. The gig list screen does not load the payroll module. Image lazy loading defers off-screen images until the user scrolls to them. Font loading uses font-display: swap so text renders immediately with a system font, then swaps to Bricolage Grotesque and DM Sans once they finish loading.

We track Core Web Vitals in production. Largest Contentful Paint stays under 2.5 seconds even on slow 3G. First Input Delay is negligible because the main thread is not blocked by heavy JavaScript bundles. These are not vanity metrics. They directly affect whether a crew member can check their call time while walking from the parking lot to the venue loading dock.

pwamobileengineeringperformance

Ready to streamline your crew management?

JamCrew helps production companies manage crews, gigs, and schedules in one place.

Get Started