Wortharead, a blogs aggregator app that assembled the largest DB of 20,000+ Substack newsletters, uses Vuex to store and manage the shared state of all Vue components, such as the articles in the Today Feed, feeds followed by the user, and articles recommended in the Explore section. However, when using Vuex out-of-the-box, the Vuex state is lost and reset to default when the page is refreshed, causing unnecessary network requests.
This article will talk about how we initially solved this problem using LocalStorage and later how and why we migrated to IndexedDB (hint: LocalStorage only lets you store 5MB of data).
LocalStorage
To persist and rehydrate the Vuex state between page reloads, we initially chose to save the state to LocalStorage after each mutation and read the data from it when the page is reloaded. The vuex-persist plugin implements this functionality and provides extensive TypeScript type declaration.
npm install --save vuex-persist
To use it, install the plugin and import VuexPersistence
from vuex-persist
. Set storage
to window.localStorage
and register vuexLocal.plugin
as a Vuex plugin. Refresh the page and then the state will be saved to LocalStorage.
const vuexLocal = new VuexPersistence({
storage: window.localStorage,
});
Vue.use(Vuex);
const store = new Vuex.Store<State>({
state: { ... },
mutations: { ... },
actions: { ... },
plugins: [vuexLocal.plugin],
});
export default store;
IndexedDB
After several iterations, Wortharead decided to save article content storage in Vuex to ensure that users can read cached articles offline. Since LocalStorage is limited to about 5MB, the large amount of article data quickly exhausted the storage quota, causing unpredictable errors. Therefore, we chose to migrate the persisted state to IndexedDB.
npm install --save localforage
localForage
implements a simple, localStorage-like API for IndexedDB, which is compatible with vuex-persist. Import localForage
from the package and set storage
to localForage
. Since localForage
storage is asynchronous, set the asyncStorage
option to true
.
import Vue from 'vue';
import Vuex from 'vuex';
import VuexPersistence from 'vuex-persist';
import localForage from 'localforage';
const vuexLocal = new VuexPersistence({
storage: localForage,
asyncStorage: true,
});
Vue.use(Vuex);
const store = new Vuex.Store<State>({
state: { ... },
mutations: { ... },
actions: { ... },
plugins: [vuexLocal.plugin],
});
export default store;
When we first attempted to use this library, it seemed to work: data was being successfully stored and the app worked. However, on a page refresh, the data disappeared. We worried that the migration to IndexedDB may not be so easy. After some exploration though, we figured out the issue.
Since localForage
is promise-based storage, the state will not be immediately restored into Vuex. It will go into the event loop and will finish when the JS thread is empty, which could invoke a delay of few seconds. vuex-persist
injected a restored
property to the store
object, which contains a Promise that will be resolved after the state is restored. The beforeEach()
hook in vue-router
could cause the app to wait for vuex-persist
to restore the state before taking any further actions.
import Vue from 'vue';
import Router from 'vue-router';
import { store } from '@/store'; // the location of Vuex store
Vue.use(Router);
const router = new Router({
// define the routes
});
router.beforeEach(async (to, from, next) => {
await store.restored;
next();
});
export default router;
Top comments (1)
Hi! I am just started to play with Orbit.js (orbitjs.com) to manage the Data of my Vue app, and looks very interesting, maybe you want to check it out.
Cheers!