add cache if server is offline

sometimes I happen to be in places where the internet doesn’t work, and I would like to at least be able to read the todo list

although I imagine there could be problems with the changes

I thought that you could add a checkbox and choose which tables to have offline

There are plans to make this happen. It’s relatively complicated though, that’s why it needs some thought.

I’m new to vikunja and I havent’ yet give a look at its source code but there are a lot of library/framework to help PWA implementing caching strategies (eg. Workbox  |  Chrome for Developers )

Hi there,

Maybe you could use this to have your todos even offline, until
the feature is developped ?

best regards
seals187

Saving the page as a single HTML file can certainly be useful for quick offline reference, but it essentially provides a static snapshot of the data, with no real capability for meaningful interaction. At that point, you might as well take a screenshot.

The true value of a well-implemented cache, for example using a framework like Workbox in a PWA, lies in its ability to allow local changes that can later be synchronized with the server once it becomes available again. This approach ensures a seamless and uninterrupted user experience, even during temporary connectivity loss.

Alternatively, a more structured and robust solution for offline data access could involve integrating a protocol like CalDAV. This would not only allow data to be viewed but also modified and automatically synchronized once the connection to the server is restored.

Hi Edo,

ooh I see, a caching function can really do a lot more than i could have imagined.

best regards
seals187

Vikunja already uses Workbox, but only to cache static assets.

This is GREAT !!!

Here are two quick suggestions to improve the offline experience for Vikunja’s PWA:

  1. Cache GET requests for todos with a simple Workbox strategy (e.g., NetworkFirst or StaleWhileRevalidate). This provides immediate offline access to previously fetched data and is very straightforward to set up.
  2. (Later) Add background sync for POST, PUT, and PATCH requests, allowing offline edits that automatically retry when the user goes back online.

1. Quick Win: Caching GET Requests

Because GET calls don’t modify data, you can cache them easily. A minimal snippet (assuming /api/v1/todos) might look like:

import { registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';

registerRoute(
  ({ url }) => url.pathname.startsWith('/api/v1/todos'),
  new NetworkFirst({
    cacheName: 'todos-cache',
    plugins: [
      // Optional plugins, e.g. for cache expiration or
      // to ensure certain response statuses are cached only.
    ],
  })
);
  • NetworkFirst ensures the user gets the most up-to-date data when connected but still serves cached data if offline.
  • Alternatively, StaleWhileRevalidate offers a snappier response from cache but updates in the background (I don’t like this policy very much).

2. Future Improvement: Background Sync for POST/PUT/PATCH

For write operations, a simple cache strategy usually won’t work—requests need to be retried if offline. That’s where Workbox Background Sync comes in:

import { registerRoute } from 'workbox-routing';
import { NetworkOnly } from 'workbox-strategies';
import { BackgroundSyncPlugin } from 'workbox-background-sync';

const bgSyncPlugin = new BackgroundSyncPlugin('todo-queue', {
  maxRetentionTime: 24 * 60, // Retry for up to 24 hours
});

registerRoute(
  ({ url, request }) =>
    url.pathname.startsWith('/api/v1/todos') &&
    ['POST', 'PUT', 'PATCH'].includes(request.method),
  new NetworkOnly({ plugins: [bgSyncPlugin] })
);

With this in place:

  • If the user is offline, those requests queue up and automatically retry when the Service Worker detects connectivity again.
  • You may need to handle potential conflicts if the server data changes in the meantime.

In summary, adding caching for GET endpoints should be quick and painless, and will instantly provide a better offline experience. Later, you can enhance offline capabilities with background sync for write operations if you want full offline functionality.

Hope this helps! If you have any questions or need more code examples, just let me know.

Thanks for the suggestion! I’m afraid it’s more complicated than this, because we’ll also need a way to indicate to the user if the content is cached or not. We have plans for this, but it will take a while to implement.

Maybe I’m missing your point but it’s not that hard to detect and show when a content is fresh or cached.

Below is an example response you could give, showing how to add a custom header in your Service Worker (so the client knows when content is served from the cache), and how you might signal a “read-only” mode when offline.

One straightforward approach to indicate whether content is coming from the cache or the network is to add a custom header (e.g., X-Cache-Status). That way, your frontend can easily detect if the response is cached and adjust the UI accordingly (e.g., disabling edits, showing a “read-only” theme, etc.).

Example: Adding a Custom Header in the Service Worker

If you’re using a custom fetch event handler, you can do something like:

self.addEventListener('fetch', (event) => {
  // Only intercept requests for todos, for instance
  if (event.request.url.includes('/api/v1/todos')) {
    event.respondWith(
      caches.match(event.request).then((cachedResponse) => {
        if (cachedResponse) {
          // Clone and add a custom header to indicate "HIT"
          const responseWithHeader = new Response(cachedResponse.body, {
            status: cachedResponse.status,
            statusText: cachedResponse.statusText,
            headers: cachedResponse.headers
          });
          responseWithHeader.headers.set('X-Cache-Status', 'HIT');
          return responseWithHeader;
        }

        // Otherwise, fetch from the network
        return fetch(event.request).then((networkResponse) => {
          // Clone and add a custom header to indicate "MISS"
          const responseWithHeader = networkResponse.clone();
          responseWithHeader.headers.set('X-Cache-Status', 'MISS');
          return responseWithHeader;
        });
      })
    );
  }
});

Then, on the client side, for example:

fetch('/api/v1/todos')
  .then((response) => {
    const cacheStatus = response.headers.get('X-Cache-Status');
    if (cacheStatus === 'HIT') {
      // We know this data is coming from the cache
      // -> Maybe switch UI to "read-only" mode or show an offline badge
    } else {
      // It's coming from the network
      // -> Normal behavior
    }
    return response.json();
  })
  .then((data) => {
    // Render data in UI
  });

Visual Indication of Offline/Read-Only

For the first step of offline functionality (caching only GET requests), you can:

  1. Disable all modifications in the UI (e.g., hide buttons or forms for adding/editing tasks) when you detect a HIT or a general “offline” state.
  2. Change the theme or style to signal “read-only” mode—this could be something like a greyed-out interface or a banner saying, “Offline: tasks are read-only.”

This way, users immediately know the data is not up to date and cannot make changes that will sync. If you later decide to implement background sync (for POST, PUT, PATCH, etc.), you can remove or adapt these UI restrictions and seamlessly handle offline edits in the background.

That’s correct, it’s just not as easy with the current architecture in the Vikunja frontend. Since we plan to add full offline storage with synching to Vikunja at some point, this would mean adding something now only to remove it later.