Persist Vuex State between Page Reloads with LocalStorage and IndexedDB
Tags: javascript, webdev, vue, tutorial
Published: 2021-07-25 01:53:31

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcd75oyntg4rjljgughr.png)
 

[Wortharead](https://www.wortharead.app/), 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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wx3lp958540zzes0h8ng.png)
 

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.

```ts
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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hno81ig1fcr2u0wjbmx.png)
 
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`.

```ts
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.

```ts
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;
```