The boilerplate includes comprehensive PWA support with installability, offline capabilities, and push notifications. Users can install the app on their devices and receive real-time alerts even when not actively using the application.
Installability
The app can be installed as a PWA on mobile and desktop devices. The manifest is dynamically generated using app configuration values.
PWA Icons
| File | Size | Purpose |
|---|---|---|
public/icons/icon-192.svg |
192×192 | Standard app icon |
public/icons/icon-512.svg |
512×512 | High-resolution icon |
public/icons/icon-192-maskable.svg |
192×192 | Maskable with safe zone |
public/icons/icon-512-maskable.svg |
512×512 | Maskable with safe zone |
public/icons/badge-72.svg |
72×72 | Notification badge |
Offline Support
The service worker is powered by Serwist, a modern service worker library optimized for Next.js.
The service worker is configured in lib/sw/index.ts and registered automatically. It precaches static assets at build time. Same-origin API responses, navigations and React Server Component payloads use network-only strategies and never enter Cache Storage; failed navigations can use the precached offline page. No additional environment variables are needed for offline support.
Caching Strategies
- Precaching: Static assets bundled at build time
- Stale-while-revalidate: Fast responses with background updates
- Network-only: APIs and personalized HTML/RSC stay outside Cache Storage
- Offline page:
/offlineshown for uncached navigation
Serwist doesn't support Turbopack yet. The service worker is disabled in development mode. Run pnpm run build && pnpm start to test PWA features.
Push Notifications
Full push notification infrastructure using Web Push API with VAPID authentication. Users can enable notifications in the My Account page and configure preferences.
Setup
To enable push notifications, generate VAPID keys using the web-push library and add them to your environment variables as VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY (both server-only — no NEXT_PUBLIC_ prefix; the browser fetches the public key at runtime via GET /api/push/vapid-key). Generate them with pnpm exec web-push generate-vapid-keys. The push notification infrastructure handles subscription management, permission requests, and server-side notification dispatch automatically.
Notification Types
| Type | Description | Default |
|---|---|---|
workspace_invitation |
Team/workspace invitations | ✓ Enabled |
credit_alert |
Low credits warning | ✓ Enabled |
subscription_update |
Subscription changes | ✓ Enabled |
system_announcement |
Platform announcements | ✓ Enabled |
ai_completion |
AI task completions | ✗ Disabled |
API Routes
| Route | Method | Auth | Purpose |
|---|---|---|---|
/api/push/subscribe |
POST | Required | Register push subscription |
/api/push/unsubscribe |
POST | Required | Remove subscription |
/api/push/preferences |
GET/PATCH | Required | Get/update notification preferences |
/api/push/vapid-key |
GET | Public | Get VAPID public key for client |
/api/push/track |
POST | Required | Track notification interactions |
The five authenticated push handlers use the verified canonical application actor, never a user or Account ID supplied by the browser. Missing application identity returns 503 APPLICATION_IDENTITY_UNAVAILABLE before body or database access. Responses are private and non-cacheable. Subscribe and tracking retain their service-worker CSRF-token exemptions, but still require the signed completed session and a valid Origin or Referer. Other mutations retain CSRF-token checks.
Preferences return only the ten settings, without database IDs or timestamps. Defaults represent an absent preference row, not a database failure: failed reads, updates or readback return a generic 500. An empty unsubscribe body or {} still removes all of the actor's device registrations; malformed nonempty JSON returns 400 without deleting anything. Subscribe preserves push endpoints and keys while sanitizing display metadata.
Tracking remains best-effort: invalid input and database failures return 200, with synthetic server diagnostics for database incidents. The accepted future timestamp tolerance is one minute from each request. Clicks update only the latest matching type for the authenticated application user; view/dismissed do not change a row. Tags still are not exact notification IDs. Persistence is provider-neutral, while browser Push delivery remains a separate certification concern.
Sending Notifications
Server-side notification sending keeps the web-push SDK's encryption and VAPID signing, with bounded HTTPS transport in lib/push/transport.ts. Call sendPushNotification(options) with userId, type, title, body, URL and optional AbortSignal, or sendLowCreditsNotification(userId, currentCredits, accountId, signal?), from server-only lib/push/index.ts. There is no public send endpoint. Delivery targets the recipient's active devices and respects quiet-hours and per-type preferences. Registration, preference management and interaction tracking live separately in core/notifications/.
config/push.ts owns requestTimeoutMs=10000, maxPayloadBytes=65536 and maxResponseBytes=65536. These are positive integers, capped at 120000 milliseconds and 1048576 bytes. The payload limit counts UTF-8 bytes before encryption, not a promise that a provider accepts that message size. The total exchange deadline starts before signing and DNS and includes connection, TLS, headers and full response-body consumption; activity does not reset it. Synchronous SDK signing is checked immediately after it returns. Timeout, cancellation, truncation or response overflow closes the network resources. A late or incomplete 404/410 response cannot expire a subscription. No response body, endpoint, keys or provider headers enter errors.
Registration and egress share pushServiceHosts: the existing FCM exact host, Mozilla and Windows subdomains, and Apple hosts. Endpoints must use HTTPS and port 443, without credentials, whitespace/control characters or fragments. Rejected values are not trimmed or rewritten. The transport independently resolves both A and AAAA through a cancellable DNS resolver; all answers must be public, and a single validated IP is pinned while preserving the original hostname and TLS certificate verification. OS hosts/NSS overrides and HTTP proxies are not used. Deployments need direct DNS and HTTPS access to the configured providers. There are no redirects, connection-pool reuse, address failover or automatic retries. Existing stored endpoints outside the policy fail delivery without being marked expired.
The low-credit job forwards its cancellation signal: no new notification effects start after its cancellation checkpoint, active Push network work is cancelled, and later device batches do not start. Unattempted devices do not acquire failed counts or delivery journals; interrupted attempts fail. A partially completed dispatch retains its sent counts and reports partial. Already-confirmed sends retain independent best-effort database tracking and journaling. Existing database bounds remain five seconds per I/O, 100 active devices and ten concurrent devices per dispatch, not globally across jobs/users. The job's candidate/preference/cooldown preflight now also consumes parent cancellation. The Push dispatcher's own database reads, post-send writes and already-started bell effects keep their separate contracts. Neither bound is a total job/dispatch deadline.
A timeout or cancellation cannot retract a message already accepted by the provider and is not proof of non-delivery. This is not durable delivery, an outbox/cooldown guarantee or an atomic lock between erasure admission and sending. Local tests exercise real SDK crypto, DNS cancellation, a stalled TLS socket and HTTP response streams; they do not certify external provider delivery.
User Preferences
Users can configure notification preferences including:
- Per-type toggles: Enable/disable each notification type
- Low credits threshold: When to trigger credit alerts
- Quiet hours: Block notifications during specified times
Database Tables
| Table | Purpose |
|---|---|
push_subscriptions |
Per-device push subscriptions (endpoint, keys, device info) |
notification_preferences |
User notification settings (toggles, quiet hours) |
notification_log |
Notification history for analytics (sent, clicked, errors) |
All push API routes that modify data require authentication via apiSecurity.authenticated(). RLS policies ensure users can only access their own subscriptions and preferences.