Eating my advice: Efficiently Improving on understanding and using Nuxt + Vuex

Todd Baur
ITNEXT
Published in
7 min readSep 12, 2019

--

In the first part of this written just over a year ago I started the piece talking about revising designs. I wanted to fess up to the amount of things that are so awesome about this stack and just invite people to discuss their approaches as well.

A lot of this is coming from daily work in the stack, paying attention to the chatter about it on Github and other places, and it felt like it was time to lay things out. There’s no particular flow, just a collection of concepts or notes.

The UI is really slow

Check https://nuxt-community.github.io/nuxt-i18n/seo.html#improving-performance. I spent days debugging this until I found nuxt-i18n to be the source.

Style Guide (outside of Lint)

I PascalCase.vue the code in my components folder. Everything with a .vue file extension is required to have a name key. Page name are basically PathPage in following a bit of an atomic design pattern.

DRYing a Nuxt app

I quite honestly use a mix of single-file components and generally organize my projects around their use case and/or what entities they manage.

orders (molecules)/
mixins/
validations.js
operations.js
getters.js
OrdersSelectMenu.vue
OrderForm.vue
etc...
core (atoms)/
buttons/
alerts/
progress/
etc..
mixins (particles)/
formatters/
dates/
format.js
fromNow.js
strings/
etc...
apis/external/
operations.js
pages/
orders/
index.vue
orders.vue

Use Vue’s composition api!

I want to cover this in a different topic because it’s huge. But https://vue-composition-api-rfc.netlify.com/#logic-reuse-code-organization for your curiosity. It’s a subject I need to catch up because lots of things were in flight last I looked. Internally I reason that the composition api could be used to build modular concerns and then the render layer can be a very fast mvvvm pattern.

Use mixins!

For example, I have a components/orders/DataTable.vue component. In this pattern I’m setting up an expectation that there is a Vuex module dependency here for orders. This folder can have its own set of mixins that I treat as private. I also have a global mixins folder which I try to use for utilities and such. Most of the time something starts as a mixin in one place and is refactored up or down to public/private status. Mixins are really easy to reason and maintain.

Here is a mixin I use for filtering, sorting, and paginating the data table from query params:

export default {
watchQuery: true,
watch: {
// Allows for this mixin to be extended using object notation
sortable(newval) {
this.$router.push({
path: this.$route.path,
query: { ...this.$route.query, ...newval }
})
}
}
}

Then in my pages/orders/index.vue I just import this:

mixins: [watchQuery],
data() { return { sortable: {} } },

Now your page can set your components start values from query params by using the values in the sortable object. Common keys would be page, rows, sortBy, desc, you get the idea. This also updates the query params and triggers a router event.

With this in place the app can switch to using b-table-simple or even -lite because the UI no longer requires the full featured b-table component that handles sorting/filtering/pagination with the items its given. Full server-side rendering of the data is favorable in most cases anyway.

I also really got tired of seeing import { mapActions } from 'vuex' in so many places.

So it was mixins to the rescue again!

// operations.js
import { mapActions } from 'vuex'

export default {
methods: {
...mapActions({
create: 'items/create',
update: 'item/update',
destroy: 'item/destroy'
})
}
}

Eventually this grew and became its own class for doing all operations in my store and components. It gave me an easy to reason API across components/pages just by importing it. So now a component folder could look like this:

components/
cars/
DataTable.vue
CarForm.vue
DetailsCard.vue
operations.js
computed.js
getters.js

Where operations provide methods calling vuex store actions for cars, computed providing mapping to get/set state for my cars list, and getters for any formatting or other operations my UI uses for formatting information.

Webstorm’s Vue and Typescript support is excellent

An excellent feature of the Vue plugin for Webstorm is to highlight a chunk of template code. Then Refactor -> Extract -> Vue Component. It let’s you name the component right in line and then creates a new component in the same directory. Webstorm also tracks import statements and changes paths when components move.

I’m sure VSCode and others can do this too, but I’m interested in keeping focus on particularly useful tools. Additionally, I don’t get anything for recommending Jetbrain’s tools.

I like Vuex but…

Let’s say you’re tasked with building some personalized dashboard. So the API needs to be called in a bunch of places, and for all those entities there is a place to stuff response data. Ok great, things are good and then the app starts to feel less snappy. It’s using 48gb of ram because vuex is storing 1000’s of items in arrays...

If your app has to manage a lot of entities and you want to have an AREL like syntax to manage it all, check out vuex-orm. It’s very easy to get setup and can help with simplifying complex relationships between entities. There is a great plugin system with it as well. Also very well documented and an active friendly Slack channel.

If you’re looking at vuex-orm and thinking ‘way too complicated for this’ then take a look at https://github.com/davestewart/vuex-pathify. I haven’t worked with it but I frequently see it given glowing reviews.

Otherwise, when components are written try to make them stateless. Ideally components receive props and emit events. Those events can trigger mutations in the parent component, which then update the props’s value. Use destroy hooks to clear state to save memory, and use fetch in Nuxt’s pages to restore state. There are great plugins for vuex caching but it is so much better to rely on the back end API caching headers.

Resetting State

In all my vuex modules I write an action that sets the state back to a default state. Then in store/index.js I have a resetAll function that calls each reset action in the modules I want to nuke.

Defaulting State

const defaultState = {
list: [],
page: 1,
total: 0,
per: null
}

export const state = () => JSON.parse(JSON.stringify(defaultState))

This way you’re certain that state is starting with a fresh object with no prototype pollution. Maybe too aggressive.

On Service Objects

A service object is just that; its a object (usually a class) that comes with common operations for a particular behaviour, for example validating inputs.

I’ve seen plenty of this code around; even saying its the best practice:

services/
orders.js
class Orders
...
getBySomething() { this.$axios.get('/orders/something')...

getByPaychecks() { this.$axios.get('/orders/weird')...

getByGetinBy() { this.$axios.get('/orders/flex')...

By adopting this pattern of encapsulating service calls it keeps Vuex actions out of the picture, which is often the largest part of a module. That’s good right? Smaller modules are easier to reason and test.

I went down this path for a while and realized an anti-pattern in Nuxt/Vuex. Here is why:

  1. Not using actions breaks the reactivity flow
  2. There is absolutely nothing about Javascript that wouldn’t let a developer create action modules and import them like mixins virtually anywhere.
  3. Nuxt has a request lifecycle for SSR that the service objects must respect, and if not can result in weird loading errors that are hard to debug.

One use of service objects is separating configuration of axios calls from the logic, validating params, and checking authorization. For example:

./store/orders.js 
async getAllOrders({commit}, params) {
await this.$axios.get(Orders.ordersPath, Orders.params(params))
}
...
class Ordersconst ordersPath = '/orders'function params(params) { orderParams = validate(params)
if(orderParams.valid) {
return orderParams
}
}
...

Service object can prevent invalid and excessive API calls, but I don’t think they are a replacement for Vuex actions. Service objects aren’t mutually exclusive to actions; they can and are used together.

Overall I found its easy to keep the design of the app around relying on Nuxt’s /page level to orchestrate calls to a server, and then mostly avoid importing operations.js in my components directly.

/pages/orders.vue:
import operations from '@/components/Orders/operations.js'
export default {
name: 'OrdersPage',
mixins: [operations]
}

Bundle Size

Don’t bundle all the things.

You don’t have to yarn install every front end dependency, especially if you want to use moment.js. I suggest using CDN urls in the nuxt.config, or in the layout’s head key. You can generate a webpack analysis of your bundle by setting nuxt.config.js to analyze: true. See the docs about analyze.

Useful Snippets

Speaking of moment.js if you happen to switch to date-fns here is a fun little snippet:

// plugins/datefns.js
import Vue from 'vue'
import { distanceInWordsToNow, format } from 'date-fns'
import FormatTime from '@/components/FormatTime'
import FromNow from '@/components/FromNow'

Vue.filter('fromNow', function(timestamp = new Date()) {
return distanceInWordsToNow(timestamp) + ' ago'
})

Vue.filter('formatTime', function(
date = new Date(),
how = 'h:mmA MMM Do YYYY'
) {
return format(date, how)
})

Vue.component('format-time', FormatTime)
Vue.component('from-now', FromNow)

And components/FormatTime.vue

<template>
<span>{{ time | formatTime }}</span>
</template>

<script>
export default {
name: 'FormatTime',
props: {
time: {
type: String,
default: () => new Date().toDateString()
}
}
}
</script>

And components/FromNow.vue

<template>
<span>{{ time | fromNow }}</span>
</template>

<script>
export default {
name: 'FromNow',
props: {
time: {
type: String,
default: () => new Date()
}
}
}
</script>

If you’re still into needing more than that, https://www.npmjs.com/package/vue-date-fns is decently done.

Handling sets of sets

Something famously hard in Bootstrap is rows of card groups. I wrote a mixin to split my array into sets of arrays of any size I want.

export default {
computed: {
chunkSize() {
if (process.client) {
return window.innerWidth > 768 ? 3 : 2
}
}
},
methods: {
foldm(r, j) {
return r.reduce(
(a, b, i, g) => (!(i % j) ? a.concat([g.slice(i, i + j)]) : a),
[]
)
}
}
}

To use it just call foldm:

<b-card-group v-for="(chunk, i) in foldm(items, chunkSize)" :key="i" deck>
<item-summary-card
v-for="item in chunk"

Conclusion

I hope you find these bits useful! I expect this information will expand as I add and edit in the future. Feel free to toss in your opinions here!

--

--