Check out a free preview of the full Fullstack Svelte with SvelteKit course

The "Hooks" Lesson is part of the full, Fullstack Svelte with SvelteKit course featured in this preview video. Here's what you'd learn in this lesson:

Rich demonstrates intercepting and overriding SvelteKit's default behavior using hooks and the contents of the RequestEvent object. The handleFetch and handleError hooks are also covered in this segment.


Transcript from the "Hooks" Lesson

>> Okay, so we've finished part 3 of the tutorial. We've learned all of the basics of SvelteKit, and it's now time to move on to part 4 where we're gonna learn some advanced techniques. We're gonna begin with hooks. SvelteKit provides several hooks, which are ways to intercept the framework's default behavior.

The most elementary of these hooks is the handle hook. It lives in a file called hooks.server.js, and it receives an event object along with a resolve function, and it returns a response object. Now, resolve here, this is where the magic happens. This is where SvelteKit will match the incoming request URL to a route of your app.

It'll import the relevant code, which means the page.server.js, and the page.svelte files and so on. It loads the data needed by the route, and then it generates the response. And so, this is what the default handle hook looks like if you don't have a hook.server.js file, this is essentially what SvelteKit is, is stubbing in on your behalf.

And when you're rendering a page, you can modify the HTML that gets generated. So here, I'm gonna add an option to resolve. I'm gonna use the transform page chunk method, which is gonna accept some HTML, and it's gonna return the HTML with some modifications. Refresh the page now.

The body tag has now been manipulated by transformPageChunk. So that's use useful for things like internationalization you can specify the lang attribute of your HTML element for example, but you can also use this hook to create entirely new routes that don't exist in your directory structure. For example, we create a new route called ping.

We can return a response, pong. Now, if we navigate to that URL, we're not gonna be able to navigate to it using the client side router because it doesn't exist as a client side route, but it will go to the server and return that response from handle. Now that event object that we were looking at, this thing here, is the same object which is an instance of something called a RequestEvent which you can read about in the documentation that is passed into API routes in server.js files, form actions in page.server.js files, and also your load functions in page.server.js and layout.server.js.

The event object contains a number of useful properties and methods, some of which you've already encountered in this workshop, the cookies API, a fetch an implementation of the fetch API that comes with some additional powers. The function for getting the user's client address, which is useful for things like geolocation.

And isDataRequest object, which is true if we're hitting a load function as a result of a client-side navigation, as opposed to rendering an entire HTML document. Event.locals is a place to put arbitrary data. Params is the current route parameters. Route is an object with an ID property representing the route that was matched, if any.

We've got the setHeaders function that we saw before, and we have that URL object with properties like origin, host name, path name, search params, and so on. So a useful pattern that you'll often use in SvelteKit is to add some data to event.locals inside handle so that it can be read in subsequent load functions.

So in our hook, we're gonna do event.locals.answer = 42, and then we can read that data inside a load function. Gonna add the event here, and then we're gonna put that data inside this template string. Okay, in a real application, this is the sort of thing that you might use with information about the current user.

You'll do your authentication in the handle hook, grab an object that represents the current user and put that on event dot local, so that all of your server load functions can access it. And one of the things that we haven't seen yet is the fetch method that exists on the event object.

So you've probably used Fetch in the browser, if not, again, MDN has all the information that you need to know how to use Fetch. The Fetch that we get on the event object is a little bit different because it can be used to make credential requests on the server, it inherits the cookies and the authorization header, that were passed from the original request from the browser.

That's not something that you get if you're using something like NodeFetch. It can make relative requests on the server, whereas normally in Node or server environments, Fetch requires a fully qualified URL. And for internal requests, for example, if inside your load function you make a fetch call to one of your own API routes, that doesn't issue an HTTP request, it just calls the function directly, so it's a lot more efficient.

And its behavior can be modified with the handleFetch hook, which by default looks like this. It's very similar to the handle hook in that it takes the event object and an implementation of fetch. And by default, all we are doing is fetching the request that was passed in, but we can change that.

For example, we could respond to requests for this file, with responses from this file instead, just by changing the behavior of event.fetch. So, open up hooks.server.js and then, inside here, we're gonna find out what the requested URL is. To create a new URL object from request.url, request.url is a string.

It's a fully qualified URL. But when we call new URL, we get that nice object with the path name and the origin and all of that other useful stuff. So if the path name that's been requested is /a, then we're gonna return a response by fetching b instead.

Okay, so in our routes/+page.server.js, we are fetching /a, that isn't being redirected inside handle fetch, and we're getting data from /b instead. Now according to everything we've seen so far, that's probably not very useful, but later we're gonna to learn about something called universal load functions, and that's what this feature is really designed for.

In that scenario, handleFetch is useful if you are making requests to a public URL, like From the browser, you wanna go to that public URL, but if you're making the fetch from the server, then you don't wanna go via the public Internet. You don't wanna go through the load balancers and proxies and whatever between the server that's rendering the HTML, and the server that's serving your API, because they can just talk directly to each other.

So you can use handle fetch in that context to change requests for, to localhost or whatever. The last hook that we're gonna learn about is the handle error hook. This lets you intercept those unexpected errors that we talked about earlier and trigger some behavior, maybe you wanna ping a Slack channel, or you wanna send some data to an error logging service.

So, to recap, an unexpected error is one that was not created with the error helper from SvelteKit. And generally, when you see an unexpected error, it means that something in your app needs fixing. Default behavior is just to log the error. So if you haven't defined handleError, then it basically behaves a little bit like this.

And so in this app, if we navigate to the bad place, we'll see an internal error message, but we're not seeing the error that was actually thrown from our load function, which says, this is the bad place. But if we open up the terminal, then we will see that error message logged, which will help us diagnose the problem and hopefully fix it.

So once again, the reason that we do this is because error messages by their very nature can contain unexpected information. You don't control it. It could be coming from a third party library or something else, and it could contain sensitive information that could reveal information about your setup to people who wish you harm.

And so the default behavior in SvelteKit is to redact that information and just show a generic internal error message instead. So the error object that is available to your application, whether that's represented as the page.error, property in your error.svelte pages or the sveltekit.error property inside your source/error.html, is just this object here.

A plain object with a message that says internal error or in the case of a 404, it'll say not found. In some situations, you may want to customize a subject. So in here, instead of allowing SvelteKit to generate that internal error object, we could return an object of our own.

Say everything is fine, refresh the page, and you can see that is now being shown, and we could add a code that goes with it. We can essentially add any serializable data in here. And so we can now reference properties other than the message inside our custom error page.

We'll create a custom error page, click the button here, plus error.svelte. We begin by importing the page store again. We'll show the page status. It's gonna be 500. We'll print the error message that was included on that object, but now we can also add the code property that we added.

So that's the sort of thing that might be useful to show to your users so that they can quote that in correspondence with your customer service people or something like that. It's also something that you might use when you're sending details of the error to an error logging service.

So those are the three hooks that we have available handle, handleFetch and handleerror, and they allow us pretty broad array of customizations of the application.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now