Progressive Web App (PWA) Support
Add offline support, service workers, and installability with PWAMixin
On this page
djust provides built-in PWA support for offline-first applications with automatic synchronization, service worker generation, and offline-aware template directives.
What You Get
- Service worker integration -- Automatic caching of HTML responses and static assets
- Offline state management -- IndexedDB/LocalStorage abstraction via mixins
- Optimistic UI updates -- Immediate feedback with sync when online
- Offline directives --
dj-offline-hide,dj-offline-show,dj-offline-disable - Automatic manifest generation -- PWA manifest with customizable settings
Quick Start
1. Enable PWA in Your Templates
{% load djust_pwa %}
<!DOCTYPE html>
<html>
<head>
{% djust_pwa_head name="My App" theme_color="#007bff" %}
</head>
<body>
{% djust_offline_indicator offline_text="You're offline" %}
{% djust_offline_styles %}
<div dj-offline-hide>Only shown when online</div>
<div dj-offline-show>Only shown when offline</div>
<button dj-offline-disable dj-click="submit">Submit</button>
</body>
</html>
2. Use PWA Mixins in Your Views
from djust import LiveView
from djust.decorators import event_handler
from djust.pwa.mixins import PWAMixin, OfflineMixin
class MyView(OfflineMixin, LiveView):
template_name = 'app.html'
def mount(self, request, **kwargs):
# No enable call: inheriting OfflineMixin is what enables offline mode.
self.items = self.storage.get('items', [])
@event_handler()
def add_item(self, name: str = "", **kwargs):
created = self.create_offline('Item', {'name': name})
self.items.append(created)
self.storage.set('items', self.items)
self.sync_when_online()
3. Generate the Service Worker
python manage.py generate_sw
You can also serve the service worker from a view instead of a generated file:
# urls.py
from djust.pwa import service_worker_view
urlpatterns = [
path("sw.js", service_worker_view),
# ...
]
service_worker_view reads the DJUST_CONFIG['PWA_*'] keys described
below.
PWA Mixins
PWAMixin
Base mixin for PWA functionality:
| Method | Description |
|---|---|
get_pwa_config() | PWA config dict injected into the template context |
register_pwa_handlers() | Register the install/update event handlers |
handle_install_prompt() | Called when the install prompt is shown |
handle_app_update(version) | Called when a new app version is available |
There is no enable_offline() / disable_offline() / is_offline_enabled() —
inheriting the mixin is what turns the behaviour on.
OfflineMixin
Enhanced offline state management. It does not subclass PWAMixin —
combine them explicitly (class V(OfflineMixin, PWAMixin, LiveView)) if you
want both.
| Method | Description |
|---|---|
storage | Property — the OfflineStorage instance (.get(key, default) / .set(key, value)) |
sync_queue | Property — the pending SyncQueue |
create_offline(model, data) | Queue a create for sync; returns the optimistic record |
update_offline(model, obj_id, data) | Queue an update for sync |
delete_offline(model, obj_id) | Queue a delete for sync |
get_cached_or_fetch(key, queryset) | Serve from cache, else evaluate the queryset |
sync_when_online() | Drain the sync queue |
get_offline_state() | Current offline state dict |
is_online() | Always True server-side — see the note below |
handle_connection_change(online) | Connection-change hook (no built-in caller) |
There is no save_offline_state() / load_offline_state() / handle_online();
use the storage property directly.
SyncMixin
Automatic background synchronization. It does not subclass OfflineMixin,
and it depends on storage / sync_queue being supplied by one — list it
after OfflineMixin (class V(SyncMixin, OfflineMixin, LiveView)), or the
properties raise AttributeError.
| Method | Description |
|---|---|
sync_queue | Property — the pending SyncQueue; enqueue via create_offline() / update_offline() / delete_offline() |
sync_manager | Property — the SyncManager that runs the sync |
sync_create_<Model>(data), sync_update_<Model>(obj_id, data), sync_delete_<Model>(obj_id) | Your hooks, e.g. sync_create_Item; return truthy on success. Case-sensitive (built as f"sync_create_{action.model}") |
Template Tags
{% djust_pwa_head %}
Complete PWA setup in one tag (manifest + service worker registration):
{% djust_pwa_head name="My App" theme_color="#007bff" %}
{% djust_pwa_manifest %}
Generate the PWA manifest link:
{% djust_pwa_manifest name="My App" short_name="App"
theme_color="#007bff" background_color="#ffffff" display="standalone" %}
{% djust_sw_register %}
Register the service worker:
{% djust_sw_register sw_url="/sw.js" scope="/" %}
{% djust_offline_indicator %}
Visual offline status banner:
{% djust_offline_indicator offline_text="You're offline" show_when="offline" %}
show_when is "offline" (the default: shown only while offline),
"online" (shown only while online) or "always". The client swaps the
indicator's text between online_text and offline_text, and its class
between online_class and offline_class (default djust-status-online /
djust-status-offline), whenever the browser's network state changes. The
server renders the state show_when implies, so the first paint is styled.
The "offline" indicator hides itself with its own CSS until the page is
offline; the "online" one relies on the dj-offline-hide rule from
{% djust_pwa_head %} or {% djust_offline_styles %}. (Before 1.2.1 the
"offline" indicator never appeared and the text and class never changed.)
Offline Directives
| Directive | Behavior |
|---|---|
dj-offline-hide | Hide element when offline |
dj-offline-show | Show element only when offline |
dj-offline-disable | Disable form element when offline |
dj-offline-queued | Not implemented — no client code reads this attribute; listed here only so it is not mistaken for a working directive |
The client keeps djust-online or djust-offline on <body>, set at page
load from navigator.onLine and updated on the browser's online /
offline events; the directives are CSS rules on those classes, emitted by
{% djust_pwa_head %} or {% djust_offline_styles %}, so include one of
them. This is browser network state: a WebSocket reconnect does not count as
offline. dj-offline-show elements stay hidden until the body is marked
offline, including before the client has run, so they do not flash on load;
a page that includes the directive CSS but never loads djust's client keeps
them hidden, as it already did dj-offline-hide elements. (Before 1.2.1
nothing set the classes, so dj-offline-hide elements were always hidden,
dj-offline-disable elements always disabled, and bare dj-offline-show
elements always visible, online or not.)
<div dj-offline-hide>
<button dj-click="save_to_server">Save</button>
</div>
<div dj-offline-show>
<p>Changes will sync when you're back online.</p>
</div>
Service Worker Configuration
There are two settings surfaces, and which one applies depends on the consumer:
DJUST_CONFIGflatPWA_*keys (below) are read byPWAMixin.get_pwa_config(),PWAManifestGenerator/manifest_view, andServiceWorkerGenerator/service_worker_view. The two views only take effect once you route them inurls.py.- Plain Django settings
DJUST_PWA_*are what the template tags ({% djust_pwa_head %},{% djust_pwa_manifest %}) andgenerate_swread. They do not readDJUST_CONFIG.
There is no DJUST_PWA dict setting.
# settings.py
DJUST_CONFIG = {
# Manifest
"PWA_NAME": "My Application",
"PWA_SHORT_NAME": "MyApp",
"PWA_DESCRIPTION": "A djust-powered app",
"PWA_THEME_COLOR": "#007bff",
"PWA_BACKGROUND_COLOR": "#ffffff",
"PWA_DISPLAY": "standalone", # fullscreen | standalone | minimal-ui
"PWA_ICONS": [
{"src": "/static/icons/icon-192.png", "sizes": "192x192", "type": "image/png"},
],
# Service worker
"PWA_CACHE_NAME": "djust-v1",
"PWA_CACHE_STRATEGY": "cache_first", # or "network_first" / "stale_while_revalidate"
"PWA_PRECACHE_URLS": ["/static/css/app.css", "/static/js/app.js"],
"PWA_OFFLINE_PAGE": "/offline/",
"PWA_CACHE_DURATION": 86400,
"PWA_ENABLE_BACKGROUND_SYNC": False,
"PWA_SYNC_ENDPOINT": "/djust/pwa/sync/",
}
Unknown PWA_CACHE_STRATEGY values silently fall back to cache_first.
The template tags take their values from their arguments
({% djust_pwa_head name="My App" theme_color="#007bff" %}) and fall back to
these plain Django settings when an argument is not passed: DJUST_PWA_NAME,
DJUST_PWA_SHORT_NAME, DJUST_PWA_DESCRIPTION, DJUST_PWA_THEME_COLOR,
DJUST_PWA_BACKGROUND_COLOR.
Offline Storage
Structured offline data goes through a configurable backend, chosen once for the project:
DJUST_CONFIG = {
"PWA_OFFLINE_STORAGE": "indexeddb", # or "localstorage"
}
indexeddb is the default. What a view sets is not a backend selector:
class TodoView(OfflineMixin, LiveView):
offline_storage = "todos" # a NAMESPACE, not a backend
offline_storage is passed through as storage_name= — it names the key
space this view persists under, so two views with different values keep
separate data on the same backend. To change where data is stored, set
PWA_OFFLINE_STORAGE; to change which slice of it a view owns, set
offline_storage.
Example: Offline Todo App
from djust import LiveView
from djust.decorators import event_handler
from djust.pwa.mixins import OfflineMixin
class TodoView(OfflineMixin, LiveView):
template_name = 'todos.html'
def mount(self, request, **kwargs):
self.todos = self.storage.get('todos', [])
@event_handler()
def add_todo(self, text: str = "", **kwargs):
todo = self.create_offline('Todo', {'text': text, 'done': False})
self.todos.append(todo)
self.storage.set('todos', self.todos)
self.sync_when_online()
@event_handler()
def toggle_todo(self, todo_id: str = "", **kwargs):
for todo in self.todos:
if todo['id'] == todo_id:
todo['done'] = not todo['done']
self.update_offline('Todo', todo_id, {'done': todo['done']})
self.storage.set('todos', self.todos)
self.sync_when_online()
Management Commands
# Basic generation
python manage.py generate_sw
# Custom output path
python manage.py generate_sw --output static/custom-sw.js
# Include static files in the cache
python manage.py generate_sw --cache-static
# Custom cache version (--sw-version: Django reserves --version)
python manage.py generate_sw --sw-version 2.1.0
Adding PWA to an Existing App
- Add
{% load djust_pwa %}to your base template - Include
{% djust_pwa_head %}in your<head> - Mix
PWAMixinorOfflineMixininto your LiveViews - Serve the service worker: run
python manage.py generate_sw, or routedjust.pwa.service_worker_viewat/sw.js - Deploy with HTTPS (required for service workers in production)
Browser Support
| Browser | Support |
|---|---|
| Chrome / Edge | Full |
| Firefox | Full |
| Safari | Partial (no background sync) |
| Mobile Safari | Full with install prompt |
Best Practices
- Service workers require HTTPS in production (localhost is exempt for development).
- Use the
network_firststrategy for dynamic content andcache_firstfor static assets. - Validate data in the sync queue before sending to the server.
- Consider authentication token expiry when designing offline flows.
- Test offline behavior in Chrome DevTools (Application > Service Workers > Offline).