Learn when ClojureScript is a good fit for mobile web apps, how PWAs actually help, and how to structure state, offline behavior, and performance for mobile browsers.
Progressive Web App (PWA): A web application that can progressively add app-like capabilities such as installation prompts, offline support, background behavior, and push messaging when the browser and platform allow it.
ClojureScript works well for mobile web apps when the product is still fundamentally a web application: content-heavy tools, field forms, dashboards, internal workflows, checklists, and offline-tolerant line-of-business apps. It is a weaker fit when you need deep background execution, heavy native-device integration, or the distribution expectations of a traditional app-store product.
The important design decision is not “Can ClojureScript feel native?” It is “Which parts of the mobile experience should stay web-first, and which parts truly require a native runtime?” Once that boundary is clear, ClojureScript becomes a strong option because its data-oriented style maps cleanly onto mobile constraints: intermittent connectivity, explicit state transitions, and careful performance budgets.
A mobile web app is a good fit when most of the value comes from:
It is a weaker fit when the product depends on:
This matters because a poor platform fit cannot be fixed by adding a service worker or installing the app to the home screen. ClojureScript helps most when the product problem is already web-shaped.
Mobile browsers are unforgiving about accidental complexity. Large mutable state graphs, duplicated derived values, and loosely coordinated effects tend to surface as jank, stale UI, or hard-to-reproduce offline bugs.
That is where ClojureScript is valuable:
A representative starting point looks like this:
1(ns mobile-web.core
2 (:require [re-frame.core :as rf]
3 [reagent.dom.client :as rdom]))
4
5(rf/reg-event-db
6 :app/boot
7 (fn [_ _]
8 {:network/status :unknown
9 :orders/loading? true
10 :orders/items []}))
11
12(rf/reg-sub
13 :orders/items
14 (fn [db _]
15 (:orders/items db)))
16
17(defn orders-screen []
18 (let [items @(rf/subscribe [:orders/items])]
19 [:main.mobile-shell
20 [:h1 "Orders"]
21 [:ul
22 (for [{:keys [id customer status]} items]
23 ^{:key id}
24 [:li.order-row
25 [:strong customer]
26 [:span.status (name status)]])]]))
27
28(defn init []
29 (rf/dispatch-sync [:app/boot])
30 (-> (rdom/create-root (.getElementById js/document "app"))
31 (.render [orders-screen])))
The point is not that this is “native-like.” The point is that the state model stays legible as the product grows.
PWAs matter, but they are often overstated. Installation, caching, offline pages, and background behavior should improve the experience without becoming prerequisites for core use.
The healthier teaching model is:
MDN’s current PWA guidance still reflects this reality: installability and background capabilities vary by browser and platform, so a mobile web app should remain fully understandable as a website first.
That is especially important now that browser support for app-like features remains uneven. Installation can help. Caching can help. Push may help in some environments. None of those should be assumed to erase browser limits around storage, background execution, or device integration.
The common mistake is trying to “make the whole app offline.” That usually produces brittle caches, stale data, and unclear conflict resolution.
A better model is task-specific offline design:
Storage limits and eviction behavior also matter. A mobile web app should know which local data is essential, what can be reconstructed, and what the user sees if cached data is missing or stale after the browser clears storage.
For example, a field-inspection app might safely queue:
But it might still require online confirmation for:
That separation is much more durable than a vague promise of “offline mode.”
Mobile browsers give you less CPU, less memory, and less patience from users. ClojureScript can still succeed, but only if the UI layer stays disciplined.
Important practices include:
For a substantial app, the architecture usually looks like this:
flowchart TD
A["Touch Input"] --> B["Event Dispatch"]
B --> C["State Transition"]
C --> D["Derived Subscriptions"]
D --> E["Visible UI"]
C --> F["Network / Storage Effects"]
F --> C
This is the real mobile advantage of a ClojureScript stack: not fancy syntax, but a controllable event-and-state loop.
Mobile web products often fail not because offline support is absent, but because it is ambiguous. Users need to know:
Explicit sync visibility usually does more for trust than a silent, magical offline layer.
For a meaningful ClojureScript mobile web app, a modern build typically means shadow-cljs plus your chosen UI and state layer. The build should help you:
What you should avoid is treating “mobile web” as a special snowflake toolchain. It is still web delivery. That means the best outcome usually comes from a normal, current ClojureScript build pipeline plus deliberate product-level mobile decisions.
The most common failure modes are not language-specific:
The ClojureScript-specific failure mode is usually over-abstraction: too much event indirection or too many derived layers for a small product. Mobile apps benefit from explicitness, but they also benefit from restraint.