Introduction to SvelteKit Data Loading
In today's video, we're going to take a look at how to fetch or load data in the latest version of SvelteKit. If you're unaware, SvelteKit recently underwent some breaking changes which kind of invalidated a lot of the content that's already out there on this subject. So I'm hoping that this video will help some of you all fill in the gaps and get on your way to developing your own SvelteKit applications, and this is a critical piece of it.
So let's first start out with some of the Svelte documentation to understand how they expect us to load data within a SvelteKit application. So it states that a +page.svelte gets its data from a load function. And if the load function is defined in a page.js, it will run on both the server and in the browser. And then instead, if it's defined in a page.server, it will only run on the server in which you can access private environment variables and make database calls. But in both cases, the return value, if there is one, must be an object. Pretty straightforward.
We're going to have two... we're actually going to cover both the page.server and the page.js in this video. And I've actually set up a little bit of an example application to demonstrate some of these concepts. I know it's beautifully designed. We have a nice nav bar at the top which has Home, Shop, and Movies. The shop page is going to be where we're going to start, and that's going to be with the +page.js.
Passing Static Data with +page.js
So our application is just set up like this: we just have a shop and a movies directory, page.js, page.svelte. That's all there is. Nothing else is really here. As the documentation said, a page.svelte receives its data from a load function, which is defined inside of a page.js file. So that means whatever we return here should get passed into page.svelte. So let's try it out. So if we type product here and just say iPhone 14, when we go into page.svelte we open up some script tags, we can actually access that object with export let data. And then we can console log this data as well. Let me open up my console over here on the browser, and you can see here that we are getting that product back.
So that's super simple to pass data between the page.js and the page.svelte. What we're going to do though is we actually want to request some external data. We're going to be using the DummyJSON API, which allows us, you know, as the name implies, to get some dummy or fake JSON data. And they have some products that we're going to get access to.
Fetching External Data with SvelteKit's fetch
And we're going to do a limit of 10, so it allows us to limit and skip products. So we're just going to grab this. Again, this isn't too important, the specific API. We're just hitting an API endpoint that gives us some data back, right? It's going to look like this. It's going to be a response object with a products property that has an array of products, right? And that's what we're going to try to access.
So inside of our page.js, before we can make any external fetch requests, we need to bring in SvelteKit's fetch, right? And that is passed as an input method to this load function. And we can check out what this does if we go into here and we type fetch, we can see that it's equivalent to the native Fetch with API with a few additional features. I'm not going to read through all these, but essentially it's what enables some of the SvelteKit magic to happen behind the scenes between the server and client interactions.
Okay, now that we have this method here, we can actually use it as normal. It's not different to us as the developer, it just does different things behind the scenes. So I'm going to show you two ways to do this. I'm going to show you the way that I see people do it pretty often, or I've seen people do it recently, and then I'm going to show you a better way to do this, and I'm going to demonstrate why one way is better than another.
So let's just say we're going to get some products. We would call a normal fetch request like this, and then we need to get the product data, productRes.json. This should be familiar to most of you. And then we will say const products = productsData.products. Now our product data is inside of this product object here, right? And that's the reason we did .products, is because the response object looks like this and we want to access the products, right? So now we can pass this here into our shop with the shorthand, which is like this, or we can do it the regular way like this. Either way, it doesn't matter.
Then inside of our page.svelte, as you can see, we have products that are being console logged, and we can see that we have a products array here. So what I like to do inside of these page.sveltes is to destructure the data object. It just makes it cleaner and easier to work with. So we can say const { products } = data, which is the same thing as doing const products = data.products, right? It just gets messy if you have a bunch of things you're trying to get out of that data object.
Rendering Data and Understanding SSR vs. CSR
Now we can render out all these products on the page pretty simply, right? We can just go each products as product, and then we'll say <h1>{product.title}</h1>, and then we'll say product.description. And we'll actually make this an H2 and this a P tag. So now you can see on our shop page we have all this content that's being pulled. And if we navigate back and forth, we can see now behind the scenes what's happening actually is that if we just refresh this page all together and we take full control of our navigation, we're going to see that no fetch requests were made on our client. Everything was server-side rendered.
But then if we go to, let's just say, the home page and then the shop page, we can see that the products are fetched through a fetch request on the client, right? So that initial page request is handled by server-side rendering, and then subsequent requests are handled with client-side rendering. And then if JavaScript were to be turned off, then server submitting would take over for the entire application. So I can say disable JavaScript, and now if I clear this out and I navigate back and forth, you can see that it still works just as expected. It's just not happening from the client.
And the reason they do that for the page.js files is because let's imagine that you had the client in this in Midwest United States, you had your SvelteKit server or back end on the east coast, and the external API on the west coast. It doesn't really make much sense for you to have to take, you know, an extra round trip every single time that you wanted to get that data if you didn't have to, right? So with the page.js, this is a flex, essentially, between server and client, so it's pretty cool.
The Problem with Sequential Fetches (Waterfalls)
I want to finish showing you the way that I was saying was the way I've seen most people do this. Let's just say we want to get some users, right? Say this page requires multiple different API calls, different data sources. They're not really related to each other whatsoever. Let's just replace product with users here, and then we'll return this the same way.
All right, let's re-enable JavaScript. I have enabled... still enable JavaScript. Okay, now when we refresh this page, we're going to see that... of course I'm getting an error here, let's see what I do wrong. I have two s's here. There we go. Okay, so as you can see, this page is working just as expected. We're not doing anything with these users just now on the page. We're not doing anything with them yet. But now when we navigate to Home and to Shop, we can see that we're making two fetch requests, one to products and one to users. And what I want you to pay attention to here is this: we've actually created a bit of a waterfall here because the users request has to wait for the products request to finish before it even starts. And if these two are independent of one another, like we're not using anything from this products response here inside of our users request, so it doesn't really make sense for them to be, you know, chained like that in a waterfall.
Solution: Parallel Fetching with Top-Level Promises
So what we can do is, let's check the SvelteKit documentation, I'm going to show you where it states this at. So you can see it says that in the output—this is still under the load section here—in the output, top-level promises will be awaited, which makes it easy to return multiple promises without creating a waterfall, which is literally what we just created right here. So what we can do is we can actually just return these promises from our load function and they'll be resolved and awaited on the page.
So let's just see how that looks. Here we can define a function called fetchProducts, and then we'll do the same thing for users. And then we'll need to adjust our last line here. We're just going to return productData.products and return userData.users is what this is supposed to be actually. It's not products. It doesn't matter either way. You're still able to see it so, and we're not using these users, I just want to show this as an example if you do need to request data for multiple sources, right?
Now, instead of returning, you know, just products, we're actually going to return this call: fetchProducts and fetchUsers. Because these both return... this returns a promise and this returns a promise, and like it says here, they will be awaited. So you can return them just like that. And now if we go here and we save, it's going to server-side render first because page refreshed, but if we go to Home and then to Shop again, we're going to see that those fetch requests were made. But if we check it out here, we can see that now they're running in parallel, which is how it should have been from the beginning, right?
So this is the recommended way to, you know, when you have requests that are non-related, and even if they were, you would want to wrap them in the same function. That way if ever down the line you want to, you know, return multiple different promises, you can do so without having to await them both inside the load function. And most people are spoiled, and I think we're all spoiled when it comes to to internet speeds, but there's people that are still running on, you know, 3G for example, and you can see the difference in the speeds when you when you switch over to 3G.
Okay, enough of that. I'm going to remove the users here now because that was just to demonstrate. Now what I want to show you is pre-fetching, which is a pretty cool feature that SvelteKit has. So let's just say that we have users on a slower connection, or just anybody really, want to improve the overall experience of our site. What we can do is, since we know it's going to be populated on this shop page, we know that same request is being made, we can actually go ahead and have SvelteKit prefetch that data when the user hovers over this link.
And we can accomplish that by going into our layout here, and in this a tag here, we can just say data-sveltekit-prefetch. And I want you to watch the browser here as what happens when you hover over shop. You can see that fetch request is made. As soon as we click it, it's done loading instantaneously, right? You can see every single time it automatically loads the page before, and you know if someone's intent is to click it, that small amount of time still will make a difference in the user experience, especially if someone's running on something like Fast 3G, for example. When we were to hard refresh this page, we can see how long that even takes with server-side rendering happening, and then if you hover over shop, we can see that it takes a couple seconds before the product... before the fetch request even happens. But at least by the time they clicked on it, you know, that would cut off some of that time. That is the page.js.
Server-Only Data Loading with +page.server.js
Now I want to talk about the page.server.js, which we're going to use within our movies page. So the reason I decided to do the movies page as a page.server.js is because I'm actually accessing data from The Movie Database API, which requires me to have an API key. So that's something I would not want to expose on the client side, right? And we're just going to grab a few movies from this and render them into our page. We can see here, here is the URL to get those movies. You can see my API key, I know, but it's okay because I'm going to delete it after this video, so don't stress out. We do have it defined as an environment variable as well that we're going to be able to pull in into our page.server.
Unlike the page.js file, how the page.server.js does not take in that fetch method from SvelteKit; we just use regular node-fetch for this. But I do need to import the environment variable, so I can import dotenv.config() so I can actually access that environment variable. And we're just going to make a fetch request to that URL I just copied. So we can say const fetchMovies, and then const res = await fetch. I'm going to take the API key out and I'm actually going to pass that here through my environment variables, tmdb_api_key I think is what it was. Yep, so that's the request there. And then we're going to take response or data = await res.json(). And then I'm just going to return data.results, I think is what it is.
So we'll do the same thing we did on the previous page where we're going to return movies, and then we're going to pass this function as the value. So the same thing happens for page.server, it will still await and still render that out properly.
Rendering Server-Loaded Data and Final Comparison
Now we can go over to our movies page and we can see here that we do, in fact, get a movies array, which is fantastic. What we can do is on our page.svelte we can render those out. So first I'm going to destructure that, so it's a const { movies } = data. And then movies... we'll just do an each block: each movies as movie, and then I think they have like movie.title and movie.overview, and movie.overview. It keeps auto-correcting me there. All right, let's see how this looks. Yep, the movies did, in fact, render right away.
And just to kind of demonstrate that this is not happening on the browser whatsoever, we can just throw a console log inside of this function here and say server load ran. And then inside of our console here on the browser, we will never see that happen. Same thing with the network tab, we won't ever see any fetch requests for this page, regardless if we move back and forth within this application. The only fetch request we're seeing here is from us hovering over that.
I mean, we can actually apply the same... let's see here. Let's clear this up. We can actually apply the same prefetch for server side as well. So we can go into our navigation here, let's just add that to the movies a tag as well. And we'll see behind the scenes, you'll be able to see the traffic here happening. If we hover over... let's go to Home first, then we hover over Movies, we could see that that server-side rendering happened in the background. So now I'm going to click Movies, boom, it's instantly there.
Conclusion
So the same thing applies. Really the major difference between these two is again, the fetch request or the fetch method doesn't get passed into the server side, and you can actually use private environment variables. You can use public environment variables in the regular +page.js files, but for ones that are more secretive that you would never want to expose on the client, that's where you would use the +page.server.js.
So I hope this video has been informative. If you have any questions, I do have a Discord server that I will leave a link to in the video description. Feel free to hop on there and ask me any questions you may have, and if not, I will see you guys in the next video.