Skip to content

Global Access to the Pinia Instance

In the previous sections, we've seen how createPinia creates a unique Pinia instance per application and how this instance is provided via Vue's provide/inject mechanism, making it accessible within components' setup functions and related lifecycle hooks.

However, Vue's provide/inject mechanism relies on the component tree structure and the execution context provided by Vue's rendering or component setup process. This means it's not available in contexts that execute outside of this component-based flow, such as:

  • Global router middleware (like defineNuxtRouteMiddleware in Nuxt 3)
  • Standalone utility functions or modules that need to access the store without being imported or called directly within a component's setup.

The Challenge in Router Middleware

Consider a global router middleware in Nuxt 3 that needs to access a store, as in the example you provided:

typescript
// playground/nuxt/middleware/route-logger.global.ts
import { useCounterSmallStore } from "../stores/small-pinia-counter";

export default defineNuxtRouteMiddleware(async (to, from) => {
  const counterSmallStore = useCounterSmallStore(); // This line will cause an error
  console.log("Counter value in middleware:", counterSmallStore.count);
});

In this example, calling useCounterSmallStore() inside the middleware will throw the "Pinia not installed" error we added earlier because inject(piniaSymbol, null) within the useStore function returns null as there is no active injection context available during the middleware's execution outside the component tree. However one thing to note here is to get the pinia instance in SPA. This detail logic in the link (https://router.vuejs.org/guide/advanced/navigation-guards.html#Global-injections-within-guards)

To enable Pinia to work seamlessly in these contexts where inject is not available, there needs to be a way for the useStore function to find the correct Pinia instance. The solution Pinia uses is to maintain a globally accessible 'active' Pinia instance that serves as a fallback.

Introducing activePinia

Pinia maintains a global mutable variable, typically named activePinia, that holds a reference to the Pinia instance that should be considered the "currently active" one. A function, setActivePinia, is used to set this global variable.

The primary place setActivePinia is called is during the installation of Pinia as a Vue plugin (app.use(pinia)). By setting activePinia here, we ensure that a Pinia instance is globally reachable as soon as the plugin is installed and the application starts.

Let's implement activePinia and setActivePinia in our small-pinia.

Implementing activePinia and setActivePinia

We need to declare the activePinia variable and the setActivePinia function in rootStore.ts.

typescript
// rootStore.ts
export let activePinia: Pinia | undefined;

//@ts-expect-error
export const setActivePinia: _SetActivePinia = (pinia) => (activePinia = pinia);

interface _SetActivePinia {
  (pinia: Pinia): Pinia;
  (pinia: undefined): undefined;
  (pinia: Pinia | undefined): Pinia | undefined;
}

Next, we need to call setActivePinia in the install method of createPinia.

typescript
//store.ts
  function useStore(): Store<
    Id,
    _ExtractStateFromSetupStore<SS>,
    _ExtractGettersFromSetupStore<SS>,
    _ExtractActionsFromSetupStore<SS>
  > {
    const hasContext = hasInjectionContext();
    let pinia = hasContext ? inject(piniaSymbol, null) : null;
    // insert into activePinia
    if (pinia) {
      setActivePinia(pinia);
    }
    if (!activePinia) {
      throw new Error(
        `[🍍 pinia] Cannot get current Pinia instance. Did you forget to call "app.use(pinia)"?`
      );
    }
    pinia = activePinia!;
typescript
// createPinia.ts
  const pinia: Pinia = {
    install(app: App) {
      setActivePinia(pinia);

The Middleware Now Works

With these changes in place, when your Nuxt application starts, the small-pinia instance is created and setActivePinia is called within its install method (in the plugin). When the router middleware then calls useCounterSmallStore(), allowing the middleware to successfully retrieve and interact with the store instance.

Reference: You can check the code I write in the pr link https://github.com/KOBATATU/small-pinia/pull/4

Caution: ssr and activePinia

While introducing activePinia solves the problem of accessing the Pinia instance outside components. it introduces a critical issue in a ssr environment: Cross-Request State Pollution(https://github.com/vuejs/pinia/discussions/2077).

On the server, multiple user requests can be processed concurrently by the same Node.js process. Since activePinia is a single, mutable global variable shared across all ongoing requests, one user's request processing could call setActivePinia and change the global reference to its specific Pinia instance while another request is in the middle of accessing the previous instance. This can lead to data leaks and incorrect state being accessed or modified by different users during the server rendering process.

Released under the MIT License.