5 min read

Mudflap's Card Application Funnel

Mudflap card application funnel on mobile

Nobody had defined what “optimize for mobile” actually meant. A PM flagged a page in the dashboard that looked wrong on phones. Separately, an Engineering Director pushed for broader mobile improvements across the experience. These were the same request at two different scopes — and without sharper definitions, either could absorb unlimited time.

I used both signals to do something neither had done: scope the problem with data.

Using Amplitude analytics, I segmented mobile traffic by device model and viewport distribution to understand real-world usage constraints. That let me define a practical design target: the 375px logical width breakpoint, covering the meaningful majority of our mobile users. Devices representing less than 1% of traffic were explicitly out of scope — over-optimizing for statistical noise would have diluted focus without returning value.

From there, I audited roughly 70 user-facing views, systematically evaluating layout, responsiveness, and content hierarchy at that viewport. The audit surfaced recurring failure patterns: text truncation, misaligned components, degraded visual hierarchy. These weren’t cosmetic — they sat directly in the path of user flow.

I partnered with design to align on high-impact fixes and independently implemented frontend changes where I could do so with creative license — improving spacing systems, responsive breakpoints, and simplifying key interaction surfaces to reduce cognitive load on smaller screens.

The audit also surfaced design system components that warranted deeper structural overhauls. We deferred those deliberately. Pursuing component-level redesigns mid-initiative would have concentrated effort on isolated parts of the funnel while the broader experience remained broken. Instead, we focused on legibility and functional behavior: ensuring components worked, read clearly, and stayed out of the way on mobile. The deeper work was logged for a later phase.

To validate impact, I worked with product and data to structure an A/B test, isolating the mobile experience changes against a control group. We measured downstream conversion metrics and confirmed a significant lift attributable to the improved mobile UX.

In the following quarter, I returned to those deferred components and delivered major redesigns: Tooltip, Toggletip (rebuilt for touch interaction patterns), Table, Filters, Select/Dropdown, Badge, and Icons — each improved for responsiveness, branding alignment, and accessibility. Much of what I worked through in those redesigns fed directly into Marq, my own design system.

Chapter 2: Performance

With the layout and UX issues addressed, I turned to load performance—specifically, how fast the funnel could get usable content in front of a user on a mobile connection.

The first lever was page-level code splitting. The funnel serves two meaningfully different user populations: new users going through onboarding, and returning fleet users accessing the dashboard. These paths have different dependencies, different interaction patterns, and different performance budgets. Splitting at the page boundary meant each route only loaded the JavaScript it actually needed, keeping initial bundles lean and aligning resource loading with real use cases rather than shipping the entire application to every visitor.

Alongside splitting, I introduced a caching strategy that separated long-lived assets from frequently changing ones. Core libraries—frameworks, utilities, anything with a stable version—were given long cache TTLs, so returning users and users navigating between pages paid no network cost for assets they’d already loaded. Application code, which changes with every deploy, was kept in a separate chunk with a shorter cache lifetime. This separation meant cache invalidation was scoped and intentional rather than a blunt instrument.

The second lever was lazy loading for bundle-heavy tracking and analytics code. Third-party tracking scripts were among the largest contributors to initial bundle weight, and they have no bearing on what a user sees or interacts with when the page first loads. Deferring them until after the critical path improved LCP directly—less JavaScript to parse and execute before the browser could render meaningful content.

I measured impact using AWS CloudWatch RUM rather than a formal A/B test — specifically to capture real-world performance from actual users on real devices and connections, not synthetic benchmarks. Before settling on CloudWatch, I evaluated DataDog, Amplitude, and PostHog. DataDog offered the most comprehensive observability suite but carried cost overhead that wasn’t justified for initial performance measurement. Amplitude and PostHog are better suited for product analytics than infrastructure-level metrics. CloudWatch fit the context: we were already on AWS, IAM provisioning wired up cleanly through existing Terraform infrastructure, and cost was negligible.

The decision not to run an A/B test was deliberate. Code splitting and lazy loading are low-risk, well-understood techniques — there’s no realistic version of “we split the bundle and it hurt users.” Structuring a controlled experiment would have added coordination overhead, delayed the work, and consumed data team capacity to confirm something the directional evidence already supported. LCP before and after was sufficient. A/B testing is the right tool when you’re validating uncertain product decisions against user behavior — not when you’re removing unnecessary weight from a page load.