While in the first part of the article we went through the major differences in Composition API vs Options API and how Reactivity API works in Nuxt 3, now it’s time to talk about how does one handle state management. This concept relies on the features discussed previously, so if you didn’t get the chance to read the first part yet, now’s the time to do it. 🙌
As we know, the variables we declare in our components are only available within that component’s scope. But in real-life applications, we very often need to share some of the variables among other components, functions or pages. And since we learned about composables and reactive data in the last article, I’ll show three ways of handling state management in a Nuxt 3 app, and first, we shall start with an example of how Reactivity API can provide a way for doing that:
Now whenever the store object is mutated, any component that imports it will update its views automatically - hence we have a single source of truth now.
However, this also means any component importing store can mutate it however they want, so to ensure that the global state won’t be arbitrarily mutated by any component, it is recommended to define methods on the store with names that express the intention of the actions.
While ref() and reactive() functions can definitely do the job of managing state in Client-Side applications, if you are building a web application that leverages Server-Side Rendering (SSR), the above pattern can have a number of challenges, including state hydration.
When using server-side rendering with Nuxt, our app is first executed on the server to generate the initial HTML, and there is a high chance that one or more refs are used during that initialisation of our components. Yet once the app is booted up on the client, we’ll have to re-run all of the initialisation code because none of these variables are set, so we have to execute the code yet again to figure out what they should be.
This is where hydration comes in. We take the state we’ve already computed on the server and send it along with the app’s HTML, CSS, and other assets. Then, instead of re-calculating everything, we can pick up where we left off.
Unfortunately, though, ref and reactive cannot do this for us, and normally, this is fine. It only becomes an issue when your ref relies on state from the server, such as a header from the request or data fetched during the server-rendering process.
That’s why Nuxt provides the useState composable to create a reactive and SSR-friendly shared state across components. When Nuxt 3 renders a page, it will store all the states being used from useState and send it along to the client. Then, when the client boots up, it will hydrate that state back in, jump-starting every piece of state that was using useState.
The useState composable is not exactly a whole state management library, though. It lets us share the state using a simple key-based system in order to prevent prop drilling or excessive use of events.
Yet, as applications get larger and more complex, so does the size and complexity of the state. Accessing a flat state with basic keys no longer makes much sense, and that’s why Pinia, the official Vue State Management library, is a must-learn, just like its older sibling, Vuex.
Pinia offers a better developer experience than Nuxt’s useState by providing more features that you’ll likely need as your application grows in size and complexity. While a stubborn developer could achieve the same things without using this library, there’s no need for re-inventing the wheel when the Vue team already did that. Let’s take a look at the features Pinia comes with:
Let’s have a look at how we can define a store using Pinia in Nuxt 3:
The first thing you need to do is to define the store with Pinia’s defineStore function, which takes two arguments: the first is the name of the store, which Pinia uses to connect to the Devtools, while the second argument can be either a Setup function (example above) or an Options API Object (below).
You can think of state as the data of the store, getters as the computed properties, and actions as the methods. Option stores are more intuitive and provide a skeleton that’s easier to work with.
On the other hand, if we look at the example using a Setup function, we can notice that despite being less verbose, it is more concise and:
Now that you read about all three ways of handling state management in Nuxt 3, the most common question would be which one to choose, right?
It all comes down to the app’s complexity. Ref() and userState should be enough for a simple presentation site or a landing page, while Pinia would be the go-to solution for a web app that requires users to log in and do more complex operations.
In this article, I wanted to cover the very basics of what Nuxt 3 brings to the table, but bear in mind that it comes with a lot of other features - and I highly recommend starting with the official Vue and Nuxt documentation before you begin to code. Yet, if you’re more impatient by nature and you like digging into the code first (like I do), here are the steps that will lead you to a fresh Nuxt 3 app with Pinia:
npx nuxi init nuxt-3-demo - this will set up a new project for you without any dependencies installed, so you need to run the following commands to navigate to the project and install the dependencies: cd nuxt-3-demo then yarn install.
Now it’s time to start up the development server with yarn dev.
If you’re familiar with Nuxt 2, you’ll probably notice that the project structure in Nuxt 3 has been a bit simplified - it uses the approach of adding the folders you need rather than having everything and activating them by putting a file inside.
This means Nuxt 3 is much smaller and doesn't include the router by default. That also means there is no pages folder by default. By adding the pages folder and putting a file inside, it will create the router for you. If you only have one page, for example, a landing page, then you may not need the pages folder. So this is the directory structure you’ll see after running the commands above.
The app.vue file is the application’s entry point and acts as the index page.
Before adding pages, we need to adapt the app.vue page by replacing the <NuxtWelcome /> starter component with <NuxtPage>, a built-in component that comes with Nuxt and is required to display top-level or nested pages located in the pages/ directory.
Then, type in your terminal npx nuxi add page about observe how Nuxi (the new Nuxt CLI) creates the pages folder, holding the about.vue file with boilerplate code. Easy as 1, 2, 3!
Now if you access http://localhost:3000/about you will see the newly added page, yet if you try navigating to the “index” page http://localhost:3000/, you’ll get the 404 error - page not found. That happens because you don’t have an index.vue file in your pages folder yet, so type the nuxi command to add it (npx nuxi add page index).
The yarn add @pinia/nuxt command will then add Pinia to your project in order for you to start playing around with state; the only change you need to do is add '@pinia/nuxt' to your modules list in the nuxt.config.ts file, and you’re good to go! 🙌
Now that we have gone through both parts of “Understanding Nuxt 3“ and have set up a Nuxt 3 app from scratch, I can say that the fundaments have been said and set 🚀 . What’s next is to get creative and play around with your Nuxt modules of choice in order to add more complex functionality to the demo website.
Also, if you find yourself in need of migrating a complex Nuxt 2 app to Nuxt 3, I recommend reading this really great article on how to do that.
In the end, I can say I find Nuxt 3 and Vue 3 very exciting to learn despite the big changes that come with them. What’s even more exciting is that the Vue core team encourages the new Composition API approach, yet it doesn’t impose it on the developer. You’re free to stick to Options API if that feels more comfortable, and maybe later on, after you’ve got a good grip and made your first website, you can challenge yourself to refactor it using the Composition API.
Thank you for reading!
Stay up to date with the tech solutions we build for startups, scale-ups and companies around the world. Read tech trends and news about what we do besides building apps.