{"id":376634,"date":"2023-02-01T07:04:33","date_gmt":"2023-02-01T15:04:33","guid":{"rendered":"https:\/\/css-tricks.com\/?p=376634"},"modified":"2023-02-01T07:04:40","modified_gmt":"2023-02-01T15:04:40","slug":"caching-data-in-sveltekit","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/caching-data-in-sveltekit\/","title":{"rendered":"Caching Data in SvelteKit"},"content":{"rendered":"\n
My previous post<\/a> was a broad overview of SvelteKit where we saw what a great tool it is for web development. This post will fork off what we did there and dive into every developer’s favorite topic: caching<\/strong>. So, be sure to give my last post a read if you haven’t already. The code for this post is available on GitHub<\/a>, as well as a live demo<\/a>.<\/p>\n\n\n\n This post is all about data handling. We’ll add some rudimentary search functionality that will modify the page’s query string (using built-in SvelteKit features), and re-trigger the page’s loader. But, rather than just re-query our (imaginary) database, we’ll add some caching so re-searching prior searches (or using the back button) will show previously retrieved data, quickly, from cache. We’ll look at how to control the length of time the cached data stays valid and, more importantly, how to manually invalidate all cached values. As icing on the cake, we’ll look at how we can manually update the data on the current screen, client-side, after a mutation, while still purging the cache.<\/p>\n\n\n\n\n\n\n\n This will be a longer, more difficult post than most of what I usually write since we’re covering harder topics. This post will essentially show you how to implement common features of popular data utilities like react-query<\/a>; but instead of pulling in an external library, we’ll only be using the web platform and SvelteKit features.<\/p>\n\n\n\n Unfortunately, the web platform’s features are a bit lower level, so we’ll be doing a bit more work than you might be used to. The upside is we won’t need any external libraries, which will help keep bundle sizes nice and small. Please don’t use the approaches I’m going to show you unless you have a good reason to.<\/strong> Caching is easy to get wrong, and as you’ll see, there’s a bit of complexity that’ll result in your application code. Hopefully your data store is fast, and your UI is fine allowing SvelteKit to just always request the data it needs for any given page. If it is, leave it alone. Enjoy the simplicity. But this post will show you some tricks for when that stops being the case.<\/p>\n\n\n\n Speaking of react-query, it was just released<\/a> for Svelte! So if you find yourself leaning on manual caching techniques a lot<\/em>, be sure to check that project out, and see if it might help.<\/p>\n\n\n Before we start, let’s make a few small changes to the code we had before<\/a>. This will give us an excuse to see some other SvelteKit features and, more importantly, set us up for success.<\/p>\n\n\n\n First, let’s move our data loading from our loader in Next, let’s take the page loader we had, and simply rename the file from Now let’s add a simple form to our Yep, forms can target directly to our normal page loaders. Now we can add a search term in the search box, hit Enter<\/kbd>, and a “search” term will be appended to the URL’s query string, which will re-run our loader and search our to-do items.<\/p>\n\n\n\nSetting up<\/h3>\n\n\n
+page.server.js<\/code> to an API route<\/a>. We’ll create a
+server.js<\/code> file in
routes\/api\/todos<\/code>, and then add a
GET<\/code> function. This means we’ll now be able to fetch (using the default GET verb) to the
\/api\/todos<\/code> path. We’ll add the same data loading code as before.<\/p>\n\n\n\n
import { json } from \"@sveltejs\/kit\";\nimport { getTodos } from \"$lib\/data\/todoData\";\n\nexport async function GET({ url, setHeaders, request }) {\n const search = url.searchParams.get(\"search\") || \"\";\n\n const todos = await getTodos(search);\n\n return json(todos);\n}<\/code><\/pre>\n\n\n\n
+page.server.js<\/code> to
+page.js<\/code> (or
.ts<\/code> if you’ve scaffolded your project to use TypeScript). This changes our loader to be a “universal” loader rather than a server loader. The SvelteKit docs explain the difference<\/a>, but a universal loader runs on both the server and also the client. One advantage for us is that the
fetch<\/code> call into our new endpoint will run right from our browser (after the initial load), using the browser’s native
fetch<\/code> function. We’ll add standard HTTP caching in a bit, but for now, all we’ll do is call the endpoint.<\/p>\n\n\n\n
export async function load({ fetch, url, setHeaders }) {\n const search = url.searchParams.get(\"search\") || \"\";\n\n const resp = await fetch(`\/api\/todos?search=${encodeURIComponent(search)}`);\n\n const todos = await resp.json();\n\n return {\n todos,\n };\n}<\/code><\/pre>\n\n\n\n
\/list<\/code> page:<\/p>\n\n\n\n
<div class=\"search-form\">\n <form action=\"\/list\">\n <label>Search<\/label>\n <input autofocus name=\"search\" \/>\n <\/form>\n<\/div><\/code><\/pre>\n\n\n\n