Skip to content

Technical basis for Offline Capability in HortiView

This section explains the technical background of how the offline capabilities are implemented in HortiView.

Service Worker

Service Worker

A Service Worker is a background script that intercepts network requests and enables offline experiences. It uses an event-driven lifecycle (install, activate, fetch) and requires HTTPS. We use the Service Worker to ensure, that modules are precached, to make them work, even when no connection is available, once a user opens a module for the first time. See Mozilla MDN Service Worker

Why we use a Service Worker

Offline capability in HortiView is not just about caching files—it’s about creating a resilient application layer that can function even when the network is unavailable. A Service Worker is the key technology enabling this because:

  • Network Independence: It intercepts requests before they hit the network, allowing us to serve cached responses or fallback content when offline
  • Performance Optimization: By caching critical resources and applying smart strategies (e.g., Stale-While-Revalidate), we reduce load times and improve perceived performance
  • Controlled Updates: Service Workers have a lifecycle that lets us manage versioning and updates without breaking the user experience
  • Background Processing: They run in a separate thread, enabling tasks like precaching, background sync, and push notifications without blocking the UI

In short, the Service Worker acts as a programmable proxy between the application and the network, giving us full control over how resources and data are fetched, cached, and updated.

How a Service Worker works (Conceptual Lifecycle)

Think of the Service Worker as a state machine with distinct phases:

  1. Registration The browser registers the Service Worker script for a given scope (usually the root of the app). From this point, the SW can start managing requests for that scope.
  2. Installation During installation, the SW prepares the environment:

  3. Precaching: It downloads and stores essential assets (HTML, CSS, JS, language files) so the app shell can load offline

  4. Setup: It creates named caches and seeds them with critical resources

  5. Activation Once installed, the SW becomes active:

  6. Cache Cleanup: Old caches are removed to prevent version conflicts

  7. Control: The SW claims open pages so it can intercept their requests immediately

  8. Fetch Interception This is the core of offline capability:

Every network request passes through the SW. The SW decides whether to:

  • Serve from cache (offline or performance reasons)
  • Fetch from the network (for "freshness")
  • Apply a strategy (e.g., Cache First, Network First, Stale-While-Revalidate)

For navigation requests, it can provide an offline fallback page if the network is down.

  1. Update Cycle When a new SW version is available:

  2. It installs in the background.

  3. Waits until the old SW is released (or we trigger an update).
  4. Cleans up old caches and takes over seamlessly.

Why This Matters for HortiView

  • Platform-Level Resilience: The Service Worker ensures the main platform shell and navigation remain functional offline.
  • Module Federation Support: It can precache remote module assets so modules load even without connectivity.
  • Data Availability: Combined with TanStack Query persistence, the SW guarantees that critical endpoint data is available offline.
  • User Experience: Updates are controlled and predictable, avoiding broken states or mixed versions.

Precaching

Once the Service Worker is active and the application shell is cached, the next critical step is preloading any kind of data. This ensures that both the platform and its modules can operate offline with consistent data. Therefore we need to preload the platform data first, followed by the modules itself - each Module needs to handle the Preloading and Offline Capability by itself, but if you use the Module Template or at least the Module Base, there will be helper methods to do so.

Why Preloading Matters

Offline capability is not just about static assets; it also requires data availability.

Preloading:

  • Reduces latency for first interactions
  • Guarantees essential data is available offline
  • Creates a predictable state for modules and platform features

Platform-Level Preloading

First, the platform loads its core data. All information originates from two central endpoints, which are fetched immediately after the Service Worker is ready. These responses are then transformed into initial data structures that represent the most commonly used endpoints. This transformation ensures that the application can render key views without additional network calls.

To manage this data efficiently, the platform uses TanStack Query, which provides a declarative approach to caching and fetching. TanStack Query also supports persistence through mechanisms like PersistQueryClient, allowing the data to survive page reloads and remain available during offline sessions. This persistence typically relies on IndexedDB or localStorage, making the platform resilient even when connectivity is lost. We use the IndexedDB.

Deep Dive: TanStack Query Persistence

TanStack Query allows you to persist query state across sessions using PersistQueryClientProvider.

  • Storage Options: IndexedDB (recommended for large datasets), localStorage (for small data)
  • Benefits: Offline-first rendering, reduced API calls, and resilience during network loss See TanStack Query Documentation

We also use TanStack Query and the same persister logic for the ModuleTemplate.

Module-Level Preloading

After the platform-level data is prepared, the system triggers the OnPlatformLoaded event. This event signals all registered modules that the platform is ready and that they should start their own offline preparation.

Each module responds by preloading its specific data using the same concept as the platform: fetching critical endpoints, hydrating its own TanStack Query cache, and storing the data in a module-exclusive persistent storage. This isolation ensures that modules do not interfere with each other’s data and can operate independently offline. The module template is designed to integrate TanStack Query with a dedicated persister, often referred to internally as PersistentQueryStorage, which guarantees that module data remains available across sessions.

In addition to data preloading, modules may execute further steps to achieve full offline capability. These steps can include caching module-specific assets, setting up an action queue for mutations that need to be retried when the network returns, and adjusting features that cannot function offline. For Details see Implementation of Offline Capability in Modules

HortiView Events

The OnPlatformLoaded event is part of the HortiView Events system. It ensures modules can hook into the platform lifecycle and perform offline setup tasks like:

  • Preloading data
  • Registering action queues
  • Preparing UI for offline mode

Additional Module-Specific Steps

  • Action Queue Setup: Queue mutations for later execution when back online
  • Feature Flags: Disable or adapt features that cannot work offline, by using the Offline-Property See Properties

Module-Level ActionQueue

Offline capability in HortiView does not stop at reading data—it also supports write operations when the user is offline. These write operations, such as creating or updating entities, are referred to as mutations. Since mutations typically involve POST or PUT requests to the backend, they cannot be executed immediately without connectivity. To solve this, HortiView introduces the ActionQueue.

The ActionQueue is a mechanism for queuing mutations during offline sessions. Instead of failing or blocking user actions, the platform captures these operations as OfflineActions. Each OfflineAction contains an instruction set describing:

  • The function name to execute when connectivity is restored
  • The arguments required for that function

These instructions are stored by the platform and associated with the relevant module. When the application detects that it is back online, the platform notifies the module and returns the queued instructions. The module then interprets these instructions and executes the corresponding functions, ensuring that the intended changes are applied.

Why This Design?

Persisting full JavaScript functions across sessions is not feasible because functions often depend on runtime context and imports that disappear when the module is unloaded. Instead, HortiView persists declarative instructions (function name + arguments), which the module can resolve later using its own logic. This approach guarantees:

  • Consistency: Actions are executed in the correct order once online
  • Resilience: Even if the user closes the app, queued actions remain available
  • Security: Modules only execute their own queued actions, preventing cross-module interference

We wanted to keep the "control" of any action with the module owner.