Is this still completely impossible without using session or cookies?
I'm working towards a quick generated site, with a very basic vuex store. I'd just like to persist some data across refreshes. I've tried hacking together a workaround by setting localstorage at the same time as setting the state, and then reading the state back from localstorage in a middleware, but obviously window not found is getting in the way.
Any quick tips? If there are any it would be great to see the FAQ updated.
Note - spa mode is not an option due to SEO concerns - which is the whole reason for using nuxt.
The easiest way to do what your after is going to be with service workers, in theory you can just bolt one on to your site and forget about faffing about with localStorage.
Take a look at workbox, I've not used it but AFAIK it trivialises service worker implementation.
Regarding spa, can you sgow, explain how user persisted data would be an issue for seo?
I'll give workbox a go out of curiosity. I'm not sure if it's a great solution though - like I said I only want to persist vuex data across refreshes. From their website,
Consider Workbox if you want to:
Make your site work offline.
Improve load performance on repeat-visits. You can use Workbox to store and serve common resources locally, rather than from the network.
@uptownhr I have a front page which I need to be prerendered and crawlable. I want to set some data in the app which hides/shows a single button on the front page. As best as I understand, enabling the global spa mode would let things like vuex-persistedstate work. The downside would be that my entire site would then not be prerendered or crawlable.
@homerjam the workbox module doesn't seem to do anything in this scenario. Vuex still resets after a refresh.
Ah sorry - I was assuming you were populating the store with api requests on the client side. I wasn't considering what happened after that - I usually bind state to the url. Local storage would be the way to go I guess.
Maybe a plugin which runs if isBrowser is true, I'm not great on vuex but perhaps you can serialize/stringify and store state periodically then attempt to read that back on refresh.
@homerjam How would I structure a vue plugin to do that? I really feel like I'm jumping through hoops to achieve something that's dead simple in vue without nuxt.
Probably something like:
export default ({ isBrowser, store }) {
if (isBrowser) {
if (localStorage.get('state')) {
store.commit('state', localStorage.get('state'))
}
window.addEventListener('beforeleave', () => {
localStorage.set('state', JSON.stringify(store.state.state))
})
}
}
How would you do it in Vue? I've only been using it a couple of weeks so still learning best practices myself.
Thanks ๐
So the part I don't understand is if I wrap that up in ~/plugins/something and add it to my plugins array in the config, it will run every time the app is initialised?
You would literally do it with one or two linesโฆ
https://github.com/robinvdvleuten/vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
// ...
plugins: [createPersistedState()]
})
Yep, once on server and again on client. The thing is Nuxt is isomorphic/universal (whatever that buzzword is), it's built to simplify ssr. Although it would appear the store is shared, it's not - state is sent with the server side render to the client (but never back again). You probably could do as you've suggested if in spa mode though right?
Great I'll have a crack at that in a minute. I tried pretty much the same thing with a middleware but I guess it wasn't running at the correct point in the process.
It would be good to see some documentation as to how plugins are bootstrapped or when they are run!
This might help. I'll admit it's confusing having modules, middleware and plugins!
I understand the need for all three. Just unclear on how plugins are executed. I will dig through the source.
This is great apart from your beforeleave event - it doesn't exist. addEventListener('beforeunload') doesn't appear to be executed either, besides that it doesn't have support for IE.
Also the plugin doesn't appear to be run in the browser (during an npm run dev).
export default function (options) {
console.log(options);
}
This logs to the terminal but logs nothing in the browser. Presumably because it's running during the prerender and not in browser.
I wrote that from memory, I think maybe you need isClient not isBrowser. I also couldn't remember that event name!
Apologies for talking to myself.
I think what I've realised is this -
Data can be written to localStorage from a plugin, middleware, store or component with the right conditionals checking that window exists.
Data cannot be read from localStorage by a plugin or middleware, as these are not executed in the browser.
So I'm back at square one. There is no good solution for persisting data cross refreshes or separate browsing sessions, without using cookies or sessions. So there is no way to do this in a generated app without using 'spa' mode, which negates many of the benefits of using nuxt.
Is there some way to request this as a feature?
Here's an actual plugin I'm using which uses the window:
export default ({ isClient }) => {
if (isClient) {
const setVars = () => {
const availableHeight = screen.height - (screen.height - window.innerHeight);
// const isPortrait = window.matchMedia('(orientation: portrait)').matches;
document.documentElement.style.setProperty('--vh', `${availableHeight}px`);
};
setVars();
window.addEventListener('orientationchange', setVars);
}
};
Shouldn't localStorage also be available at this point too?
Thanks @homerjam I think maybe I needed ssr: false when referencing the plugin in nuxt.config? Otherwise I couldn't get anything even logged to the browser console from a plugin file. It was like it was executed during the prerender and then left alone. isClient didn't evaluate to true.
Here's something I've thrown together which works, but there is a caveat.
nuxt.config.js
plugins: [ {
src: '~/plugins/persistence.js',
ssr: false
}],
router: {
middleware: 'persistence'
},
middleware/persistence.js
export default function ({ isClient, store }) {
if (!isClient) {
return;
}
localStorage.setItem('test', JSON.stringify(store.state.test));
}
plugins/persistence.js
export default function ({ store }) {
if (!localStorage.getItem('test')) {
return;
}
let test = JSON.parse(localStorage.getItem('test'));
store.commit('setTest', {
firstProp: test.firstProp,
secondProp: test.secondProp
});
}
And when I have a conditional in a template based on this test data in the store I see this in the console.
The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.
Well yes, this is because your server isn't aware of the persisted state so it's rendering doesn't match (the virtual doms are compared during hydration, and a re-render only occurs if necessary - ie. an error/mismatch).
You'd need to persist your state to the server to get around this (ie. a database). I was under the impression you were using spa mode though?
You might get around this and achieve a bit of a performance improvement if you delay the store commit until after the initial render, either through timeout or maybe some vue event I don't know about.
I understand why it's happening and to be honest I think it's okay, as this will only happen after a user has visited the site and crawlers will not have any data in localstorage to cause the mismatch.
And no, I specifically do not want spa mode to make sure the site is as crawlable as possible. I often don't have the luxury of creating a site and then revisiting it later to evaluate how well google has crawled any JS heavy areas, so prerendering is important. It's one of the main reasons I'm interested in nuxt.
I'm still inclined to politely request that the nuxt community have a think about whether it's possible to provide a drop in solution that works as well as vuex-persistedstate so I'm not closing this issue myself.
Thanks for your help finding a workaround @homerjam !
Me too, ssr ftw!
@gridsystem just curious, why are you not able to emit the commit on a mounted hook on your layout our the page in question?
Just fiddled with this myself and if I interpreted the results correctly, if you're executing that code on the mounted hook, vuex-persistedstate already updated the data in localStorage with the (default) state supplied by the server.
Edit: It seems like I have found a workaround outlined over at robinvdvleuten/vuex-persistedstate#54 it would be great if someone could review it.
There's a great fix here in https://github.com/robinvdvleuten/vuex-persistedstate/issues/54
@zweizeichen, still no better workaround since your last comment? https://github.com/robinvdvleuten/vuex-persistedstate/issues/54#issuecomment-333312145
thanks, BTW
Nope, sorry about that
@gridsystem you probably have something figured out by now but thanks for getting this thread started as I just ran into this part of my Nuxt experience this week and since I also do not want to have to use SPA mode I started worrying that i might have to after going through this thread.
@uptownhr gave me the directional hint to play with the mounted hook accessible in each of the .vue pages you create. The mounted hook is ONLY run on the client side (not the server side) so when you use it you don't need to check variables to see if the rendering happens from client or server, you can just run your code knowing that window, etc. are available to you by that point.
By using the mounted hook combined with sessionStorage (or localStorage if you prefer that instead), I was able to copy whatever data variables were present and needed on my active page into sessionStorage in case of browser reload and then copy them back into the Vuex store after a browser reload happens. As a bonus, I was also able to fire off 3rd party code and code of my own that required window, jQuery, etc. to be ready to use. Yes, I realize that if you need access to window, etc. and also want the data from the 3rd party stuff to be SSR rendered so that Google, etc. can index EVERYTHING on that particular page then there's more to explore, but fortunately, for my purposes, those parts of those pages were not important to me and I was super happy at not being temped to switch over to SPA just to get the pesky SSR reload issue handled.
SIDE NOTE: anything that is an Object can be saved into session/localStorage but you will need to JSON.stringify(theObject) before inserting and then JSON.parse(theStringifiedObject) after retreiving)
So in summary:
Whenever you hit a new page, save any page data in sessionStorage in case you need to get it back if a user hits the browser reload button, and then in each of your .vue files utilize the mounted hook:
for example (inside a .vue file):
export default {
mounted: function () {
// Whatever logic you want like:
// - Retrieve sessionStorage variables and re-insert them into the Vuex store
// - Run any 3rd party stuff or your own stuff (plugins, etc.) that might need to rely on window or jQuery or... being ready for use
},
computed: {
// Whatever computed stuff you need for the page
},
methods: {
// Whatever methods you need for the page
}
}
So. reading/setting session storage or local storage inside a .vue file, is the way to go? because i was trying with vuex-persistedstate, but it wasn't working.
I didn't actually get to the point of trying vuex-persistedstate since I went exploring in the direction as I described above so not sure how well vuex-persistedstate would work but I've been continuing to make my .vue files as I described above and still think it works better than anything else I've thought of or come across yet (note I haven't actually been actively "looking" for alternate solutions though since this one has been working as needed).
CLARIFICATION: @epileftro85 based on your question it might not be clear on where/how I was setting and retrieving the sessionStorage info. While I supposed you can read/write sessionStorage directly within your .vue javascript, I was actually doing the majority (or ALL) of my reading and writing of sessionStorage inside Vuex $store mutators since you really need to update/write to your Vuex $store state variables from inside a mutator anyways so I was just doing the reading or writing from sessionStorage within those mutators as well and simply calling the mutators whenever needed from the .vue files.
Hope that helps
@epileftro85 I just found the same problem and it can be solved by:
@lukasamd, yeah i saw that option, but, i was wondering what about the app performance?
@epileftro85 On spa mode app is faster on server side (no rendering) and slower on user browser (more data to send, rendering on user browser). I think it depend on app size. Vue can use chunks and lazy loading:
https://router.vuejs.org/en/advanced/lazy-loading.html
This https://github.com/kegged/client uses nuxt + vuex-persist
@championswimmer Yes, but it also uses SPA mode :)
I give up and use express session + mongoose session. Works perfectly.
You may want to check out the latest version of vuex-persistedstate where we fixed some hiccups regarding merging state (robinvdvleuten/vuex-persistedstate#54).
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
Here's an actual plugin I'm using which uses the
window:Shouldn't
localStoragealso be available at this point too?