What is the best way to integrate Vuex store within the router of a Vue 3 SSR application?

Currently, I am working on a Vue3 project with SSR, Vue-Cli, Vuex, and Typescript.

While trying to commit data to the Vuex Store from the router page, I faced an issue. In a .vue file, it's straightforward as I can use this.$store with the typings in vuex.d.ts like this:

this.$store.commit("setFoo", "Bar")

However, when attempting to do the same from a ts file (router/index.ts) where there is no 'this' or vue instance, I encountered difficulties.

I attempted importing the store index file and committing:

import store from "@/store/index"

store.commit("setFoo", "Bar")

This led to an error:

Property 'commit' does not exist on type '() => Store<{ foo: string; }>'.ts(2339)

My store file (as SSR prevents singleton stores) looks like this:

import Vuex from "vuex"

export default function () {
  return new Vuex.Store({
    state: () => ({
      foo: "foo",
    }),
    mutations: {
      setFoo(state, payload) {
        state.foo = payload
      },
    },
  })
}

For Vuex 4, the updated store file is:

import { createStore } from "vuex"

const store = {
  state: () => ({
    foo: "foo",
  })
}

export default function () {
  return createStore(store)
}

In entry-client.js:

import createApp from "./main"

const { app, router } = createApp()

router.isReady().then(() => {
  app.mount("#app", true)
})

As for entry-server.ts:

import createApp from "./main"

export default function () {
  const { app, router } = createApp()

  return {
    app,
    router,
  }
}

In main.js:

import { createSSRApp, createApp, h } from "vue"
// other imports...

export default function () {

 // implementation details...

  return {
    app,
    router,
    store,
  }
}

Within Router/index.ts:

  import { createRouter, createWebHistory, createMemoryHistory } from "vue-router"
  // other imports...

  export default function () {
    return router
  }

The package.json includes various scripts, dependencies, and devDependencies related to the project setup.

Answer №1

Remember to follow the guideline of avoiding stateful singletons, which applies not only to the main app instance and store but also to a router.

The current implementation in Router/index.ts creates a stateful singleton. To resolve this issue, create a "router factory" function that generates a new router instance for each server request. This approach allows passing a store instance into it as well.

Router/index.ts

  import { createRouter, createWebHistory, createMemoryHistory } from "vue-router"
  import axios from "axios"
  import MockAdapter from "axios-mock-adapter"
  import { routes } from "./routes"
  import { isSSR } from "@/helpers"

  const createHistory = isSSR()
    ? createMemoryHistory
    : createWebHistory

  export default function (store) {
    const router = createRouter({ 
      routes, 
      history: createHistory(process.env.BASE_URL)
    })

    router.beforeEach(async (to, from, next) => {
      // do stuff with store (store comes from argument)
    })
  
    return router
  }

Note that the server and client bundles should utilize createSSRApp. If standard createApp is used instead, client-side hydration will not function correctly.

Vue offers a createSSRApp method for use in client-side code to instruct Vue to hydrate the existing static HTML rather than recreating all DOM elements

main.js

import { createSSRApp, h } from "vue"
import { isSSR } from "@/helpers"
import createRouter from "@/router"
import createStore from "@/store"
import axios from "axios"
import VueAxios from "vue-axios"
import App from "@/App.vue"

export default function () {

  const rootComponent = {
    render: () => h(App),
    components: { App },
  }

  const app = createSSRApp(rootComponent)
  const store = createStore()
  const router = createRouter(store)

  app.use(VueAxios, axios)
  app.use(router)
  app.use(store)

  app.provide("axios", app.config.globalProperties.axios)

  return {
    app,
    router,
    store,
  }
}

Answer №2

Your main export is a function

export default function () {

To optimize, consider using:

export default new Redux.Store({...})

If you prefer to keep it as a function, you could also attempt store().dispatch but this will result in a new Redux instance being created each time store() is called.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

TypeScript encountered an error (TS2403) stating that subsequent variable declarations must have matching types

Encountered an issue with my typings.d.ts file Error TS2403: Subsequent variable declarations must have the same type. Variable 'module' is expected to be of type 'NodeModule', but is currently of type '{id:string}'. declare ...

Passing properties to a component from Material UI Tab

I have been attempting to combine react-router with Material-UI V1 Tabs, following guidance from this GitHub issue and this Stack Overflow post, but the solution provided is leading to errors for me. As far as I understand, this is how it should be implem ...

There seems to be an issue with the VueJs + ElementUi Change method as it is

Just starting out with Vue and Element UI. I'm attempting to create a custom component using the ElementUI autocomplete/select feature. The problem I am facing is that the @change method does not contain a event.target.value value. When I try to acc ...

simulate express-jwt middleware functions for secure routes

I am currently facing an issue with my code snippet, which looks like this: import app from '../src/app'; beforeAll(() => jest.mock('../src/middleware/auth', () => (req: Request, res: Response, next: NextFunction) => { ...

Utilizing ease-in effect on show more button clicks in CSS

When I click "show more," I want to have a smooth ease-in/out animation for 3 seconds. However, I am facing difficulties achieving this because I am using overflow: hidden and -webkit-line-clamp: 2; Are there any other methods to accomplish this? https: ...

My React JS page suddenly turned blank right after I implemented a setState() function within my functional component

I was working on my code and everything seemed fine until I tried to incorporate the setState function with setcategory and setvalue. However, after making this change, my react page suddenly went blank. Can anyone help me identify what went wrong and pr ...

Is there a way to define type information for a global variable when utilizing dynamic import within a function?

Here is a simplified version of my server code: server.ts import google from "googleapis"; const androidPublisher = google.androidpublisher("v3"); app.use('something', function(req, res, n){ ... }) ...(only one of the dozens of other meth ...

What is the method for renaming Props in Vue components?

I recently embarked on my VueJS journey and attempted to implement v-model in a component, following an example I found. <template> <div class="date-picker"> Month: <input type="number" ref="monthPicker" :value="value.month" @in ...

What is the best way to determine the appropriate generic type for this situation?

Here is an example of some code: type secondaryObjectConstraint = { [key: string]: number } abstract class Base<TObject extends object, TSecondaryObject extends secondaryObjectConstraint> {} type secondaryObjectType = { myProp: number } c ...

When implementing useReducer with TypeScript, the error "Argument of type '(type, action) => { state: (...}' is not assignable to parameter of type 'ReducerWithoutAction<any>'" may occur

Recently, I decided to delve into learning TypeScript by building a simple shopping cart application. If you want to check out the code, feel free to visit my GitHub repository: https://github.com/CsarGomez/shopping-cart-reducers-tx I've encountered ...

The Vuetify VSelect component displays a blank comment in the DOM instead of the expected rendering

Incorporating vuetify into my project has been a success overall. The components like v-file-input and v-text-field are functioning properly. However, I am encountering an issue with v-select as it is not showing up as expected. Instead, something unusual ...

Incorporating a React Bootstrap spinner into your project

I am looking to enhance my modal by adding a spinner to it. Here is the current structure of my modal: <Modal show={modal.show} onHide={onHideModal}> <form onSubmit={onImport}> <Modal.Header closeButton> <Mo ...

Guide to adding a marker in Vue2-Leaflet while ensuring the popup is initially open

Is there a way to set the pop up as initially open when adding a marker dynamically? <v-map> <v-marker v-for="item in markers" :key="item.id" :lat-lng="item.latlng"> <v-popup :content="item.content"></v-popup> </v-marker ...

Angular Error TS2339: The property 'car' is missing from type 'Array of Vehicles'

Encountering Angular Error TS2339: Property 'vehicle' is not found on type 'Vehicle[]'. The error is occurring on data.vehicle.results. Any thoughts on what could be causing this issue? Is the problem related to the Vehicle model? I hav ...

Real-time updates with Pusher not syncing correctly in Laravel Echo, Vue.js, and Laravel chat application

Recently, I've been diving into a fascinating tutorial on how to construct a real-time chat application using Laravel, Vue js, Laravel Echo, and Pusher js. After diligently setting my BROADCAST_DRIVER=pusher in the env file and configuring the pusher ...

Next.js is displaying an error message indicating that the page cannot be properly

Building a Development Environment next.js Typescript Styled-components Steps taken to set up next.js environment yarn create next-app yarn add --dev typescript @types/react @types/node yarn add styled-components yarn add -D @types/styled-c ...

Show an input field upon button click within a ngFor loop by utilizing *ngIf in Angular/TypeScript

I'm facing an issue with understanding how to utilize *ngIf in a *ngFor loop. Here's my code: <div *ngFor="let movie of movieList" class="movieRow"> <button (click)="onEdit()">click</button> <di ...

How can we define a member of the ReactElement type within an interface using TypeScript?

In an attempt to restrict a specific type of interface member for <Route ... />, the following code does not seem to be functioning as intended. import React, { ReactElement } from "react"; import { Route, RouteProps } from 'react-rout ...

Changing JSON names to display on a webpage

I am looking to modify the name displayed in a json file for presentation on a page using ion-select. mycodehtml ion-select [(ngModel)]="refine" (ionChange)="optionsFn(item, i);" > <ion-option [value]="item" *ngFor="let item of totalfilter ...

Storing numerous string labels and arrays in a TypeScript associative array

I am currently developing a mobile app using Ionic 4 where I need to store various labels and arrays in an associative array. However, I am encountering challenges when it comes to initializing the array, adding new items to it, and updating existing ones ...