Emmanuel Akhigbe Full-stack web developer and designer. I love learning and writing code to solve problems. Find me on Twitter @theoscoder.

Scalable state management with Vuex and Nuxt.js

6 min read 1701

The Nuxt.js and Vuex Logos

Application state management is a key factor in structuring applications for better scalability and maintainability. In this article, we will learn different ways to structure different sized applications using Vuex and Nuxt.js.

Introduction to Nuxt

Nuxt is a high-level framework built on top of Vue to help you build production-ready Vue applications.

Nuxt.js can help improve development work in Vue in several ways. First, right out of the box, Nuxt.js gives you a great structure for your code, which is also flexible and configurable in the next.config.js file. Nuxt.js optimizes your code and facilitates proper indexing of your application by search engines.

Nuxt.js also comes packed with awesome packages like Vue-router, Vuex, Webpack, Babel, and PostCSS, as well as cool features like auto importing components and seamlessly setting up routing by just creating names of files in the pages directory.

Nuxt.js also provides server-side rendering (SSR) capabilities which enable faster first meaningful paint.

Introduction to Vuex

State Management Diagram
Image from https://vuex.vuejs.org/#what-is-a-state-management-pattern

According to its documentation, Vuex is Vue’s state management pattern and library. So, what does that mean? Simply put, Vuex is a library for managing the data that your components depend on.

Vuex was designed based on Flux (developed by Facebook for managing state) and provides a store that serves as the single source of truth for your Vue application as well as mutations and actions for predictable manipulation of the data in the store.

Before we continue, here are a few important Vuex terminologies to keep in mind:

  • Store — A Vuex store contains the application’s state, mutations, and actions. Stores are reactive, if a component is using the store’s state and there is a change in the state, the component will be rerendered optimally to reflect the change
  • State — A state is a simple object that contains an application-level state. Vue provides an in-component state through the data hook, but if you want the data to be accessible by any component, it is recommended that you store it in the state
  • Getters — These enable computation to be done with data from the store’s state. Alternatively, you can retrieve the state from the store, and perform the computation as needed by a component, but this can become tedious and repetitive if you have several components that need the same computation. With a getter handling the computation, you can just retrieve the computed value
  • Mutations — The store’s state is immutable and can only be mutated through mutations. Mutations are linked with actions. Imagine Vuex’s mutation as just a block of code. This code won’t run unless it is executed
  • Action — An action performs mutations. An action is a command that executes the “block of code” I mentioned in the definition of mutations above. Without this command, the block of code won’t be executed

Why we need state management

State management in an application with many components can be a real pain. The more components you have, the more you’ll need to pass the data up the component tree with the use of events (until you reach a parent component), and then down to the component that needs the data with the use of props.

With fewer components, this relatively is simple:

We made a custom demo for .
No really. Click here to check it out.

Simple Component Structure

With more, however, things become complicated, like in this example component structure:

Complex Component Structure

If component A has the data needed by component G, then you’ll need to move the data through B and E, if component G has data needed by component C, you’ll need to move the data through E, B, and A by emitting events, and then to C through props. As you can imagine, this can become a huge mess as the number of components and nesting grows.

If you can easily move data between components, then you probably don’t need a state management tool like Vuex. But, as Dan Abramov, the author of Redux said, “Flux libraries are like glasses: you’ll know when you need them.” I’d say the same for any state management library.

With Vuex, you can access application-level data and functions from all the components, easily and predictably. You can begin to see what I mean in the diagram below.

Simplified Vuex Component Structure

State management in a Nuxt application

Vuex automatically enables a Vuex store instance when it notices a file that isn’t hidden in the store directory (located in the root directory of the project). Nuxt automatically converts every file (except actions, mutations, and state files) in the store directory to a namespaced module, this means that the actions or mutations of these modules can have the same names and be run independently. Nuxt then scopes the action or mutation to that module.

Nuxt stores can be created in two modes, classic mode, and modules mode.

Classic mode is set to be deprecated in Vuex 3. In this mode, store/index.js returns a method that creates a store instance like this:

import Vuex from "vuex";
import state from './state';
import actions from './actions';
import mutations from './mutations;
import moduleOne from './moduleOne;
const createStore = () => {
    return new Vuex.Store({
       state,
       actions,
       mutations,
        modules: {
            moduleOne
        },
    });
};
export default createStore;

In modules mode, every .js file in the store directory is automatically converted into a namespaced module (index.js). This is the root module that will export an object like this:

import Vuex from "vuex";
import state from './state';
import actions from './actions';
import mutations from './mutations;
export default {
       state,
       actions,
       mutations,
};

There is no need to import the modules because Nuxt will do that for you.

State management in a small scale application

A typical small-scale application includes around five state properties, one action, and five mutations. When you put the state, mutation, action, and getters in the same file, it will not be complex to manage and the file won’t be overly code-heavy.

For this kind of app, simply create a store directory in the root directory of your project, and create an index.js file within it.

Index.js

export const state = () => ({
  counter: 0
})
 
export const mutations = {
  increment(state) {
    state.counter++
  },
  decrement(state) {
    state.counter--
  }
}

When creating a module, the state value must always be a function, while your mutations and actions can be objects. For the store to be created, your store, mutations, and actions must be exported. The store that will be created by Vuex for the above file will be:

new Vuex.Store({
  state: () => ({
    counter: 0
  }),
  mutations: {
    increment(state) {
      state.counter++
    }
  }
})

That is pretty much as simple as a root module can look in Nuxt.

As the number of state properties, actions, and mutations grow to about 20 each, the index.js file begins to look less presentable, maintainable, and manageable. Hence the need to break the store down.

Here are two approaches to structuring your state in a medium and large scale application

  1. Division of concerns — In this approach, you group like-logic together, that is, you have different files for your actions, mutations, and state
  2. Modular approach — In the modular approach, state, actions, and mutations are grouped in the same file or folder based on modules

State management in a medium scale application

Following the first approach means your store directory will look like this:

Store Directory

While this will better accommodate a growing store, I think that each file can get bogus and messy. Imagine there are 30 actions, mutations, and state properties, navigation through the code becomes more hectic and then we may start using comments and spacing to demarcate actions related to different models.

If we follow the second approach, we’ll have to divide our store into modules.

Modules Broken Out

There are different opinions on what should make up a module, some think modules should represent features in the application, some think modules should represent data models from API endpoints. From experience and reading on the subject, I’d recommend the latter for medium-scale applications.

Therefore if we have some endpoints serving these models:

URL Method Description
/product POST Adds a product to the DB
/product/:id GET Gets info of a product
/product/id PUT Edits the info of a product
/product/:id DELETE Deletes a product from the DB
/products GET Gets all the products in the DB
/user GET Gets a user’s info
/user POST Add a user
/user PUT Edit information of a user

Representing data models with modules will make the directory look something like this:

Data Models With Modules in Store Directory

Each module will then have its state (exported as a function), mutations, and actions (exported as objects) either in the module’s index.js file or in their respective files.

State management in a large scale application

Following the modular approach, and representing each data model from the API as modules, we can scale our large application easily. Imagine our app has grown to the point that we are dealing with over 60 state properties, actions, and mutations, neither the approach we used in the small scale or medium scale application will yield an easily manageable store structure.

Instead of creating just a file for our module like we did in the medium-scale application, Nuxt allows us to work with subdirectories in the state, these subdirectories should be inside a modules directory. Each subdirectory in the modules directory will be a module, the name of the subdirectory will be the name of the module.

In this subdirectory, we can create a state.js for the state, actions.js for the actions, mutation.js file for the mutations, and an index.js file that will import these files and export them as an object. Nuxt will take this object as a module and include it in the store (no need for us to import it manually in the root index.js file).

Conclusion

In this article, we have looked at what Nuxt is, some advantages of using Nuxt, what Vuex is, and how it communicates with Nuxt.

We also went through how you can structure your store in a small scale, medium scale, and large scale Nuxt application. Their documentation is also a great resource for understanding these concepts.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Emmanuel Akhigbe Full-stack web developer and designer. I love learning and writing code to solve problems. Find me on Twitter @theoscoder.

Leave a Reply