 So, we're a press developer's guide to Qixing, right? So, yeah, caching, caching, what is it, Qixing? Lots of ways to mispronounce, right? So, yeah, so what is caching? In its simplest form, it is just a temporarily storing some sort of resource so that future requests can be made faster, which brings me to my breakfast. So, basically, every morning I like to eat three eggs, make a nice little omelet, put a lot of ingredients in there, right? I put some spinach, maybe some mushrooms, tomatoes, that kind of thing. Ultimately, I cache every day my food, right? So, all I'm doing is I'm going to the store and I buy my stuff in bulk and I put it in a fridge, I put it in a pantry, I put it in a freezer, and I basically store those resources temporarily in a location where I can get to them faster, because I do not want to make a trip to the store every morning for all of the ingredients so I can make my omelet. So, in a sense, we all do this every day, we just may not think about it in the same way, right? So, every time you go to the grocery store, you're cashing. And every time you put that thing in your pantry or wherever, that's essentially the same idea. So, all you're doing in the digital space is the same type of thing. So, that's one of the key aspects of it, is just understanding that. Another key aspect of cashing is the idea of invalidation, right? So, this is the act of expiring some sort of cash resource, right? And we all experience this in real life as well, right? So, we buy milk, it has some sort of use-buy date, maybe it's a, you know, you go to the store and you get the use-buy, or the sell-buy, or the best if sold, or was it best if used-buy. All right, well, and then the actual expiration date, right? But, those are kind of just rough dates, right? Like, they're not hard and fast. And so, it's kind of like the milk, you know, you get it. It depends on whether you store it in the door or in the back of the fridge, how many times you open that door, whether somebody actually left it out. You know, and sometimes it can taste horrible and smell fine, and sometimes it smells horrible, and it always tastes bad in that case. So, you know, so we kind of run into these situations like, well, how do you tell if milk is bad? Well, you know, you can use the baking soda method and put the baking soda on a plate and dump the milk on there. And if it interacts, then it's no good. If you put it in the microwave and you heat it up and it becomes something other than liquid, it's no good. You know, we have all these kind of interesting things you can do to figure out whether milk is bad. Same thing happens with the digital space, right? So we have this data, and ideally, that data is still valid. It may not be valid, you know, after some event takes place. Maybe some remote sources updated your Twitter account, or maybe you've updated a post, or somebody commented on that post. And all of these things could potentially expire your data, but it just depends on kind of what's going on and how your data interacts with the rest of the world, right? So a lot of times it's standard to put some sort of expiration date on it, but that date could be valid. It could have long expired based off of some data change or whatnot. So we have to just be careful about invalidating things when maybe they are still good for another week. You never know, right? So, and then we have cache busting, which is another thing we hear a lot when we hear caching, right? So cache busting is basically forcing a cache to load a new resource. In this case, that's the act of throwing the milk out, and let's go buy some more. And so, getting to actual code here. One way that you'll probably see cache busting, particularly Gutenberg does this. So if you're enqueuing some sort of style or script in WordPress, you give it a handle, and then you give it a URL where that resource can be found, and you also define any dependencies. In this case, there's an empty array for that. And then you have some sort of cache busting. This is usually a version number. So every time WordPress updates all the scripts inside of WordPress, they get a new version number that corresponds to the version of WordPress, right? So every time you update WordPress, whatever the old versions of the scripts that released with that version of WordPress, those will get a new version when you update. And what that will do is it will automatically ensure that if you have these JavaScript files cached on a CDN or somewhere else, that those things will be flushed, essentially, or busted, because this new version number indicates that this is a different URL resource, right? It would depend a question mark version equals to the end of the URL, and that would force that resource to load a new one. So this is a common approach is to essentially have the... In a plugin, you'd use plugins URL. In a theme, you'd use style sheet URI or something like that. And then ultimately, for the version number, what we're doing here is just saying FileMTime, which is a PHP function. Let's check to see if the modified timestamp essentially associated with that file has changed. And if it has, let's use that as our version number. So that basically ensures that if you deploy a new version of said CSS file to the server, then that modification time would change, and then this would essentially have a new version number, and then that would ensure that the resource is loaded. So that's cache busting. So let's take a look at the general types of caching that there are. So in a very general sense, we have two things. We have one, which is the non-persistent cache. So this is basically where in a given page load, any resources that are requested within the context of loading that page, they are cached but only for that specific page load. So they're not persisted beyond that. And WordPress uses this, and we'll go into that a little bit more in a bit. Then we have persistent cache, which is basically where the data remains in the cache across page loads. This is more common and the kind of more ideal situation, but we'll explain kind of how those things come into play. So those are kind of the two general types, but we have a number of methods, and I'm not necessarily going to cover every single possible method, but kind of more the most common methods that you'll see when it comes to caching. So one of these is a CDN, right? So we've all heard of CDN, a content delivery network. Basically all of your files are stored in the cloud, and they're served from the location that's the closest. And so you can kind of set policies as to how those things, how long those files should stay in there, when they should expire and that kind of thing, and you can manually purge, and it kind of gives you some control over that. So that's just a tool that is commonly used. Most people don't necessarily think of it as caching per se, but ultimately you are temporarily storing images or JavaScript files or CSS files in a place, a location, server, closer to wherever your visitor is from, so you can serve them faster. So it's essentially a cache. So another one here is the browser cache, right? So everybody has to use a browser to navigate the web, so the content is stored essentially on that user's computer and managed by the browser itself. And the user can go in and flush the cache themselves, but generally there's cache headers and things when you make a request that indicate what things will be cached for how long and so on. So there's the browser cache. We also have the object cache. This is where essentially we have key value pairs. So typically we'll have some data that has a name and it is stored in memory or in some sort of persistent data store and then those things can be expired or forcibly invalidated based off of other use cases. So this would be... So the object cache would... There's a bunch of different ones like Redis or Memcache is another common one. Those typically by default operate off of the RAM associated with the server. So they're loaded into memory there, but they do have a limitation in that typically whatever machine you're running on has very limited resources. It only has so much RAM available. So I think a common configuration is somewhere around two megabytes of space for cached stuff. Correct. So that's on the server. So the browser cache is one of those things that you can send headers and ask for things to be cached, but ultimately user has to visit and it has to be cached on the machine and then it'll stay, I'm assuming it'll flush it. But yeah, so that's on the server. And then we also have page caching which is where we take an entire page and store the actual rendered HTML and put that into some sort of data store based off of typically the URL. And then in the event that some page is requested, it's going to basically shortcut the loading of WordPress and return the HTML unless that HTML is not available, then it would consider a miss and then allow it to go back through and WordPress would process as usual. So those are some of the common methods. Another one is fragment cache which is very similar to page caching except instead of caching an entire page, we're just getting a segment or a section of the page. So this is commonly done for things that may require a lot more processing. So if you think of, let's say you have an API that generates some sort of output that gets put out into some section of the page, you could cache just that portion and reduce the need to check that API as often. So getting to specifically the things that we can do in WordPress to kind of leverage some of these tools. Obviously some of these things are a little outside of our hands. I'm probably not going to go as much into, for example, sending proper cache headers and things to ensure that things get cached for a certain amount of time. A lot of plugins out there will just do that stuff for you and if you really want to know, you can figure all that out for yourself. But we're going to talk more from the aspect of creating a plugin. What does it look like to do caching? What are the different approaches? What are the ups and downsides of doing that? And what are the considerations? So this is a function that I created that basically has no caching involved at all. So as a good programmer, I prefix it with a vendor name, which I use WP Scholar. And the name of the function is get related posts. And obviously what it's going to do is it's going to get posts related to the post ID that you pass in. So we start out with a line of code there that says we have related posts as an array. It's an empty array. And down at the end, we're going to return whatever related posts we have or find. So all we're doing is we're taking a WP query, running some sort of query, which I've hidden because that's not the important part, right? Except the fact that we're returning IDs from this. So this query is going to return a query object or instance, which is going to have IDs as the post items. So we're basically just saying if the query has posts, then related posts is equal to an array of post IDs. So query arrow posts is going to be an array of nothing but IDs. And so those IDs represent the related posts. And we'll kind of explain in our, so toward the end of this presentation, the last segment is essentially different mindsets and things or things you need to think about when it comes to caching. But so this is our kind of what do you call it when you test and you have control? Yeah, this is our control group, right? So this is what it would look like if we did no caching and then we're going to kind of have different versions here that we take a look at. So the next version is essentially what I call runtime caching. So this idea of a non-persistent cache. So if we were to hit this function multiple times within the code on a single page load, then we would consistently get the same results every time without having to make the query over and over again. But that's all it's doing. The important thing here is to notice that caching is entirely dependent upon the information's passed in, right? So if you pass the same post ID in, you should always get the same data back. So a very common problem is someone who might be implementing something like this would just say static queries equals zero or something or just an array, right? I'm sorry. They would do like static related posts equals array, right? And then say run the query and whatever the result is set it to be that static variable. The problem is you pass in a different post ID. It's going to give you the old query, which would not be any good. So the idea here is that we have an array which represents the queries that have been run for a given post ID. And so we take our related posts. We check to see if the queries array has a post ID, what you call it, index. And then so if it does not, we'll actually run a query and set the post ID as the key and the query result as the value. And that way if we run multiple queries, each query just essentially gets stored in that array. And because we have a static variable, that will actually persist across function calls. So then we'll basically check to see if the item that we just set in the array has posts and if it does we'll fetch those posts and storage related posts and return that. So it gets a little bit interesting when we start dealing with that. But that's kind of a way that you would do it with runtime or non-persistent caching. And it works. So if you happen to have a very high traffic site and unreliable other options, this can work. So the next option here is transients. So transients are a common thing that people will use in WordPress. Essentially what happens with transients just in case you're not aware is if you have some sort of object caching like Redis or Memcache or things like that, calling getTransient and setTransient and all those functions will actually manipulate and work with your object caching that you set up. However, if there is no object caching set up then WordPress will actually store these things into the options table in your database and that is essentially how that will work. So a lot of plugins will use transients because they don't know what caching is set up and if no caching is set up they can leverage the WordPress database itself to store their information. So in this case, again we want to make sure that the post ID, whatever post ID we are requesting related posts for, we always get the same related posts back at least until the cache clears and we do it again, right? So in this case we're calling getTransient and we're doing wpscholarrelatedpost underscore and we're appending essentially the post ID to the end so we have some sort of unique key for our cache so we're naming our transient uniquely to the post ID itself. So there we have a query and if we don't have a query so in the case that the transient doesn't exist it's going to return false so query would be false and in that case if it's false we're going to run a query, do the same thing we have been doing fetching our related posts and in this case once we get our related posts we're going to call setTransient which is going to allow us to use that same cache key essentially so the same name with the post ID appended we're going to pass in our related posts and then we're going to pass in essentially how long it should be before that transient expires so in this case we're going to just cache this for five minutes so we're using the minute and seconds constant which provides multiplying that times five and just makes it easy to read and we're not putting some really big number in there that you have to then calculate. Say that again? True, that's what I get for trying to do it and hurry not actually fully running the code. Good point, yeah. So important thing is to make sure that you actually store the same information that you are fetching. But actually that's something I actually see a lot is where somebody will take information put it in transient, get it back and expect some very specific data structure which is not the data structure they passed in so it's a good thing to check for. So yeah, so transients. So that's a common thing that people use. Another option here is WP object cache the thing to note about the WP object cache which is actually a class but there's a set of functions that essentially manipulate work with that class for you. They're very similar to the set transient, get transient type functions. We have WP cache get, WP cache set, WP cache delete. So these functions are a little different in that they do not ever put information into the database. So if you have a caching system set up like Redis or Memcache then it will use that. If you do not then it essentially does nothing except for runtime caching. So this is where WordPress defaults in this case to a runtime or non-persistent cache. So that instance that we saw earlier where we were essentially doing that part ourselves and doing it with static variables and all of that WordPress will store all of the information that you cache if there is no cache just as a runtime in memory type thing. It stores it until the page load is done basically since it's non-persistent. It does, yeah. Because it was in memory for that particular runtime once that runtime for that page load is done whatever was in memory is gone. So, yeah, it really depends on what plugins are doing what and how cache is configured and whether it's... Well, maybe. Usually, yeah, we'll figure that out later, but yeah. Some of what we cover in the next section might be helpful, we'll see. So, yeah, so the WP object cache will use your persistent cache if it's there otherwise it falls back to your runtime cache. So in this case there's also a little bit more functionality as far as robustness, I should say, in how you can cache things beyond what transients can do. Well, I say that. It's just easier to use, I should say. So WP Cache Git allows you to pass in, for example, the post ID and then a group name which in this case would be WP Scholar-related posts. And so instead of having to concatenate and form my own cache key I can just pass in a unique value and then the name of the group and it will find exactly what I'm looking for. So it makes it a little easier to kind of group things. So this is how WordPress, for example, caches posts themselves. So normally when you load WordPress every call to Git post it would have to do a query to find that post and return it. Obviously, if we're using WP Cache Git and we don't have a persistent cache what's going to happen is WordPress somebody's going to call Git post in their code it's going to trigger WP Cache Git. And so essentially when they call Git post they typically pass in the post ID. So that's what they're doing in WordPress. They're passing in the post ID and then saying we have a group name of posts and that's essentially how it stores all that essentially in, I think of it as an array of information that would just be there until the end. It just needs to be unique. I don't think it necessarily needs to be an integer. Exactly. So yeah, I think the official variable names is... Well, I'd have to go look, but yeah, it could just be anything, I'm pretty sure. So yeah, so then again just like transients if there's nothing there then WP Cache Git will turn false and then you can run your query get your values and then call WP Cache set and pass in your stuff and make sure that you actually are passing in the thing you're getting back. And then you can set your expiration. With transients the interesting thing is if you set zero or don't set a expiration it will actually never expire. Just so you're aware it can be helpful in some cases but in other cases not so helpful. Generally I recommend that if you're caching something you should set some sort of expiration date on things. But yeah, so this is kind of the... another option, right? So there's evaluations to be made of which you should use but take a second and look at some of the considerations when it comes to caching. So there's things that a lot of people don't think about so I want to kind of expose these. So number one, you want to always assume that the cache is completely empty or broken. In other words, don't ever assume that you'll actually get what you're expecting back in your code. Always make sure that your code if it doesn't get the value it's expecting from the cache will go and run whatever it needs to get that information or data. So caching is an enhancement that provides better performance and along the same lines as this assuming that it's broken and you should always go back and get your data. You should never use a cache to add some sort of functionality to your application. You don't want to assume that the data's there and that makes something work differently in your application. You always want to make sure that if caching is unavailable, your stuff works. If caching is available, your stuff just works faster. So again, always set an expiration. You're more than likely at some point going to run into some edge case bug if you don't but also be aware that you don't want to cache more than is necessary because there are limits. I mentioned like some caching the two megabytes is kind of the default. So taking into consideration I want to cache a query results. So my example shows that we're caching just the IDs themselves as an array. So that seems like the minimalist information. I can always use getPost to get the post for each thing that I need. If I were to cache all of the actual post objects I wouldn't have to call getPost but at the same time my caching layer would have more information in there because now it's not just a number, it's a number, it's a post slug, it's probably a lot of other things I don't need or won't really use and if I were just to call getPost for the thing I need, it's very possible that somebody else has already done it. I mean cache is already there available so it's better to get a bunch of IDs and use that than it is the other. You could do it either way. I mean if you know exactly what you need then I would say if you put it in the cache and it's not huge bits of information it's a good option. But if for example you don't know or your application changes it's probably more flexible to store the ID, get the post from that ID and then use whatever you need from it. And the nice thing is if you have a post object that you get after the fact you also have virtual properties and things that allow you to get meta and all that fun stuff. So be aware of the limits and think through how that works. Definitely don't cache the entire query because there's a lot more than just the posts themselves in there as well. Always make sure you test your application with caching on and off. That will help you catch any bugs related to the actual caching of things and don't cache sensitive data. Interestingly, if you made the rookie mistake of caching the entire query what a lot of people don't realize is that the WP query object or an instance of WP query actually contains a reference to an instance of WPDB which contains your username and password for your database. So every time you cache a query in WordPress you are caching your database login information as well. So it introduces an additional security concern which you want to make sure that you're careful. So again, only caching exactly what you need is an easy way to avoid that because you know exactly what's being cached. So it's an easy mistake to make. And ultimately a lot of engineers don't even realize that. So that one I want to highlight for you. So there are... Well, it really depends I guess what caching you're using. So for example, I use local by flywheel for my local development environment and it automatically has Redis available but not active. So there's a plug-in called Redis WP something or other WP Redis I think and you put that in and it will automatically once you hit enable turn the cache on. And so then if you have something like query monitor plug-in, it will actually tell you when something was a hit or miss on the cache. So then you can actually load up a page, see, physically see something that says yes, this hit the object cache or the object cache is in use or the object cache is not working or whatever the case may be. So you know it's on or you know it's off and that way you can test to see that. Who knows? Well, that's more of a testing thing, right? So I've had situations too where let's say... So you get a plug-in or something where you've got lots of things that are being cached, maybe a little too much and what I've seen happen is so you have a caching layer or something you've got a host that's got a nice memcache layer going on and then I've seen situations where for whatever reason that memcache layer just nope, we're not working anymore. So now all that information, that too much information, which was kind of... the cache was not being extremely valuable in the first place because it was exceeding the limits and then just pushing old stuff out faster than it was supposed to. When it actually ever reached the expiration date it just got forced out and new things got pushed in and so maybe like 25% of all requests were actually cached in some way and then when the caching layer went down all of those things went into the database and so now we have hundreds of thousands of extra lines in the database and so now because someone forgot to put an index on the autoload column in the database now the site is just slowing to a crawl so there's all kinds of interesting bugs and things that can happen related to caching there's definitely not one size fits all it depends on the caching it depends on how the caching is being used it depends on what's being cached depends on how much is being cached and all that so yeah, there's a lot of possibility in that arena right, yeah I think transients is a good option for something that's publicly released as long as it's used appropriately, yeah, judiciously, yeah so I think that there's definitely a use case for if you have a plugin that needs to potentially optimize lots of things there's kind of that top layer of things where it's like okay, these are super heavy requests and maybe I'm going to use getTransient for these and they're not going to overload things too much and then there's kind of this next layer of stuff where it's like if I cached all of this and there was no caching layer I would just slow the site down to a crawl I might use persisting or non-persistent caching for some of that especially if it's things that are less likely to be called that kind of thing but maybe if they are called a lot within that runtime so yeah, I think it's definitely being aware of what your application is doing how it's doing it and all of that so yeah, and then caching is one of those things where it's kind of a balancing act so it's kind of trying to find that fine line between performance and actually having fresh data so it's things like let's say Twitter count is an easy use case right so you want to make sure that the number of times somebody shared this particular post on Twitter is accurate but at the same time you don't want to hit the Twitter API all the time to try to figure that out and so of course there's JavaScript things but let's assume that we're doing this on our own in PHP or something so we can make the request to the API and fetch that data and then we can cache it do we cache it for an hour or do we cache it for a day the day may be too much but then again it depends on how often people share your stuff so it's kind of one of those things where if I write a plugin to do it and I say hit it every 5 minutes or 30 minutes or hour or something like that it might even be too much for some people because their stuff they only care about it once a day so being aware that there's kind of this balancing act and sometimes it really depends on the user as to how that will pan out so sometimes it makes sense to actually give the user some control over how often or how long I should say things are cached so these are some resources all of these slides are up on Twitter at the moment I also do this interesting thing where I have a QR code or you can go to the link there and get to it as well but yeah so that's that's caching in general there's like I said a lot to it so if there's questions I think we kind of went through some questions as we go but if we have more questions I forget what time do we have to what time 2.45 gotcha so we have some time we can chat try yeah yeah I haven't used WP rocket but I've heard a lot of good things about it so and there's actually some of the web hosts can actually do that for you and one thing that I try to do is limit the plugins they install and try to see if I can get some sort of external service to do some of that and some of those tools can be things like Cloudflare can actually do some of the page caching for you but I know certain web hosts actually will set it up to where they have reverse proxy caching servers and all that kind of good stuff so maybe worth looking into whether your host does and other of course WordPress specific hosts they actually have their own caching plugins and things and if you run your own stuff next to what they've got it sometimes can even cause issues so yeah exactly yeah but the other thing to be aware of too is that a lot of times plugins do things like page caching full page caching they do not take into consideration things like dynamic functionality that may live on a page so if you have a membership site or something where you're trying to show specific users specific information based off of who they are or what they've done then that won't work I've actually misconfigured Cloudflare one time and so everybody that came to my site saw the WP admin bar with my picture in the corner not that they could actually you know go anywhere with that cause they're not actually logged in but the page response had the actual HTML and JavaScript and everything for that so they could find the links and try to go places they wouldn't get in so right so that's where it gets to be important to figure out how you whitelist things and what URL parameters maybe you should take into consideration for ignoring all of that so e-commerce and all those things can be interesting say that last part again right question for the video so it's a very short question so basically the question was if you're doing some sort of Ajax front end request as opposed to initiating the request from the back end is there a difference in caching ultimately I think if you have caching happening in PHP it shouldn't matter one way or the other there are I know for example when it comes to things like page caching there are some things that is commonly used to again bust the cache for certain users who might be doing a e-commerce checkout or log in as some sort of member to membership site actually looking at the cookie responses and detecting certain WordPress cookies and things is a common way to kind of bust the cache so yeah there's a lot of a lot of different ways that it can be done but as far as the front end you can actually leverage the browser as a cache for Ajax request so if you're making multiple requests potentially you know if your application may or may not know that a request has already been made then that's a problem because you know you could potentially make multiple requests for the exact same information which has not changed so you can actually leverage things like local storage in the browser and write JavaScript that will essentially make a request store it in local storage and then when it makes a request check local storage first and then you know if it's not there then call call the back end um you're totally on your own for cache priming so yeah um yeah but yeah that's another thing a whole other topic too is the idea of cache priming or cache warming so essentially just think about the fact that you know if something is cached let's say it's a full page cache for example if a full page has already been cached for a static site or whatever then somebody hits that it's going to serve that page up really quick um problem is that when that page expires in the cache then well the next person that lands on that is going to have to wait until the server does all its stuff and it's going to be a little slower um but if the um the server is aware of when those things expire and it automatically visits that page before another user does then it gets cached again and it wasn't a user who's sitting around waiting for that page to load so it's kind of the idea of cache warming right uh is that you um that you can actually have a fake request request made by the server or some other technology that will essentially prime that cache for the next person who hits it so a lot of times when it comes to things like object caching if you've cached a WP query instance and you know for example that if someone updates the posts in the admin you're going to have to expire that you might as well go ahead and take the updated version and put it into the cache for them so it's already there for the next call so by putting that uh that extra load time in the admin for the person who made the update then all the other users on the front end will never experience it away so yeah definitely don't don't cache WP query objects um mainly because it's just the sheer uh size as well as sensitivity of actually what's in there um but for example like if you make an API request to Twitter and they just give you a JSON object you know caching that JSON object uh works uh too like you may even consider other options when it comes to caching uh so WordPress obviously provides certain tools but that doesn't mean that those are the only tools that are available um let's say you hit an API and this API has enormously large responses and you don't know what you're going to use out of that but you don't really have to make that many calls to it but it doesn't make sense to store it in the database or anywhere else you can actually just store it in a file uh in the uploads directory for a folder for your plugin or something uh and then you know based off the naming of the file structure you could use that for caching and then you could have some functionality that would clear it uh when it makes new requests or something like that uh and so there's a lot more that you can do beyond what WordPress provides um and there's obviously a lot of other technology that I probably don't even know about so uh uh but yeah so it's being aware of how it works and just kind of some of the options that are available that WordPress provides as well as being creative I guess uh any other questions comments things I didn't cover or may not know about cool so yeah for the video uh plugin called WP cache or rest cache um yeah which will basically um allow you to set headers and different things for filter gotcha so still gonna have the code figuring out some of the headers and whatnot um so I guess in some cases you may want to cache certain rest API calls in other cases you may not want to depending on the data but yeah yeah and again for the video uh uh yeah so there is the uh in the Gutenberg project which uh all the packages they have in there they're they're on NPM for the WordPress organization and they get put into WordPress core uh and they're there now um so there's the data package uh WP data which allow you to it's essentially a wrapper around the redux uh data store uh with some customizations by WordPress itself uh to allow you to actually create your own uh inside of the data store that they have um so yeah have for javascript applications it's really good for uh managing global state so if you make multiple requests you can essentially you know uh create an array inside your data store where you know the key is maybe the ID or whatever of the thing you requested and the response is uh the value um so uh some people make the mistake of trying to nest data in javascript data stores um keeping flat structure makes it a whole lot easier to work with um but there are also some javascript packages which will um uh if you're using like a normal just plain redux store doing some react stuff they have packages that kind of wrap the redux uh data store and automatically handle uh persisting things to the local uh browser cache or browser uh local storage so um there's a lot of interesting projects and things out there that'll make it a lot easier um only downside to local storage is if uh user opens an incognito window or a different browser then none of that's there but as long as they use the same browser uh you're good what's that or yeah they could flush it I guess cool well I guess that's uh that's it for me appreciate it