Shared State (Pinia)¶
Each Vue island is its own app, but they can share reactive state through a single page-wide Pinia instance. This is opt-in by design: Pinia loads only when a component imports a store. The island bootstrap then installs that shared instance on the islands β but a page where no component uses a store ships no Pinia at all.
A store module activates the shared instance on import (via ensureSharedPinia()), and because every store resolves against the same active Pinia, any islands that import it share state. Native ESM, pay-per-use.
Requirement: stores import
pinia, so the theme must expose it inexposeNpmPackages(see JS Configuration). If a store is imported without Pinia exposed, the build fails loudly.
Authoring a shared store¶
Call ensureSharedPinia() at the top of your store module, then define stores as usual:
Two different islands β say a header cart button and a cart drawer β that both import { useUiStore } now read and write the same state.
The customer-data bridge¶
useCustomerData is a ready-made store that mirrors Magento's private content (the customer-data sections: cart, customer, wishlist, compare-products, β¦) into reactive state:
It is read-mostly: Magento's native customer-data.js stays the owner of the canonical store (localStorage['mage-cache-storage']) and the /customer/section/load/ endpoint. The bridge reads that cache, re-syncs when Magento broadcasts an update, and exposes reload() for an explicit refresh β so it never breaks Full Page Cache.
| Member | Purpose |
|---|---|
sections |
Reactive map of all sections. |
section(name) |
A single section, or null. |
sync() |
Re-read the canonical store (called automatically on Magento updates). |
reload(names = [], { force }) |
Fetch sections from /customer/section/load/ and merge. |
patch(name, partial) |
Merge a partial section into the reactive map, in memory only. |
snapshot() |
The current map, to go back to. |
restore(snapshot) |
Put it back. |
Projecting a change before the server confirms it¶
patch exists so an interaction can show its result immediately instead of waiting out the round trip β bumping summary_count the moment an add-to-cart is submitted, dropping a row the moment its trash icon is clicked.
It is deliberately memory-only: it never writes to mage-cache-storage. Magento's customer-data.js remains the owner of the canonical cache, so a projection that turns out to be wrong dies with the page rather than outliving it in local storage. snapshot() and restore() are the pair that undoes one when the server refuses:
A flow that reloads the section afterwards needs no rollback at all β the reload overwrites the projection with the truth. See Optimistic UI & Motion for the full pattern and the admin flag that governs it.
How it stays FPC-safe¶
Magento serves cached pages and loads private content client-side after render (via /customer/section/load/, a never-cached endpoint), so per-user data is never baked into the cached HTML. The bridge observes that flow instead of fighting it: it reads mage-cache-storage, subscribes to Magento's update broadcasts, and only the explicit reload() ever hits the network.
Next Steps¶
- JS Configuration β exposing npm packages like
piniato the bundle. - Vue Islands β the per-island app model this state layer spans.
- Optimistic UI & Motion β what
patch,snapshotandrestorewere built for.