Skip to content

Integrate Redux Toolkit in your Nuxt 3 app

Posted on:September 20, 2023 at 05:57 PM


I sometimes code applications using React and Vue.js. However, I’ve used Redux more than Vuex or Pinia, so I was wondering if it would be possible to use it in one of my projects with Nuxt 3. I came across some articles explaining how to use it with Vuejs 3, but I’m comfortable using Nuxt 3. So how can I use it with Nuxt 3 ?

I’ve created a todo app as an example. The link is at the end of this post.

Add Redux Toolkit to your project.

Just add @reduxjs/toolkit to your Nuxt 3 project.

npm install @reduxjs/toolkit

Create a store and a Nuxt plugin

We need to create a store and a Nuxt plugin to use Redux Toolkit in Nuxt 3.

// src/store/store.ts
// ESM import Workaround with Redux Toolkit.
import * as reduxToolkit from "@reduxjs/toolkit";
import { PayloadAction } from "@reduxjs/toolkit";
const { configureStore, createSlice } = ((reduxToolkit as any).default ??
  reduxToolkit) as typeof reduxToolkit;
export const todoSlice = createSlice({
  name: "todos",
  initialState: {
    todoList: [] as Todo[],
  reducers: {
    addTodo: (state, action: PayloadAction<Todo>) => {
    removeTodo: (state, action: PayloadAction<string>) => {
      state.todoList = state.todoList.filter(
        todo => !== action.payload
    editTodo: (state, action: PayloadAction<Todo>) => {
      state.todoList = => === ? action.payload : todo

export const { addTodo, removeTodo, editTodo } = todoSlice.actions;

export const store = configureStore({
  reducer: {
    todos: todoSlice.reducer,

type Todo = {
  id: string;
  label: string;
  done: boolean;

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Let’s create a Nuxt plugin to provide the store to the Nuxt app.

import { EnhancedStore } from "@reduxjs/toolkit";
import { App, reactive } from "vue";
import { RootState, store } from "~/store/store";

export const storeKey = Symbol("Redux-Store");

export const createRedux = (store: EnhancedStore) => {
  const rootStore = reactive<{ state: RootState }>({
    state: store.getState(),
  return {
    install: (app: App) => {
      app.provide<{ state: RootState }>(storeKey, rootStore);

      store.subscribe(() => {
        rootStore.state = store.getState();

export default defineNuxtPlugin(nuxtApp => {

Implementation of composables to use the store

We can implement the composables useDispatch and useSelector for the store.

// src/helpers/redux.ts
import { RootState, store } from "~/store/store";
import { storeKey } from "~/plugins/redux";

export const useDispatch = () => store.dispatch;

export const useSelector = <State extends RootState = RootState>(
  fn: (state: State) => State[keyof State]
) => {
  const rootStore = inject(storeKey) as { state: RootState };
  return computed(() => fn(rootStore.state as State));

Usage in a component

Here is an example of how to use the store in a component.

  <form @submit.prevent="onSubmit">
    <h2 class="label-wrapper">
      <label for="new-todo-input" class="label__lg">
        What needs to be done?
    <button type="submit" class="btn btn__primary btn__lg">Add</button>

<script setup lang="ts">
import { useDispatch } from "~/helpers/redux";
import { addTodo as addTodoStore } from "~/store/store";

const dispatch = useDispatch();

const label = ref("");

function onSubmit() {
  if (label.value === "") {
      label: label.value,
      done: false,
      id: crypto.randomUUID(),
  label.value = "";

What about SSR ?

Nuxt 3 enables SSR (server-side rendering) by default. This can cause problems because the code is executed twice, which can cause hydration mismatch. There are two solutions: either disable SSR in the Nuxt options (nuxt.config.ts), or wrap components that use the Redux toolkit with <LazyClientOnly></LazyClientOnly> or <ClientOnly></ClientOnly>.

I choose the second solution, because I want to keep SSR enabled.

// app.vue
  <div id="app">
    <h1>To-Do List</h1>
    <todo-form />
    <h2 id="list-summary" ref="listSummary" tabindex="-1">{{ listSummary }}</h2>
      <ul aria-labelledby="list-summary" class="stack-large">
        <li v-for="item in todos.todoList" :key="">
          <todo-item :label="item.label" :done="item.done" :id="" />
//Rest of the code...